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>
|
||||
<translation type="unfinished"></translation>
|
||||
</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>
|
||||
<name>BrowserSettingsWidget</name>
|
||||
@ -5992,10 +6001,6 @@ Do you want to overwrite it?
|
||||
<source>KeePassXC - Passkey Import</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Do you want to import the Passkey?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>URL: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -6004,10 +6009,6 @@ Do you want to overwrite it?
|
||||
<source>Username: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use default group (Imported Passkeys)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Group</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -6016,10 +6017,6 @@ Do you want to overwrite it?
|
||||
<source>Database</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select Database</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Import Passkey</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -6033,11 +6030,23 @@ Do you want to overwrite it?
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Database: %1</source>
|
||||
<source>Import the following Passkey:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</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>
|
||||
</message>
|
||||
</context>
|
||||
@ -6075,6 +6084,12 @@ Do you want to overwrite it?
|
||||
<source>Cannot import Passkey file "%1". Private key is missing or malformed.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot import Passkey file "%1".
|
||||
The following data is missing:
|
||||
%2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<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_NOT_HTTP_AUTH = QStringLiteral("BrowserNotHttpAuth");
|
||||
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);
|
||||
|
||||
@ -775,6 +773,20 @@ void BrowserService::addPasskeyToEntry(Entry* entry,
|
||||
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->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 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());
|
||||
#endif
|
||||
res["password"] = entry->resolveMultiplePlaceholders(entry->password());
|
||||
res["name"] = entry->resolveMultiplePlaceholders(entry->title());
|
||||
res["uuid"] = entry->resolveMultiplePlaceholders(entry->uuidToHex());
|
||||
@ -1291,8 +1309,7 @@ QList<Entry*> BrowserService::getPasskeyEntries(const QString& rpId, const Strin
|
||||
{
|
||||
QList<Entry*> entries;
|
||||
for (const auto& entry : searchEntries(rpId, "", keyList, true)) {
|
||||
if (entry->attributes()->hasKey(BrowserPasskeys::KPEX_PASSKEY_PRIVATE_KEY_PEM)
|
||||
&& entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY) == rpId) {
|
||||
if (entry->hasPasskey() && entry->attributes()->value(BrowserPasskeys::KPEX_PASSKEY_RELYING_PARTY) == rpId) {
|
||||
entries << entry;
|
||||
}
|
||||
}
|
||||
@ -1419,14 +1436,34 @@ bool BrowserService::handleURL(const QString& entryUrl,
|
||||
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) {
|
||||
return m_currentDatabaseWidget->database();
|
||||
}
|
||||
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()
|
||||
{
|
||||
QList<DatabaseWidget*> databaseWidgets;
|
||||
|
@ -84,7 +84,9 @@ public:
|
||||
QString getCurrentTotp(const QString& uuid);
|
||||
void showPasswordGenerator(const KeyPairMessage& keyPairMessage);
|
||||
bool isPasswordGeneratorRequested() const;
|
||||
QSharedPointer<Database> getDatabase(const QUuid& rootGroupUuid = {});
|
||||
QSharedPointer<Database> selectedDatabase();
|
||||
QList<QSharedPointer<Database>> getOpenDatabases();
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
QJsonObject
|
||||
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_NOT_HTTP_AUTH;
|
||||
static const QString OPTION_OMIT_WWW;
|
||||
static const QString ADDITIONAL_URL;
|
||||
|
||||
signals:
|
||||
void requestUnlock();
|
||||
@ -191,7 +192,6 @@ private:
|
||||
const QString& siteUrl,
|
||||
const QString& formUrl,
|
||||
const bool omitWwwSubdomain = false);
|
||||
QSharedPointer<Database> getDatabase();
|
||||
QString getDatabaseRootUuid();
|
||||
QString getDatabaseRecycleBinUuid();
|
||||
bool checkLegacySettings(QSharedPointer<Database> db);
|
||||
|
@ -385,7 +385,8 @@ QStringList Entry::getAllUrls() const
|
||||
}
|
||||
|
||||
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);
|
||||
if (!additionalUrl.isEmpty()) {
|
||||
urlList << resolveMultiplePlaceholders(additionalUrl);
|
||||
@ -545,6 +546,11 @@ bool Entry::hasTotp() const
|
||||
return !m_data.totpSettings.isNull();
|
||||
}
|
||||
|
||||
bool Entry::hasPasskey() const
|
||||
{
|
||||
return m_attributes->hasPasskey();
|
||||
}
|
||||
|
||||
QString Entry::totp() const
|
||||
{
|
||||
if (hasTotp()) {
|
||||
|
@ -121,6 +121,7 @@ public:
|
||||
void setExcludeFromReports(bool state);
|
||||
|
||||
bool hasTotp() const;
|
||||
bool hasPasskey() const;
|
||||
bool isExpired() const;
|
||||
bool willExpireInDays(int days) const;
|
||||
bool isRecycled() const;
|
||||
|
@ -34,6 +34,7 @@ const QString EntryAttributes::SearchInGroupName = "SearchIn";
|
||||
const QString EntryAttributes::SearchTextGroupName = "SearchText";
|
||||
|
||||
const QString EntryAttributes::RememberCmdExecAttr = "_EXEC_CMD";
|
||||
const QString EntryAttributes::AdditionalUrlAttribute = "KP2A_URL";
|
||||
const QString EntryAttributes::PasskeyAttribute = "KPEX_PASSKEY";
|
||||
|
||||
EntryAttributes::EntryAttributes(QObject* parent)
|
||||
@ -52,6 +53,18 @@ bool EntryAttributes::hasKey(const QString& key) const
|
||||
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> customKeys;
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -33,6 +33,7 @@ public:
|
||||
explicit EntryAttributes(QObject* parent = nullptr);
|
||||
QList<QString> keys() const;
|
||||
bool hasKey(const QString& key) const;
|
||||
bool hasPasskey() const;
|
||||
QList<QString> customKeys() const;
|
||||
QString value(const QString& key) const;
|
||||
QList<QString> values(const QList<QString>& keys) const;
|
||||
@ -61,6 +62,7 @@ public:
|
||||
static const QString NotesKey;
|
||||
static const QStringList DefaultAttributes;
|
||||
static const QString RememberCmdExecAttr;
|
||||
static const QString AdditionalUrlAttribute;
|
||||
static const QString PasskeyAttribute;
|
||||
static bool isDefaultAttribute(const QString& key);
|
||||
static bool isPasskeyAttribute(const QString& key);
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "core/Global.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QList>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
class QIODevice;
|
||||
@ -100,6 +101,19 @@ namespace Tools
|
||||
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"});
|
||||
|
||||
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
|
||||
* 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();
|
||||
if (newUrl != url) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
@ -564,7 +564,12 @@ void DatabaseTabWidget::showPasskeys()
|
||||
|
||||
void DatabaseTabWidget::importPasskey()
|
||||
{
|
||||
currentDatabaseWidget()->switchToImportPasskey();
|
||||
currentDatabaseWidget()->showImportPasskeyDialog();
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::importPasskeyToEntry()
|
||||
{
|
||||
currentDatabaseWidget()->showImportPasskeyDialog(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -88,6 +88,7 @@ public slots:
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
void showPasskeys();
|
||||
void importPasskey();
|
||||
void importPasskeyToEntry();
|
||||
#endif
|
||||
void performGlobalAutoType(const QString& search);
|
||||
void performBrowserUnlock();
|
||||
|
@ -1407,11 +1407,21 @@ void DatabaseWidget::switchToPasskeys()
|
||||
m_reportsDialog->activatePasskeysPage();
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToImportPasskey()
|
||||
void DatabaseWidget::showImportPasskeyDialog(bool isEntry)
|
||||
{
|
||||
PasskeyImporter passkeyImporter;
|
||||
|
||||
if (isEntry) {
|
||||
auto currentEntry = currentSelectedEntry();
|
||||
if (!currentEntry) {
|
||||
return;
|
||||
}
|
||||
|
||||
passkeyImporter.importPasskey(m_db, currentEntry);
|
||||
} else {
|
||||
passkeyImporter.importPasskey(m_db);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void DatabaseWidget::performUnlockDatabase(const QString& password, const QString& keyfile)
|
||||
|
@ -214,7 +214,7 @@ public slots:
|
||||
void switchToDatabaseSettings();
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
void switchToPasskeys();
|
||||
void switchToImportPasskey();
|
||||
void showImportPasskeyDialog(bool isEntry = false);
|
||||
#endif
|
||||
void switchToOpenDatabase();
|
||||
void switchToOpenDatabase(const QString& filePath);
|
||||
|
@ -135,6 +135,10 @@ MainWindow::MainWindow()
|
||||
m_entryContextMenu->addSeparator();
|
||||
m_entryContextMenu->addAction(m_ui->actionEntryAutoType);
|
||||
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->actionEntryClone);
|
||||
m_entryContextMenu->addAction(m_ui->actionEntryDelete);
|
||||
@ -441,6 +445,7 @@ MainWindow::MainWindow()
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
m_ui->actionPasskeys->setIcon(icons()->icon("passkey"));
|
||||
m_ui->actionImportPasskey->setIcon(icons()->icon("document-import"));
|
||||
m_ui->actionEntryImportPasskey->setIcon(icons()->icon("document-import"));
|
||||
#endif
|
||||
|
||||
m_actionMultiplexer.connect(
|
||||
@ -491,6 +496,7 @@ MainWindow::MainWindow()
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
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->actionEntryImportPasskey, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importPasskeyToEntry()));
|
||||
#endif
|
||||
connect(m_ui->actionImportCsv, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importCsv()));
|
||||
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
|
||||
m_ui->actionPasskeys->setEnabled(true);
|
||||
m_ui->actionImportPasskey->setEnabled(true);
|
||||
m_ui->actionEntryImportPasskey->setEnabled(true);
|
||||
#endif
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
bool singleEntryHasSshKey =
|
||||
@ -1060,9 +1067,11 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
m_ui->actionPasskeys->setEnabled(false);
|
||||
m_ui->actionImportPasskey->setEnabled(false);
|
||||
m_ui->actionEntryImportPasskey->setEnabled(false);
|
||||
#else
|
||||
m_ui->actionPasskeys->setVisible(false);
|
||||
m_ui->actionImportPasskey->setVisible(false);
|
||||
m_ui->actionEntryImportPasskey->setVisible(false);
|
||||
#endif
|
||||
|
||||
m_searchWidgetAction->setEnabled(false);
|
||||
|
@ -342,6 +342,8 @@
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionEntryAutoType"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionEntryImportPasskey"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionEntryOpenUrl"/>
|
||||
<addaction name="actionEntryDownloadIcon"/>
|
||||
<addaction name="separator"/>
|
||||
@ -730,6 +732,14 @@
|
||||
<string>Perform &Auto-Type</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionEntryImportPasskey">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Import Passkey</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionEntryAutoTypeUsername">
|
||||
<property name="enabled">
|
||||
<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) 2021 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -329,11 +329,11 @@ void EditEntryWidget::insertURL()
|
||||
{
|
||||
Q_ASSERT(!m_history);
|
||||
|
||||
QString name(BrowserService::ADDITIONAL_URL);
|
||||
QString name(EntryAttributes::AdditionalUrlAttribute);
|
||||
int i = 1;
|
||||
|
||||
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++;
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ QVariant EntryURLModel::data(const QModelIndex& index, int role) const
|
||||
const auto urlValid = urlTools()->isUrlValid(value);
|
||||
|
||||
// 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);
|
||||
|
||||
const auto duplicateUrl =
|
||||
@ -148,7 +148,7 @@ void EntryURLModel::updateAttributes()
|
||||
|
||||
const auto attributesKeyList = m_entryAttributes->keys();
|
||||
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);
|
||||
m_urls.append(qMakePair(key, value));
|
||||
|
||||
|
@ -27,33 +27,41 @@
|
||||
PasskeyImportDialog::PasskeyImportDialog(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, m_ui(new Ui::PasskeyImportDialog())
|
||||
, m_useDefaultGroup(true)
|
||||
{
|
||||
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
|
||||
|
||||
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->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->useDefaultGroupCheckbox, SIGNAL(stateChanged(int)), SLOT(useDefaultGroupChanged()));
|
||||
}
|
||||
|
||||
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->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;
|
||||
for (auto dbWidget : getMainWindow()->getOpenDatabases()) {
|
||||
@ -61,34 +69,96 @@ void PasskeyImportDialog::setInfo(const QString& url, const QString& username, c
|
||||
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;
|
||||
}
|
||||
|
||||
QUuid PasskeyImportDialog::getSelectedGroupUuid()
|
||||
QUuid PasskeyImportDialog::getSelectedEntryUuid() const
|
||||
{
|
||||
return m_selectedEntryUuid;
|
||||
}
|
||||
|
||||
QUuid PasskeyImportDialog::getSelectedGroupUuid() const
|
||||
{
|
||||
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();
|
||||
for (const auto& group : database->rootGroup()->groupsRecursive(true)) {
|
||||
if (!group || group->isRecycled() || group == database->metadata()->recycleBin()) {
|
||||
m_ui->selectGroupComboBox->addItem(tr("Default Passkeys group (Imported Passkeys)"), {});
|
||||
|
||||
for (const auto& group : m_selectedDatabase->rootGroup()->groupsRecursive(true)) {
|
||||
if (!group || group->isRecycled() || group == m_selectedDatabase->metadata()->recycleBin()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -96,26 +166,20 @@ void PasskeyImportDialog::addGroups(const QSharedPointer<Database>& database)
|
||||
}
|
||||
}
|
||||
|
||||
void PasskeyImportDialog::selectDatabase()
|
||||
void PasskeyImportDialog::changeDatabase(int index)
|
||||
{
|
||||
auto selectedDatabase = browserService()->selectedDatabase();
|
||||
if (!selectedDatabase) {
|
||||
return;
|
||||
m_selectedDatabaseUuid = m_ui->selectDatabaseCombobBox->itemData(index).value<QUuid>();
|
||||
m_selectedDatabase = browserService()->getDatabase(m_selectedDatabaseUuid);
|
||||
emit updateGroups();
|
||||
}
|
||||
|
||||
m_selectedDatabase = selectedDatabase;
|
||||
m_ui->selectDatabaseLabel->setText(QString("Database: %1").arg(getDatabaseName(m_selectedDatabase)));
|
||||
|
||||
addGroups(m_selectedDatabase);
|
||||
void PasskeyImportDialog::changeEntry(int index)
|
||||
{
|
||||
m_selectedEntryUuid = m_ui->selectEntryComboBox->itemData(index).value<QUuid>();
|
||||
}
|
||||
|
||||
void PasskeyImportDialog::changeGroup(int index)
|
||||
{
|
||||
m_selectedGroupUuid = m_ui->selectGroupComboBox->itemData(index).value<QUuid>();
|
||||
}
|
||||
|
||||
void PasskeyImportDialog::useDefaultGroupChanged()
|
||||
{
|
||||
m_ui->selectGroupComboBox->setEnabled(!m_ui->useDefaultGroupCheckbox->isChecked());
|
||||
m_useDefaultGroup = m_ui->useDefaultGroupCheckbox->isChecked();
|
||||
emit updateEntries();
|
||||
}
|
||||
|
@ -36,25 +36,33 @@ public:
|
||||
explicit PasskeyImportDialog(QWidget* parent = nullptr);
|
||||
~PasskeyImportDialog() override;
|
||||
|
||||
void setInfo(const QString& url, const QString& username, const QSharedPointer<Database>& database);
|
||||
QSharedPointer<Database> getSelectedDatabase();
|
||||
QUuid getSelectedGroupUuid();
|
||||
bool useDefaultGroup();
|
||||
void setInfo(const QString& url, const QString& username, const QSharedPointer<Database>& database, bool isEntry);
|
||||
QSharedPointer<Database> getSelectedDatabase() const;
|
||||
QUuid getSelectedEntryUuid() const;
|
||||
QUuid getSelectedGroupUuid() const;
|
||||
bool useDefaultGroup() const;
|
||||
bool createNewEntry() const;
|
||||
|
||||
private:
|
||||
QString getDatabaseName(const QSharedPointer<Database>& database) const;
|
||||
void addGroups(const QSharedPointer<Database>& database);
|
||||
void addDatabases();
|
||||
|
||||
signals:
|
||||
void updateEntries();
|
||||
void updateGroups();
|
||||
|
||||
private slots:
|
||||
void selectDatabase();
|
||||
void addEntries();
|
||||
void addGroups();
|
||||
void changeDatabase(int index);
|
||||
void changeEntry(int index);
|
||||
void changeGroup(int index);
|
||||
void useDefaultGroupChanged();
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::PasskeyImportDialog> m_ui;
|
||||
QSharedPointer<Database> m_selectedDatabase;
|
||||
QUuid m_selectedDatabaseUuid;
|
||||
QUuid m_selectedEntryUuid;
|
||||
QUuid m_selectedGroupUuid;
|
||||
bool m_useDefaultGroup;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_PASSKEYIMPORTDIALOG_H
|
||||
|
@ -6,10 +6,22 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>405</width>
|
||||
<height>227</height>
|
||||
<width>500</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</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">
|
||||
<string>KeePassXC - Passkey Import</string>
|
||||
</property>
|
||||
@ -24,7 +36,7 @@
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Do you want to import the Passkey?</string>
|
||||
<string>Import the following Passkey:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
@ -52,80 +64,62 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="useDefaultGroupCheckbox">
|
||||
<property name="text">
|
||||
<string>Use default group (Imported Passkeys)</string>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</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>
|
||||
<layout class="QHBoxLayout" name="selectGroupHorLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="selectGroupLabel">
|
||||
<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>
|
||||
<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>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="selectGroupComboBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="selectDatabaseHorLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="selectDatabaseLabel">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="entryLabel">
|
||||
<property name="text">
|
||||
<string>Database</string>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>false</bool>
|
||||
<string>Entry</string>
|
||||
</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 row="2" column="1">
|
||||
<widget class="QComboBox" name="selectEntryComboBox"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="selectDatabaseButton">
|
||||
<property name="text">
|
||||
<string>Select Database</string>
|
||||
</property>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "browser/BrowserService.h"
|
||||
#include "core/Entry.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Tools.h"
|
||||
#include "gui/FileDialog.h"
|
||||
#include "gui/MessageBox.h"
|
||||
#include <QFileInfo>
|
||||
@ -29,7 +30,7 @@
|
||||
|
||||
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 fileName =
|
||||
@ -47,10 +48,10 @@ void PasskeyImporter::importPasskey(QSharedPointer<Database>& database)
|
||||
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 passkeyObject = browserMessageBuilder()->getJsonObject(fileData);
|
||||
@ -61,18 +62,20 @@ void PasskeyImporter::importSelectedFile(QFile& file, QSharedPointer<Database>&
|
||||
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 missingKeys = Tools::getMissingValuesFromList<QString>(passkeyObject.keys(),
|
||||
QStringList() << "relyingParty"
|
||||
<< "url"
|
||||
<< "username"
|
||||
<< "credentialId"
|
||||
<< "userHandle"
|
||||
<< "privateKey");
|
||||
|
||||
if (relyingParty.isEmpty() || username.isEmpty() || credentialId.isEmpty() || userHandle.isEmpty()
|
||||
|| privateKey.isEmpty()) {
|
||||
if (!missingKeys.isEmpty()) {
|
||||
MessageBox::information(nullptr,
|
||||
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-----")
|
||||
|| !privateKey.trimmed().endsWith("-----END PRIVATE KEY-----")) {
|
||||
MessageBox::information(
|
||||
@ -80,7 +83,12 @@ void PasskeyImporter::importSelectedFile(QFile& file, QSharedPointer<Database>&
|
||||
tr("Cannot import Passkey"),
|
||||
tr("Cannot import Passkey file \"%1\". Private key is missing or malformed.").arg(file.fileName()));
|
||||
} 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& credentialId,
|
||||
const QString& userHandle,
|
||||
const QString& privateKey)
|
||||
const QString& privateKey,
|
||||
Entry* entry)
|
||||
{
|
||||
PasskeyImportDialog passkeyImportDialog;
|
||||
passkeyImportDialog.setInfo(relyingParty, username, database);
|
||||
passkeyImportDialog.setInfo(relyingParty, username, database, entry != nullptr);
|
||||
|
||||
auto ret = passkeyImportDialog.exec();
|
||||
if (ret != QDialog::Accepted) {
|
||||
@ -105,6 +114,29 @@ void PasskeyImporter::showImportDialog(QSharedPointer<Database>& 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* group = nullptr;
|
||||
|
||||
@ -123,7 +155,7 @@ void PasskeyImporter::showImportDialog(QSharedPointer<Database>& database,
|
||||
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);
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "core/Database.h"
|
||||
#include <QFile>
|
||||
#include <QObject>
|
||||
#include <QUuid>
|
||||
|
||||
class Entry;
|
||||
|
||||
@ -31,18 +32,19 @@ class PasskeyImporter : public QObject
|
||||
public:
|
||||
explicit PasskeyImporter() = default;
|
||||
|
||||
void importPasskey(QSharedPointer<Database>& database);
|
||||
void importPasskey(QSharedPointer<Database>& database, Entry* entry = nullptr);
|
||||
|
||||
private:
|
||||
void importSelectedFile(QFile& file, QSharedPointer<Database>& database);
|
||||
void importSelectedFile(QFile& file, QSharedPointer<Database>& database, Entry* entry);
|
||||
void showImportDialog(QSharedPointer<Database>& database,
|
||||
const QString& url,
|
||||
const QString& relyingParty,
|
||||
const QString& username,
|
||||
const QString& credentialId,
|
||||
const QString& userHandle,
|
||||
const QString& privateKey);
|
||||
Group* getDefaultGroup(QSharedPointer<Database>& database);
|
||||
const QString& privateKey,
|
||||
Entry* entry);
|
||||
Group* getDefaultGroup(QSharedPointer<Database>& database) const;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_PASSKEYIMPORTER_H
|
||||
|
@ -341,8 +341,8 @@ void TestBrowser::testSearchEntriesByReference()
|
||||
auto secondEntryUuid = entries[1]->uuidToHex();
|
||||
auto fullReference = QString("{REF:A@I:%1}").arg(firstEntryUuid);
|
||||
auto partialReference = QString("https://subdomain.{REF:A@I:%1}").arg(secondEntryUuid);
|
||||
entries[2]->attributes()->set(BrowserService::ADDITIONAL_URL, fullReference);
|
||||
entries[3]->attributes()->set(BrowserService::ADDITIONAL_URL, partialReference);
|
||||
entries[2]->attributes()->set(EntryAttributes::AdditionalUrlAttribute, fullReference);
|
||||
entries[3]->attributes()->set(EntryAttributes::AdditionalUrlAttribute, partialReference);
|
||||
entries[4]->setUrl(fullReference);
|
||||
entries[5]->setUrl(partialReference);
|
||||
|
||||
@ -351,10 +351,12 @@ void TestBrowser::testSearchEntriesByReference()
|
||||
QCOMPARE(result[0]->url(), urls[0]);
|
||||
QCOMPARE(result[1]->url(), urls[1]);
|
||||
QCOMPARE(result[2]->url(), urls[2]);
|
||||
QCOMPARE(result[2]->resolveMultiplePlaceholders(result[2]->attributes()->value(BrowserService::ADDITIONAL_URL)),
|
||||
QCOMPARE(
|
||||
result[2]->resolveMultiplePlaceholders(result[2]->attributes()->value(EntryAttributes::AdditionalUrlAttribute)),
|
||||
urls[0]);
|
||||
QCOMPARE(result[3]->url(), urls[3]);
|
||||
QCOMPARE(result[3]->resolveMultiplePlaceholders(result[3]->attributes()->value(BrowserService::ADDITIONAL_URL)),
|
||||
QCOMPARE(
|
||||
result[3]->resolveMultiplePlaceholders(result[3]->attributes()->value(EntryAttributes::AdditionalUrlAttribute)),
|
||||
urls[0]);
|
||||
QCOMPARE(result[4]->url(), fullReference);
|
||||
QCOMPARE(result[4]->resolveMultiplePlaceholders(result[4]->url()), urls[0]); // Should be resolved to the main entry
|
||||
@ -386,7 +388,7 @@ void TestBrowser::testSearchEntriesWithAdditionalURLs()
|
||||
auto entries = createEntries(urls, root);
|
||||
|
||||
// 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");
|
||||
QCOMPARE(result.length(), 1);
|
||||
@ -663,7 +665,7 @@ void TestBrowser::testBestMatchingWithAdditionalURLs()
|
||||
browserSettings()->setBestMatchOnly(true);
|
||||
|
||||
// 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
|
||||
auto result = m_browserService->searchEntries(
|
||||
|
@ -19,6 +19,9 @@
|
||||
#include "browser/BrowserCbor.h"
|
||||
#include "browser/BrowserMessageBuilder.h"
|
||||
#include "browser/BrowserService.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Entry.h"
|
||||
#include "core/Group.h"
|
||||
#include "crypto/Crypto.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
@ -469,3 +472,27 @@ void TestPasskeys::testSetFlags()
|
||||
auto discouragedResult = browserPasskeys()->setFlagsFromJson(discouragedJson);
|
||||
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 testParseFlags();
|
||||
void testSetFlags();
|
||||
|
||||
void testEntry();
|
||||
};
|
||||
#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
|
||||
* 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)
|
||||
<< 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -35,6 +35,7 @@ private slots:
|
||||
void testEscapeRegex_data();
|
||||
void testConvertToRegex();
|
||||
void testConvertToRegex_data();
|
||||
void testArrayContainsValues();
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TESTTOOLS_H
|
||||
|
Loading…
Reference in New Issue
Block a user