Skip to content

Commit

Permalink
Add support for importing Passkey to entry
Browse files Browse the repository at this point in the history
  • Loading branch information
varjolintu committed Nov 4, 2023
1 parent 454dc71 commit 8c3fab3
Show file tree
Hide file tree
Showing 17 changed files with 318 additions and 97 deletions.
17 changes: 17 additions & 0 deletions share/translations/keepassxc_en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -6040,6 +6049,14 @@ Do you want to overwrite it?
<source>Group:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Import to entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Do you want to import the Passkey to this entry?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>PasskeyImporter</name>
Expand Down
16 changes: 14 additions & 2 deletions src/browser/BrowserService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,19 @@ 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->username()),
MessageBox::Overwrite | MessageBox::Cancel,
MessageBox::Cancel)
!= MessageBox::Overwrite) {
return;
}
}

entry->beginUpdate();

entry->attributes()->set(BrowserPasskeys::KPEX_PASSKEY_USERNAME, username);
Expand Down Expand Up @@ -1289,8 +1302,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;
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/core/Entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
1 change: 1 addition & 0 deletions src/core/Entry.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions src/core/EntryAttributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<QString> EntryAttributes::customKeys() const
{
QList<QString> customKeys;
Expand Down
3 changes: 2 additions & 1 deletion src/core/EntryAttributes.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2023 KeePassXC Team <[email protected]>
* Copyright (C) 2012 Felix Geyer <[email protected]>
* Copyright (C) 2017 KeePassXC Team <[email protected]>
*
* 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
Expand Down Expand Up @@ -33,6 +33,7 @@ class EntryAttributes : public ModifiableObject
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;
Expand Down
5 changes: 5 additions & 0 deletions src/gui/DatabaseTabWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,11 @@ void DatabaseTabWidget::importPasskey()
{
currentDatabaseWidget()->switchToImportPasskey();
}

void DatabaseTabWidget::importPasskeyToEntry()
{
currentDatabaseWidget()->switchToImportPasskey(true);
}
#endif

bool DatabaseTabWidget::isModified(int index) const
Expand Down
1 change: 1 addition & 0 deletions src/gui/DatabaseTabWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
14 changes: 12 additions & 2 deletions src/gui/DatabaseWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1407,10 +1407,20 @@ void DatabaseWidget::switchToPasskeys()
m_reportsDialog->activatePasskeysPage();
}

void DatabaseWidget::switchToImportPasskey()
void DatabaseWidget::switchToImportPasskey(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

Expand Down
2 changes: 1 addition & 1 deletion src/gui/DatabaseWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ public slots:
void switchToDatabaseSettings();
#ifdef WITH_XC_BROWSER_PASSKEYS
void switchToPasskeys();
void switchToImportPasskey();
void switchToImportPasskey(bool isEntry = false);
#endif
void switchToOpenDatabase();
void switchToOpenDatabase(const QString& filePath);
Expand Down
9 changes: 9 additions & 0 deletions src/gui/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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()));
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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);
Expand Down
10 changes: 10 additions & 0 deletions src/gui/MainWindow.ui
Original file line number Diff line number Diff line change
Expand Up @@ -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"/>
Expand Down Expand Up @@ -730,6 +732,14 @@
<string>Perform &amp;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>
Expand Down
87 changes: 79 additions & 8 deletions src/gui/passkeys/PasskeyImportDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,42 @@ PasskeyImportDialog::PasskeyImportDialog(QWidget* parent)
m_ui->setupUi(this);
m_ui->useDefaultGroupCheckbox->setChecked(true);
m_ui->selectGroupComboBox->setEnabled(false);
m_ui->importToEntryCheckbox->setChecked(false);
m_ui->selectEntryComboBox->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->selectGroupComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changeGroup(int)));
connect(m_ui->selectEntryComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changeEntry(int)));
connect(m_ui->useDefaultGroupCheckbox, SIGNAL(stateChanged(int)), SLOT(useDefaultGroupChanged()));
connect(m_ui->importToEntryCheckbox, SIGNAL(stateChanged(int)), SLOT(importToEntryCheckboxChanged()));
}

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("Do you want to import the Passkey to this entry?"));
m_ui->groupBox->setVisible(false);
}

m_selectedDatabase = database;
addGroups();

auto openDatabaseCount = 0;
for (auto dbWidget : getMainWindow()->getOpenDatabases()) {
Expand All @@ -69,33 +85,68 @@ QSharedPointer<Database> PasskeyImportDialog::getSelectedDatabase()
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;
}

bool PasskeyImportDialog::isImportedToEntry() const
{
return m_ui->importToEntryCheckbox->isChecked();
}

QString PasskeyImportDialog::getDatabaseName(const QSharedPointer<Database>& database) const
{
return QFileInfo(database->filePath()).fileName();
}

void PasskeyImportDialog::addGroups(const QSharedPointer<Database>& database)
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()) {
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());
}
}

void PasskeyImportDialog::addEntries()
{
if (!m_selectedDatabase || !m_selectedDatabase->rootGroup()) {
return;
}

const auto group = m_selectedDatabase->rootGroup()->findGroupByUuid(m_selectedGroupUuid);
if (!group) {
return;
}

m_ui->selectEntryComboBox->clear();
for (const auto& entry : group->entries()) {
if (!entry || entry->isRecycled()) {
continue;
}

m_ui->selectEntryComboBox->addItem(entry->title(), entry->uuid());
}
}

void PasskeyImportDialog::selectDatabase()
{
auto selectedDatabase = browserService()->selectedDatabase();
Expand All @@ -106,16 +157,36 @@ void PasskeyImportDialog::selectDatabase()
m_selectedDatabase = selectedDatabase;
m_ui->selectDatabaseLabel->setText(QString("Database: %1").arg(getDatabaseName(m_selectedDatabase)));

addGroups(m_selectedDatabase);
m_selectedGroupUuid = m_selectedDatabase->rootGroup()->uuid();
emit updateGroups();
}

void PasskeyImportDialog::changeGroup(int index)
{
m_selectedGroupUuid = m_ui->selectGroupComboBox->itemData(index).value<QUuid>();
emit updateEntries();
}

void PasskeyImportDialog::changeEntry(int index)
{
m_selectedEntryUuid = m_ui->selectEntryComboBox->itemData(index).value<QUuid>();

// Disable Import button if selected group has no entries
if (m_ui->importToEntryCheckbox->isChecked()) {
m_ui->importButton->setEnabled(!m_selectedEntryUuid.isNull());
}
}

void PasskeyImportDialog::useDefaultGroupChanged()
{
m_ui->selectGroupComboBox->setEnabled(!m_ui->useDefaultGroupCheckbox->isChecked());
m_useDefaultGroup = m_ui->useDefaultGroupCheckbox->isChecked();
}

void PasskeyImportDialog::importToEntryCheckboxChanged()
{
auto isChecked = m_ui->importToEntryCheckbox->isChecked();
m_ui->selectEntryComboBox->setEnabled(isChecked);
m_ui->useDefaultGroupCheckbox->setEnabled(!isChecked);
m_ui->selectGroupComboBox->setEnabled(isChecked || !m_ui->useDefaultGroupCheckbox->isChecked());
}
Loading

0 comments on commit 8c3fab3

Please sign in to comment.