Skip to content

Commit

Permalink
Redesign database unlock widget.
Browse files Browse the repository at this point in the history
With this change we get rid of the confusing key component checkboxes.
Now a component is either there or not (if left empty). There is
no redundant distinction between "unset" and "emtpy" anymore.
For compatibility with older databases that have "empty" passwords,
KeePassXC will ask if the user wants to retry with an empty password
if unlocking failed and the password field was left blank.

Besides these functional changes, the widget's layout has been
rearranged to be more compact and less stretched out (e.g. input fields
do not fill the full window width anymore).
  • Loading branch information
phoerious committed Jun 18, 2019
1 parent 1e915ee commit 784f573
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 291 deletions.
103 changes: 44 additions & 59 deletions src/gui/DatabaseOpenWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,12 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)

m_ui->messageWidget->setHidden(true);

QFont font = m_ui->labelHeadline->font();
font.setBold(true);
font.setPointSize(font.pointSize() + 2);
m_ui->labelHeadline->setFont(font);
m_ui->labelHeadline->setText(tr("Unlock KeePassXC Database"));

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()));

Expand All @@ -68,7 +62,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);
Expand All @@ -80,7 +73,6 @@ 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
Expand Down Expand Up @@ -137,13 +129,12 @@ void DatabaseOpenWidget::load(const QString& filename)
{
m_filename = filename;

m_ui->labelFilename->setText(filename);

m_ui->comboKeyFile->addItem(tr("None"), -1);
if (config()->get("RememberLastKeyFiles").toBool()) {
QHash<QString, QVariant> 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);
}
}

Expand All @@ -158,9 +149,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();
Expand All @@ -174,6 +162,7 @@ QSharedPointer<Database> 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();
}
Expand All @@ -194,6 +183,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<QMessageBox> 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"
"If your database really has no password and this keeps happening,\n"
"go to \"Database Settings / Security\" and reset your password there."));
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;
}
Expand Down Expand Up @@ -236,7 +245,7 @@ QSharedPointer<CompositeKey> DatabaseOpenWidget::databaseKey()
{
auto masterKey = QSharedPointer<CompositeKey>::create();

if (m_ui->checkPassword->isChecked()) {
if (!m_ui->editPassword->text().isEmpty() || m_retryUnlockWithEmptyPassword) {
masterKey->addKey(QSharedPointer<PasswordKey>::create(m_ui->editPassword->text()));
}

Expand All @@ -260,11 +269,11 @@ QSharedPointer<CompositeKey> DatabaseOpenWidget::databaseKey()
#endif

QHash<QString, QVariant> lastKeyFiles = config()->get("LastKeyFiles").toHash();
QHash<QString, QVariant> lastChallengeResponse = config()->get("LastChallengeResponse").toHash();
lastKeyFiles.remove(m_filename);

if (m_ui->checkKeyFile->isChecked()) {
auto key = QSharedPointer<FileKey>::create();
QString keyFilename = m_ui->comboKeyFile->currentText();
auto key = QSharedPointer<FileKey>::create();
QString keyFilename = m_ui->comboKeyFile->currentText();
if (!m_ui->comboKeyFile->currentText().isEmpty() && m_ui->comboKeyFile->currentData() != -1) {
QString errorMsg;
if (!key->load(keyFilename, &errorMsg)) {
m_ui->messageWidget->showMessage(tr("Failed to open key file: %1").arg(errorMsg), MessageWidget::Error);
Expand All @@ -281,42 +290,39 @@ QSharedPointer<CompositeKey> 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);
});

legacyWarning.exec();
}
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()) {
config()->set("LastKeyFiles", lastKeyFiles);
}

#ifdef WITH_XC_YUBIKEY
if (config()->get("RememberLastKeyFiles").toBool()) {
config()->set("LastChallengeResponse", lastChallengeResponse);
}
QHash<QString, QVariant> 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<YkChallengeResponseKey>(new YkChallengeResponseKey(slot, blocking));
masterKey->addChallengeResponseKey(key);
auto crKey = QSharedPointer<YkChallengeResponseKey>(new YkChallengeResponseKey(slot, blocking));
masterKey->addChallengeResponseKey(crKey);
lastChallengeResponse[m_filename] = true;
}

if (config()->get("RememberLastKeyFiles").toBool()) {
config()->set("LastChallengeResponse", lastChallengeResponse);
}
#endif

Expand All @@ -328,24 +334,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"));
Expand All @@ -355,15 +343,14 @@ 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::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);
Expand All @@ -382,7 +369,6 @@ void DatabaseOpenWidget::yubikeyDetected(int slot, bool blocking)
if (config()->get("RememberLastKeyFiles").toBool()) {
QHash<QString, QVariant> lastChallengeResponse = config()->get("LastChallengeResponse").toHash();
if (lastChallengeResponse.contains(m_filename)) {
m_ui->checkChallengeResponse->setChecked(true);
m_ui->comboChallengeResponse->setCurrentIndex(1);
}
}
Expand All @@ -391,7 +377,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;
Expand Down
4 changes: 1 addition & 3 deletions src/gui/DatabaseOpenWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,6 @@ protected slots:
void reject();

private slots:
void activatePassword();
void activateKeyFile();
void activateChallengeResponse();
void browseKeyFile();
void yubikeyDetected(int slot, bool blocking);
void yubikeyDetectComplete();
Expand All @@ -72,6 +69,7 @@ private slots:
const QScopedPointer<Ui::DatabaseOpenWidget> m_ui;
QSharedPointer<Database> m_db;
QString m_filename;
bool m_retryUnlockWithEmptyPassword = false;

private:
bool m_yubiKeyBeingPolled = false;
Expand Down
Loading

0 comments on commit 784f573

Please sign in to comment.