diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts
index 52237dd2a1..0b0239b16d 100644
--- a/share/translations/keepassxc_en.ts
+++ b/share/translations/keepassxc_en.ts
@@ -959,6 +959,15 @@ Do you want to delete the entry?
+
+
+
+
+
+
+
+
BrowserSettingsWidget
@@ -6040,6 +6049,14 @@ Do you want to overwrite it?
+
+
+
+
+
+
+
+
PasskeyImporter
diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp
index 0a2ec6eaf3..43a46e2e06 100644
--- a/src/browser/BrowserService.cpp
+++ b/src/browser/BrowserService.cpp
@@ -773,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);
@@ -1289,8 +1303,7 @@ QList BrowserService::getPasskeyEntries(const QString& rpId, const Strin
{
QList 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;
}
}
@@ -1417,14 +1430,34 @@ bool BrowserService::handleURL(const QString& entryUrl,
return false;
}
-QSharedPointer BrowserService::getDatabase()
+QSharedPointer 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> BrowserService::getOpenDatabases()
+{
+ QList> databaseList;
+ for (auto dbWidget : getMainWindow()->getOpenDatabases()) {
+ if (!dbWidget->isLocked()) {
+ databaseList << dbWidget->database();
+ }
+ }
+ return databaseList;
+}
+
QSharedPointer BrowserService::selectedDatabase()
{
QList databaseWidgets;
diff --git a/src/browser/BrowserService.h b/src/browser/BrowserService.h
index 01daaee852..137cb05da8 100644
--- a/src/browser/BrowserService.h
+++ b/src/browser/BrowserService.h
@@ -85,7 +85,9 @@ class BrowserService : public QObject
void showPasswordGenerator(const KeyPairMessage& keyPairMessage);
bool isPasswordGeneratorRequested() const;
bool isUrlIdentical(const QString& first, const QString& second) const;
+ QSharedPointer getDatabase(const QUuid& rootGroupUuid = {});
QSharedPointer selectedDatabase();
+ QList> getOpenDatabases();
#ifdef WITH_XC_BROWSER_PASSKEYS
QJsonObject
showPasskeysRegisterPrompt(const QJsonObject& publicKey, const QString& origin, const StringPairList& keyList);
@@ -192,7 +194,6 @@ private slots:
const QString& siteUrl,
const QString& formUrl,
const bool omitWwwSubdomain = false);
- QSharedPointer getDatabase();
QString getDatabaseRootUuid();
QString getDatabaseRecycleBinUuid();
bool checkLegacySettings(QSharedPointer db);
diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp
index 7e238f0494..1bfa3cb36e 100644
--- a/src/core/Entry.cpp
+++ b/src/core/Entry.cpp
@@ -545,6 +545,11 @@ bool Entry::hasTotp() const
return !m_data.totpSettings.isNull();
}
+bool Entry::hasPasskey() const
+{
+ return m_attributes->hasPasskey();
+}
+
QString Entry::totp() const
{
if (hasTotp()) {
diff --git a/src/core/Entry.h b/src/core/Entry.h
index 88f878e98a..3a03892d0a 100644
--- a/src/core/Entry.h
+++ b/src/core/Entry.h
@@ -121,6 +121,7 @@ class Entry : public ModifiableObject
void setExcludeFromReports(bool state);
bool hasTotp() const;
+ bool hasPasskey() const;
bool isExpired() const;
bool willExpireInDays(int days) const;
bool isRecycled() const;
diff --git a/src/core/EntryAttributes.cpp b/src/core/EntryAttributes.cpp
index 13207e1688..4693323988 100644
--- a/src/core/EntryAttributes.cpp
+++ b/src/core/EntryAttributes.cpp
@@ -52,6 +52,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 EntryAttributes::customKeys() const
{
QList customKeys;
diff --git a/src/core/EntryAttributes.h b/src/core/EntryAttributes.h
index 2e7f8c05c0..0b029ec965 100644
--- a/src/core/EntryAttributes.h
+++ b/src/core/EntryAttributes.h
@@ -1,6 +1,6 @@
/*
+ * Copyright (C) 2023 KeePassXC Team
* Copyright (C) 2012 Felix Geyer
- * Copyright (C) 2017 KeePassXC Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,6 +33,7 @@ class EntryAttributes : public ModifiableObject
explicit EntryAttributes(QObject* parent = nullptr);
QList keys() const;
bool hasKey(const QString& key) const;
+ bool hasPasskey() const;
QList customKeys() const;
QString value(const QString& key) const;
QList values(const QList& keys) const;
diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp
index 4454266339..73a61fac39 100644
--- a/src/gui/DatabaseTabWidget.cpp
+++ b/src/gui/DatabaseTabWidget.cpp
@@ -564,7 +564,12 @@ void DatabaseTabWidget::showPasskeys()
void DatabaseTabWidget::importPasskey()
{
- currentDatabaseWidget()->switchToImportPasskey();
+ currentDatabaseWidget()->showImportPasskeyDialog();
+}
+
+void DatabaseTabWidget::importPasskeyToEntry()
+{
+ currentDatabaseWidget()->showImportPasskeyDialog(true);
}
#endif
diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h
index 6b4b121af7..8f27038786 100644
--- a/src/gui/DatabaseTabWidget.h
+++ b/src/gui/DatabaseTabWidget.h
@@ -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();
diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp
index d42292d348..b20f5a240b 100644
--- a/src/gui/DatabaseWidget.cpp
+++ b/src/gui/DatabaseWidget.cpp
@@ -1407,10 +1407,20 @@ void DatabaseWidget::switchToPasskeys()
m_reportsDialog->activatePasskeysPage();
}
-void DatabaseWidget::switchToImportPasskey()
+void DatabaseWidget::showImportPasskeyDialog(bool isEntry)
{
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
diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h
index 4f79ebd2f8..1102245bde 100644
--- a/src/gui/DatabaseWidget.h
+++ b/src/gui/DatabaseWidget.h
@@ -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);
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
index 27dada98d6..a41f631e4a 100644
--- a/src/gui/MainWindow.cpp
+++ b/src/gui/MainWindow.cpp
@@ -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);
diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui
index e6fbfc22ca..a0b8d6e687 100644
--- a/src/gui/MainWindow.ui
+++ b/src/gui/MainWindow.ui
@@ -342,6 +342,8 @@
+
+
@@ -730,6 +732,14 @@
Perform &Auto-Type
+
+
+ false
+
+
+ Import Passkey
+
+
false
diff --git a/src/gui/passkeys/PasskeyImportDialog.cpp b/src/gui/passkeys/PasskeyImportDialog.cpp
index 2d54ba5bad..c7237711a7 100644
--- a/src/gui/passkeys/PasskeyImportDialog.cpp
+++ b/src/gui/passkeys/PasskeyImportDialog.cpp
@@ -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)
+void PasskeyImportDialog::setInfo(const QString& url,
+ const QString& username,
+ const QSharedPointer& 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,61 +69,106 @@ void PasskeyImportDialog::setInfo(const QString& url, const QString& username, c
openDatabaseCount++;
}
}
- m_ui->selectDatabaseButton->setEnabled(openDatabaseCount > 1);
+ m_ui->selectDatabaseCombobBox->setEnabled(openDatabaseCount > 1);
}
-QSharedPointer PasskeyImportDialog::getSelectedDatabase()
+QSharedPointer 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) const
+bool PasskeyImportDialog::createNewEntry() const
{
- return QFileInfo(database->filePath()).fileName();
+ return m_selectedEntryUuid.isNull();
}
-void PasskeyImportDialog::addGroups(const QSharedPointer& database)
+void PasskeyImportDialog::addDatabases()
{
- m_ui->selectGroupComboBox->clear();
- for (const auto& group : database->rootGroup()->groupsRecursive(true)) {
- if (!group || group->isRecycled() || group == database->metadata()->recycleBin()) {
+ 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;
+ }
+
+ for (const auto& entry : group->entries()) {
+ if (!entry || entry->isRecycled()) {
continue;
}
- m_ui->selectGroupComboBox->addItem(group->fullPath(), group->uuid());
+ m_ui->selectEntryComboBox->addItem(entry->title(), entry->uuid());
}
}
-void PasskeyImportDialog::selectDatabase()
+void PasskeyImportDialog::addGroups()
{
- auto selectedDatabase = browserService()->selectedDatabase();
- if (!selectedDatabase) {
+ if (!m_selectedDatabase) {
return;
}
- m_selectedDatabase = selectedDatabase;
- m_ui->selectDatabaseLabel->setText(QString("Database: %1").arg(getDatabaseName(m_selectedDatabase)));
+ m_ui->selectGroupComboBox->clear();
+ 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;
+ }
+
+ m_ui->selectGroupComboBox->addItem(group->fullPath(), group->uuid());
+ }
+}
- addGroups(m_selectedDatabase);
+void PasskeyImportDialog::changeDatabase(int index)
+{
+ m_selectedDatabaseUuid = m_ui->selectDatabaseCombobBox->itemData(index).value();
+ m_selectedDatabase = browserService()->getDatabase(m_selectedDatabaseUuid);
+ emit updateGroups();
}
-void PasskeyImportDialog::changeGroup(int index)
+void PasskeyImportDialog::changeEntry(int index)
{
- m_selectedGroupUuid = m_ui->selectGroupComboBox->itemData(index).value();
+ m_selectedEntryUuid = m_ui->selectEntryComboBox->itemData(index).value();
}
-void PasskeyImportDialog::useDefaultGroupChanged()
+void PasskeyImportDialog::changeGroup(int index)
{
- m_ui->selectGroupComboBox->setEnabled(!m_ui->useDefaultGroupCheckbox->isChecked());
- m_useDefaultGroup = m_ui->useDefaultGroupCheckbox->isChecked();
+ m_selectedGroupUuid = m_ui->selectGroupComboBox->itemData(index).value();
+ emit updateEntries();
}
diff --git a/src/gui/passkeys/PasskeyImportDialog.h b/src/gui/passkeys/PasskeyImportDialog.h
index 7b316721e7..705c6d187f 100644
--- a/src/gui/passkeys/PasskeyImportDialog.h
+++ b/src/gui/passkeys/PasskeyImportDialog.h
@@ -36,25 +36,33 @@ class PasskeyImportDialog : public QDialog
explicit PasskeyImportDialog(QWidget* parent = nullptr);
~PasskeyImportDialog() override;
- void setInfo(const QString& url, const QString& username, const QSharedPointer& database);
- QSharedPointer getSelectedDatabase();
- QUuid getSelectedGroupUuid();
- bool useDefaultGroup();
+ void setInfo(const QString& url, const QString& username, const QSharedPointer& database, bool isEntry);
+ QSharedPointer getSelectedDatabase() const;
+ QUuid getSelectedEntryUuid() const;
+ QUuid getSelectedGroupUuid() const;
+ bool useDefaultGroup() const;
+ bool createNewEntry() const;
private:
- QString getDatabaseName(const QSharedPointer& database) const;
- void addGroups(const QSharedPointer& 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 m_ui;
QSharedPointer m_selectedDatabase;
+ QUuid m_selectedDatabaseUuid;
+ QUuid m_selectedEntryUuid;
QUuid m_selectedGroupUuid;
- bool m_useDefaultGroup;
};
#endif // KEEPASSXC_PASSKEYIMPORTDIALOG_H
diff --git a/src/gui/passkeys/PasskeyImportDialog.ui b/src/gui/passkeys/PasskeyImportDialog.ui
index ffc80d1419..ec8de7e062 100755
--- a/src/gui/passkeys/PasskeyImportDialog.ui
+++ b/src/gui/passkeys/PasskeyImportDialog.ui
@@ -6,10 +6,22 @@
0
0
- 405
- 227
+ 500
+ 300
+
+
+ 0
+ 0
+
+
+
+
+ 400
+ 300
+
+
KeePassXC - Passkey Import
@@ -24,7 +36,7 @@
- Do you want to import the Passkey?
+ Import the following Passkey:
Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
@@ -52,80 +64,62 @@
-
-
-
- Use default group (Imported Passkeys)
+
+
+ Qt::Vertical
-
- false
+
+
+ 20
+ 10
+
-
-
- -
-
-
-
-
-
- Group
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
-
+
-
-
-
-
-
-
- Database
-
-
- false
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- Select Database
-
-
-
-
+
+
+ -
+
+
-
+
+
+ Database
+
+
+
+ -
+
+
+ -
+
+
+ Group
+
+
+
+ -
+
+
+ -
+
+
+ Entry
+
+
+
+ -
+
+
+
+
+
+
-
-
+
-
diff --git a/src/gui/passkeys/PasskeyImporter.cpp b/src/gui/passkeys/PasskeyImporter.cpp
index 103b1df4e7..a8bbe12325 100644
--- a/src/gui/passkeys/PasskeyImporter.cpp
+++ b/src/gui/passkeys/PasskeyImporter.cpp
@@ -29,7 +29,7 @@
static const QString IMPORTED_PASSKEYS_GROUP = QStringLiteral("Imported Passkeys");
-void PasskeyImporter::importPasskey(QSharedPointer& database)
+void PasskeyImporter::importPasskey(QSharedPointer& database, Entry* entry)
{
auto filter = QString("%1 (*.passkey);;%2 (*)").arg(tr("Passkey file"), tr("All files"));
auto fileName =
@@ -47,10 +47,10 @@ void PasskeyImporter::importPasskey(QSharedPointer& database)
return;
}
- importSelectedFile(file, database);
+ importSelectedFile(file, database, entry);
}
-void PasskeyImporter::importSelectedFile(QFile& file, QSharedPointer& database)
+void PasskeyImporter::importSelectedFile(QFile& file, QSharedPointer& database, Entry* entry)
{
const auto fileData = file.readAll();
const auto passkeyObject = browserMessageBuilder()->getJsonObject(fileData);
@@ -80,7 +80,7 @@ void PasskeyImporter::importSelectedFile(QFile& file, QSharedPointer&
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, password, userHandle, privateKey);
+ showImportDialog(database, url, relyingParty, username, password, userHandle, privateKey, entry);
}
}
@@ -90,10 +90,11 @@ void PasskeyImporter::showImportDialog(QSharedPointer& database,
const QString& username,
const QString& userId,
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 +106,29 @@ void PasskeyImporter::showImportDialog(QSharedPointer& database,
db = database;
}
+ // Store to entry if given directly
+ if (entry) {
+ browserService()->addPasskeyToEntry(
+ entry, relyingParty, relyingParty, username, userId, 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, userId, userHandle, privateKey);
+ }
+ }
+
+ return;
+ }
+
// Group settings. Use default group "Imported Passkeys" if user did not select a specific one.
Group* group = nullptr;
@@ -123,7 +147,7 @@ void PasskeyImporter::showImportDialog(QSharedPointer& database,
group, url, relyingParty, relyingParty, username, userId, userHandle, privateKey);
}
-Group* PasskeyImporter::getDefaultGroup(QSharedPointer& database)
+Group* PasskeyImporter::getDefaultGroup(QSharedPointer& database) const
{
auto defaultGroup = database->rootGroup()->findGroupByPath(IMPORTED_PASSKEYS_GROUP);
diff --git a/src/gui/passkeys/PasskeyImporter.h b/src/gui/passkeys/PasskeyImporter.h
index c1523cbc18..5babee226f 100644
--- a/src/gui/passkeys/PasskeyImporter.h
+++ b/src/gui/passkeys/PasskeyImporter.h
@@ -21,6 +21,7 @@
#include "core/Database.h"
#include
#include
+#include
class Entry;
@@ -31,18 +32,19 @@ class PasskeyImporter : public QObject
public:
explicit PasskeyImporter() = default;
- void importPasskey(QSharedPointer& database);
+ void importPasskey(QSharedPointer& database, Entry* entry = nullptr);
private:
- void importSelectedFile(QFile& file, QSharedPointer& database);
+ void importSelectedFile(QFile& file, QSharedPointer& database, Entry* entry);
void showImportDialog(QSharedPointer& database,
const QString& url,
const QString& relyingParty,
const QString& username,
const QString& userId,
const QString& userHandle,
- const QString& privateKey);
- Group* getDefaultGroup(QSharedPointer& database);
+ const QString& privateKey,
+ Entry* entry);
+ Group* getDefaultGroup(QSharedPointer& database) const;
};
#endif // KEEPASSXC_PASSKEYIMPORTER_H
diff --git a/tests/TestPasskeys.cpp b/tests/TestPasskeys.cpp
index 556e287d70..4e5db2803b 100644
--- a/tests/TestPasskeys.cpp
+++ b/tests/TestPasskeys.cpp
@@ -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
@@ -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());
+}
diff --git a/tests/TestPasskeys.h b/tests/TestPasskeys.h
index ef2b68c24c..3d702e84a5 100644
--- a/tests/TestPasskeys.h
+++ b/tests/TestPasskeys.h
@@ -43,5 +43,7 @@ private slots:
void testExtensions();
void testParseFlags();
void testSetFlags();
+
+ void testEntry();
};
#endif // KEEPASSXC_TESTPASSKEYS_H