diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index ced72485e..9c04eed22 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -36,6 +36,8 @@ #include #include +#include +#include DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) : DialogyWidget(parent) @@ -46,21 +48,30 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) m_ui->messageWidget->setHidden(true); - QFont font = m_ui->labelHeadline->font(); + QFont font; + font.setPointSize(font.pointSize() + 4); font.setBold(true); - font.setPointSize(font.pointSize() + 2); m_ui->labelHeadline->setFont(font); + m_ui->labelHeadline->setText(tr("Unlock KeePassXC Database")); + + m_ui->comboKeyFile->lineEdit()->addAction(m_ui->keyFileClearIcon, QLineEdit::TrailingPosition); m_ui->buttonTogglePassword->setIcon(filePath()->onOffIcon("actions", "password-show")); connect(m_ui->buttonTogglePassword, SIGNAL(toggled(bool)), m_ui->editPassword, SLOT(setShowPassword(bool))); connect(m_ui->buttonBrowseFile, SIGNAL(clicked()), SLOT(browseKeyFile())); - connect(m_ui->editPassword, SIGNAL(textChanged(QString)), SLOT(activatePassword())); - connect(m_ui->comboKeyFile, SIGNAL(editTextChanged(QString)), SLOT(activateKeyFile())); - connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase())); connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); + m_ui->hardwareKeyLabelHelp->setIcon(filePath()->icon("actions", "system-help").pixmap(QSize(12, 12))); + connect(m_ui->hardwareKeyLabelHelp, SIGNAL(clicked(bool)), SLOT(openHardwareKeyHelp())); + + connect(m_ui->comboKeyFile->lineEdit(), SIGNAL(textChanged(QString)), SLOT(handleKeyFileComboEdited())); + connect(m_ui->comboKeyFile, SIGNAL(currentIndexChanged(int)), SLOT(handleKeyFileComboChanged())); + m_ui->keyFileClearIcon->setIcon(filePath()->icon("actions", "edit-clear-locationbar-rtl")); + m_ui->keyFileClearIcon->setVisible(false); + connect(m_ui->keyFileClearIcon, SIGNAL(triggered(bool)), SLOT(clearKeyFileEdit())); + #ifdef WITH_XC_YUBIKEY m_ui->yubikeyProgress->setVisible(false); QSizePolicy sp = m_ui->yubikeyProgress->sizePolicy(); @@ -68,7 +79,6 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) m_ui->yubikeyProgress->setSizePolicy(sp); connect(m_ui->buttonRedetectYubikey, SIGNAL(clicked()), SLOT(pollYubikey())); - connect(m_ui->comboChallengeResponse, SIGNAL(activated(int)), SLOT(activateChallengeResponse())); #else m_ui->checkChallengeResponse->setVisible(false); m_ui->buttonRedetectYubikey->setVisible(false); @@ -80,11 +90,10 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) // add random padding to layouts to align widgets properly m_ui->dialogButtonsLayout->setContentsMargins(10, 0, 15, 0); m_ui->gridLayout->setContentsMargins(10, 0, 0, 0); - m_ui->labelLayout->setContentsMargins(10, 0, 10, 0); #endif #ifndef WITH_XC_TOUCHID - m_ui->checkTouchID->setVisible(false); + m_ui->touchIDContainer->setVisible(false); #else if (!TouchID::getInstance().isAvailable()) { m_ui->checkTouchID->setVisible(false); @@ -136,14 +145,18 @@ void DatabaseOpenWidget::hideEvent(QHideEvent* event) void DatabaseOpenWidget::load(const QString& filename) { m_filename = filename; + m_ui->fileNameLabel->setRawText(m_filename); - m_ui->labelFilename->setText(filename); + m_ui->comboKeyFile->addItem(tr("Select file..."), -1); + m_ui->comboKeyFile->setCurrentIndex(0); + m_ui->keyFileClearIcon->setVisible(false); + m_keyFileComboEdited = false; if (config()->get("RememberLastKeyFiles").toBool()) { QHash lastKeyFiles = config()->get("LastKeyFiles").toHash(); if (lastKeyFiles.contains(m_filename)) { - m_ui->checkKeyFile->setChecked(true); m_ui->comboKeyFile->addItem(lastKeyFiles[m_filename].toString()); + m_ui->comboKeyFile->setCurrentIndex(1); } } @@ -158,9 +171,6 @@ void DatabaseOpenWidget::clearForms() m_ui->editPassword->setText(""); m_ui->comboKeyFile->clear(); m_ui->comboKeyFile->setEditText(""); - m_ui->checkPassword->setChecked(false); - m_ui->checkKeyFile->setChecked(false); - m_ui->checkChallengeResponse->setChecked(false); m_ui->checkTouchID->setChecked(false); m_ui->buttonTogglePassword->setChecked(false); m_db.reset(); @@ -174,6 +184,7 @@ QSharedPointer DatabaseOpenWidget::database() void DatabaseOpenWidget::enterKey(const QString& pw, const QString& keyFile) { m_ui->editPassword->setText(pw); + m_ui->comboKeyFile->setCurrentIndex(-1); m_ui->comboKeyFile->setEditText(keyFile); openDatabase(); } @@ -194,6 +205,26 @@ void DatabaseOpenWidget::openDatabase() bool ok = m_db->open(m_filename, masterKey, &error, false); QApplication::restoreOverrideCursor(); if (!ok) { + if (m_ui->editPassword->text().isEmpty() && !m_retryUnlockWithEmptyPassword) { + QScopedPointer msgBox(new QMessageBox(this)); + msgBox->setIcon(QMessageBox::Critical); + msgBox->setWindowTitle(tr("Unlock failed and no password given")); + msgBox->setText(tr("Unlocking the database failed and you did not enter a password.\n" + "Do you want to retry with an \"empty\" password instead?\n\n" + "To prevent this error from appearing, you must go to " + "\"Database Settings / Security\" and reset your password.")); + auto btn = msgBox->addButton(tr("Retry with empty password"), QMessageBox::ButtonRole::AcceptRole); + msgBox->setDefaultButton(btn); + msgBox->addButton(QMessageBox::Cancel); + msgBox->exec(); + + if (msgBox->clickedButton() == btn) { + m_retryUnlockWithEmptyPassword = true; + openDatabase(); + return; + } + } + m_retryUnlockWithEmptyPassword = false; m_ui->messageWidget->showMessage(error, MessageWidget::MessageType::Error); return; } @@ -236,7 +267,7 @@ QSharedPointer DatabaseOpenWidget::databaseKey() { auto masterKey = QSharedPointer::create(); - if (m_ui->checkPassword->isChecked()) { + if (!m_ui->editPassword->text().isEmpty() || m_retryUnlockWithEmptyPassword) { masterKey->addKey(QSharedPointer::create(m_ui->editPassword->text())); } @@ -260,11 +291,11 @@ QSharedPointer DatabaseOpenWidget::databaseKey() #endif QHash lastKeyFiles = config()->get("LastKeyFiles").toHash(); - QHash lastChallengeResponse = config()->get("LastChallengeResponse").toHash(); + lastKeyFiles.remove(m_filename); - if (m_ui->checkKeyFile->isChecked()) { - auto key = QSharedPointer::create(); - QString keyFilename = m_ui->comboKeyFile->currentText(); + auto key = QSharedPointer::create(); + QString keyFilename = m_ui->comboKeyFile->currentText(); + if (!m_ui->comboKeyFile->currentText().isEmpty() && m_keyFileComboEdited) { QString errorMsg; if (!key->load(keyFilename, &errorMsg)) { m_ui->messageWidget->showMessage(tr("Failed to open key file: %1").arg(errorMsg), MessageWidget::Error); @@ -281,7 +312,8 @@ QSharedPointer DatabaseOpenWidget::databaseKey() legacyWarning.setDefaultButton(QMessageBox::Ok); legacyWarning.setCheckBox(new QCheckBox(tr("Don't show this warning again"))); - connect(legacyWarning.checkBox(), &QCheckBox::stateChanged, [](int state) { + connect(legacyWarning.checkBox(), &QCheckBox::stateChanged, [](int state) + { config()->set("Messages/NoLegacyKeyFileWarning", state == Qt::CheckState::Checked); }); @@ -289,14 +321,6 @@ QSharedPointer DatabaseOpenWidget::databaseKey() } masterKey->addKey(key); lastKeyFiles[m_filename] = keyFilename; - } else { - lastKeyFiles.remove(m_filename); - } - - if (m_ui->checkChallengeResponse->isChecked()) { - lastChallengeResponse[m_filename] = true; - } else { - lastChallengeResponse.remove(m_filename); } if (config()->get("RememberLastKeyFiles").toBool()) { @@ -304,19 +328,23 @@ QSharedPointer DatabaseOpenWidget::databaseKey() } #ifdef WITH_XC_YUBIKEY - if (config()->get("RememberLastKeyFiles").toBool()) { - config()->set("LastChallengeResponse", lastChallengeResponse); - } + QHash lastChallengeResponse = config()->get("LastChallengeResponse").toHash(); + lastChallengeResponse.remove(m_filename); - if (m_ui->checkChallengeResponse->isChecked()) { - int selectionIndex = m_ui->comboChallengeResponse->currentIndex(); + int selectionIndex = m_ui->comboChallengeResponse->currentIndex(); + if (selectionIndex > 0) { int comboPayload = m_ui->comboChallengeResponse->itemData(selectionIndex).toInt(); // read blocking mode from LSB and slot index number from second LSB bool blocking = comboPayload & 1; int slot = comboPayload >> 1; - auto key = QSharedPointer(new YkChallengeResponseKey(slot, blocking)); - masterKey->addChallengeResponseKey(key); + auto crKey = QSharedPointer(new YkChallengeResponseKey(slot, blocking)); + masterKey->addChallengeResponseKey(crKey); + lastChallengeResponse[m_filename] = true; + } + + if (config()->get("RememberLastKeyFiles").toBool()) { + config()->set("LastChallengeResponse", lastChallengeResponse); } #endif @@ -328,24 +356,6 @@ void DatabaseOpenWidget::reject() emit dialogFinished(false); } -void DatabaseOpenWidget::activatePassword() -{ - bool hasPassword = !m_ui->editPassword->text().isEmpty(); - m_ui->checkPassword->setChecked(hasPassword); -} - -void DatabaseOpenWidget::activateKeyFile() -{ - bool hasKeyFile = !m_ui->comboKeyFile->lineEdit()->text().isEmpty(); - m_ui->checkKeyFile->setChecked(hasKeyFile); -} - -void DatabaseOpenWidget::activateChallengeResponse() -{ - bool hasCR = m_ui->comboChallengeResponse->currentData().toInt() != -1; - m_ui->checkChallengeResponse->setChecked(hasCR); -} - void DatabaseOpenWidget::browseKeyFile() { QString filters = QString("%1 (*);;%2 (*.key)").arg(tr("All files"), tr("Key files")); @@ -355,15 +365,33 @@ void DatabaseOpenWidget::browseKeyFile() QString filename = fileDialog()->getOpenFileName(this, tr("Select key file"), QString(), filters); if (!filename.isEmpty()) { - m_ui->comboKeyFile->lineEdit()->setText(filename); + m_ui->comboKeyFile->setCurrentIndex(-1); + m_ui->comboKeyFile->setEditText(filename); } } +void DatabaseOpenWidget::clearKeyFileEdit() +{ + m_ui->comboKeyFile->setCurrentIndex(0); + // make sure that handler is called even if 0 was the current index already + handleKeyFileComboChanged(); +} + +void DatabaseOpenWidget::handleKeyFileComboEdited() +{ + m_keyFileComboEdited = true; + m_ui->keyFileClearIcon->setVisible(true); +} + +void DatabaseOpenWidget::handleKeyFileComboChanged() +{ + m_keyFileComboEdited = m_ui->comboKeyFile->currentIndex() != 0; + m_ui->keyFileClearIcon->setVisible(m_keyFileComboEdited); +} + void DatabaseOpenWidget::pollYubikey() { m_ui->buttonRedetectYubikey->setEnabled(false); - m_ui->checkChallengeResponse->setEnabled(false); - m_ui->checkChallengeResponse->setChecked(false); m_ui->comboChallengeResponse->setEnabled(false); m_ui->comboChallengeResponse->clear(); m_ui->comboChallengeResponse->addItem(tr("Select slot..."), -1); @@ -382,7 +410,6 @@ void DatabaseOpenWidget::yubikeyDetected(int slot, bool blocking) if (config()->get("RememberLastKeyFiles").toBool()) { QHash lastChallengeResponse = config()->get("LastChallengeResponse").toHash(); if (lastChallengeResponse.contains(m_filename)) { - m_ui->checkChallengeResponse->setChecked(true); m_ui->comboChallengeResponse->setCurrentIndex(1); } } @@ -391,7 +418,6 @@ void DatabaseOpenWidget::yubikeyDetected(int slot, bool blocking) void DatabaseOpenWidget::yubikeyDetectComplete() { m_ui->comboChallengeResponse->setEnabled(true); - m_ui->checkChallengeResponse->setEnabled(true); m_ui->buttonRedetectYubikey->setEnabled(true); m_ui->yubikeyProgress->setVisible(false); m_yubiKeyBeingPolled = false; @@ -403,3 +429,8 @@ void DatabaseOpenWidget::noYubikeyFound() m_ui->yubikeyProgress->setVisible(false); m_yubiKeyBeingPolled = false; } + +void DatabaseOpenWidget::openHardwareKeyHelp() +{ + QDesktopServices::openUrl(QUrl("https://keepassxc.org/docs#hwtoken")); +} \ No newline at end of file diff --git a/src/gui/DatabaseOpenWidget.h b/src/gui/DatabaseOpenWidget.h index 72f07cfa8..1ea05ca9f 100644 --- a/src/gui/DatabaseOpenWidget.h +++ b/src/gui/DatabaseOpenWidget.h @@ -60,21 +60,24 @@ protected slots: void reject(); private slots: - void activatePassword(); - void activateKeyFile(); - void activateChallengeResponse(); void browseKeyFile(); + void clearKeyFileEdit(); + void handleKeyFileComboEdited(); + void handleKeyFileComboChanged(); void yubikeyDetected(int slot, bool blocking); void yubikeyDetectComplete(); void noYubikeyFound(); + void openHardwareKeyHelp(); protected: const QScopedPointer m_ui; QSharedPointer m_db; QString m_filename; + bool m_retryUnlockWithEmptyPassword = false; private: bool m_yubiKeyBeingPolled = false; + bool m_keyFileComboEdited = false; Q_DISABLE_COPY(DatabaseOpenWidget) }; diff --git a/src/gui/DatabaseOpenWidget.ui b/src/gui/DatabaseOpenWidget.ui index 641d67da0..d3186b96a 100644 --- a/src/gui/DatabaseOpenWidget.ui +++ b/src/gui/DatabaseOpenWidget.ui @@ -6,253 +6,480 @@ 0 0 - 841 - 467 + 592 + 462 - - - 8 - + + Unlock KePassXC Database + + - - Qt::Vertical - - - - 20 - 40 - - - - - - - - 5 - - - 5 - - - - - Enter master key - - - - - - - - - - Qt::Vertical - QSizePolicy::Fixed + QSizePolicy::MinimumExpanding 20 - 20 + 5 - - - 5 + + + 0 - - 8 + + QLayout::SetDefaultConstraint - - - - Key File: - - - - - - - Password: - - - - - - - 5 - - - 5 - - - - - true - - - - 0 - 0 - - - - true - - - - - - - Browse - - - - - - - - - 5 - - - 5 - - - - - QLineEdit::Password - - - - - - - true - - - - - - - - - 5 - - - 5 - - - 0 - - - - - true - - - Refresh - - - - - - - false - - - - 0 - 0 - - - - false - - - - - - - - 16777215 - 2 - - - - 0 - - - 0 - - - -1 - - - false - - - - - - - - - false - - - Challenge Response: - - - - - - - TouchID for quick unlock - - - - - + + - Qt::Vertical + Qt::Horizontal + + + QSizePolicy::MinimumExpanding - 20 - 40 + 0 + 0 - - - - - - 5 - - - 5 - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + 700 + 16777215 + + + + QLayout::SetMinimumSize + + + + + + 12 + 75 + true + + + + Unlock KeePassXC Database + + + + + + + filename.kdbx + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 439 + 13 + + + + + + + + + 550 + 0 + + + + QFrame::StyledPanel + + + QFrame::Plain + + + 2 + + + + QLayout::SetMinimumSize + + + 20 + + + 15 + + + 20 + + + 15 + + + + + + 400 + 0 + + + + + 700 + 16777215 + + + + + + + Enter Password: + + + editPassword + + + + + + + + + Password field + + + QLineEdit::Password + + + + + + + Toggle password visibility + + + true + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + Enter Additional Credentials: + + + + + + + QLayout::SetMinimumSize + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 15 + 20 + + + + + + + + QLayout::SetMinimumSize + + + 3 + + + + + 0 + + + + + true + + + + 0 + 0 + + + + Key file selection + + + true + + + + + + + + + 0 + + + + + + 16777215 + 2 + + + + 0 + + + 0 + + + -1 + + + false + + + + + + + false + + + + 0 + 0 + + + + Hardware key slot selection + + + false + + + + + + + + + Browse for key file + + + Browse... + + + + + + + true + + + Refresh hardware tokens + + + Refresh + + + + + + + 0 + + + + + Hardware Key: + + + comboChallengeResponse + + + + + + + PointingHandCursor + + + <p>You can use a hardware security key such as a <strong>YubiKey</strong> or <strong>OnlyKey</strong> with slots configured for HMAC-SHA1.</p> + <p>Click for more information...</p> + + + Hardware key help + + + QToolButton { + border: none; + background: none; + } + + + ? + + + + 12 + 12 + + + + QToolButton::InstantPopup + + + + + + + + + Key File: + + + comboKeyFile + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 0 + + + + + + + + TouchID for Quick Unlock + + + + + + + + + + 15 + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 0 + 0 + + + + @@ -260,15 +487,26 @@ Qt::Vertical + + QSizePolicy::MinimumExpanding + 20 - 40 + 55 + + + Clear + + + Clear Key File + + @@ -282,14 +520,21 @@
gui/MessageWidget.h
1
+ + ElidedLabel + QLabel +
gui/widgets/ElidedLabel.h
+
- checkPassword editPassword buttonTogglePassword - checkKeyFile comboKeyFile buttonBrowseFile + hardwareKeyLabelHelp + comboChallengeResponse + buttonRedetectYubikey + checkTouchID diff --git a/src/gui/KeePass1OpenWidget.cpp b/src/gui/KeePass1OpenWidget.cpp index 834425ec1..6b369b9e5 100644 --- a/src/gui/KeePass1OpenWidget.cpp +++ b/src/gui/KeePass1OpenWidget.cpp @@ -29,7 +29,7 @@ KeePass1OpenWidget::KeePass1OpenWidget(QWidget* parent) : DatabaseOpenWidget(parent) { - m_ui->labelHeadline->setText(tr("Import KeePass1 database")); + m_ui->labelHeadline->setText(tr("Import KeePass1 Database")); } void KeePass1OpenWidget::openDatabase() @@ -39,11 +39,11 @@ void KeePass1OpenWidget::openDatabase() QString password; QString keyFileName; - if (m_ui->checkPassword->isChecked()) { + if (!m_ui->editPassword->text().isEmpty() || m_retryUnlockWithEmptyPassword) { password = m_ui->editPassword->text(); } - if (m_ui->checkKeyFile->isChecked()) { + if (!m_ui->comboKeyFile->currentText().isEmpty() && m_ui->comboKeyFile->currentData() != -1) { keyFileName = m_ui->comboKeyFile->currentText(); } diff --git a/src/gui/OpVaultOpenWidget.cpp b/src/gui/OpVaultOpenWidget.cpp index 37b23d528..a322d2835 100644 --- a/src/gui/OpVaultOpenWidget.cpp +++ b/src/gui/OpVaultOpenWidget.cpp @@ -28,7 +28,7 @@ OpVaultOpenWidget::OpVaultOpenWidget(QWidget* parent) : DatabaseOpenWidget(parent) { - m_ui->labelHeadline->setText("Import 1Password database"); + m_ui->labelHeadline->setText("Import 1Password Database"); } void OpVaultOpenWidget::openDatabase() @@ -36,9 +36,7 @@ void OpVaultOpenWidget::openDatabase() OpVaultReader reader; QString password; - if (m_ui->checkPassword->isChecked()) { - password = m_ui->editPassword->text(); - } + password = m_ui->editPassword->text(); QDir opVaultDir(m_filename); diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetMasterKey.cpp b/src/gui/dbsettings/DatabaseSettingsWidgetMasterKey.cpp index ed22244c7..a58ee701f 100644 --- a/src/gui/dbsettings/DatabaseSettingsWidgetMasterKey.cpp +++ b/src/gui/dbsettings/DatabaseSettingsWidgetMasterKey.cpp @@ -155,7 +155,21 @@ bool DatabaseSettingsWidgetMasterKey::save() } } - if (!addToCompositeKey(m_passwordEditWidget, newKey, oldPasswordKey)) { + if (m_passwordEditWidget->visiblePage() == KeyComponentWidget::Page::AddNew || m_passwordEditWidget->isEmpty()) { + QScopedPointer msgBox(new QMessageBox(this)); + msgBox->setIcon(QMessageBox::Warning); + msgBox->setWindowTitle(tr("No password set")); + msgBox->setText(tr("WARNING! You have not set a password. Using a database without " + "a password is strongly discouraged!\n\n" + "Are you sure you want to continue without a password?")); + auto btn = msgBox->addButton(tr("Continue without password"), QMessageBox::ButtonRole::AcceptRole); + msgBox->addButton(QMessageBox::Cancel); + msgBox->setDefaultButton(QMessageBox::Cancel); + msgBox->exec(); + if (msgBox->clickedButton() != btn) { + return false; + } + } else if (!addToCompositeKey(m_passwordEditWidget, newKey, oldPasswordKey)) { return false; } @@ -178,19 +192,6 @@ bool DatabaseSettingsWidgetMasterKey::save() return false; } - if (m_passwordEditWidget->isEmpty()) { - auto answer = MessageBox::warning(this, - tr("No password set"), - tr("WARNING! You have not set a password. Using a database without " - "a password is strongly discouraged!\n\n" - "Are you sure you want to continue without a password?"), - MessageBox::Yes | MessageBox::Cancel, - MessageBox::Cancel); - if (answer != MessageBox::Yes) { - return false; - } - } - m_db->setKey(newKey, true, false, false); emit editFinished(true); diff --git a/src/gui/masterkey/PasswordEditWidget.cpp b/src/gui/masterkey/PasswordEditWidget.cpp index de00199bb..b27248d34 100644 --- a/src/gui/masterkey/PasswordEditWidget.cpp +++ b/src/gui/masterkey/PasswordEditWidget.cpp @@ -43,8 +43,9 @@ bool PasswordEditWidget::addToCompositeKey(QSharedPointer key) QString pw = m_compUi->enterPasswordEdit->text(); if (!pw.isEmpty()) { key->addKey(QSharedPointer::create(pw)); + return true; } - return true; + return false; } /**