mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-01-27 14:57:09 -05:00
Improve overall database settings behavior
* Fixes #10723 - only display password strength warning when actively editing the password * Also improve behavior of minimum quality warning * Improve behavior and handling of password changes with the database settings dialog * Prevents loss of newly entered password when toggling between elements in the settings page * On error, switch to tab that prevents saving database settings for easier correction
This commit is contained in:
parent
f83cd81ad7
commit
ed3f7f5a16
@ -1903,11 +1903,11 @@ Are you sure you want to continue without a password?</source>
|
|||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>You must enter a stronger password to protect your database.</source>
|
<source>This is a weak password! For better protection of your secrets, you should choose a stronger password.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>This is a weak password! For better protection of your secrets, you should choose a stronger password.</source>
|
<source>The provided password does not meet the minimum quality requirement.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
|
@ -75,6 +75,10 @@ KeyComponentWidget::Page KeyComponentWidget::visiblePage() const
|
|||||||
|
|
||||||
void KeyComponentWidget::updateAddStatus(bool added)
|
void KeyComponentWidget::updateAddStatus(bool added)
|
||||||
{
|
{
|
||||||
|
if (m_ui->stackedWidget->currentIndex() == Page::Edit) {
|
||||||
|
emit editCanceled();
|
||||||
|
}
|
||||||
|
|
||||||
if (added) {
|
if (added) {
|
||||||
m_ui->stackedWidget->setCurrentIndex(Page::LeaveOrRemove);
|
m_ui->stackedWidget->setCurrentIndex(Page::LeaveOrRemove);
|
||||||
} else {
|
} else {
|
||||||
@ -103,12 +107,6 @@ void KeyComponentWidget::cancelEdit()
|
|||||||
emit editCanceled();
|
emit editCanceled();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeyComponentWidget::showEvent(QShowEvent* event)
|
|
||||||
{
|
|
||||||
resetComponentEditWidget();
|
|
||||||
QWidget::showEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void KeyComponentWidget::resetComponentEditWidget()
|
void KeyComponentWidget::resetComponentEditWidget()
|
||||||
{
|
{
|
||||||
if (!m_componentWidget || static_cast<Page>(m_ui->stackedWidget->currentIndex()) == Page::Edit) {
|
if (!m_componentWidget || static_cast<Page>(m_ui->stackedWidget->currentIndex()) == Page::Edit) {
|
||||||
|
@ -104,9 +104,6 @@ signals:
|
|||||||
void editCanceled();
|
void editCanceled();
|
||||||
void componentRemovalRequested();
|
void componentRemovalRequested();
|
||||||
|
|
||||||
protected:
|
|
||||||
void showEvent(QShowEvent* event) override;
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void updateAddStatus(bool added);
|
void updateAddStatus(bool added);
|
||||||
void doAdd();
|
void doAdd();
|
||||||
|
@ -27,6 +27,9 @@ PasswordEditWidget::PasswordEditWidget(QWidget* parent)
|
|||||||
, m_compUi(new Ui::PasswordEditWidget())
|
, m_compUi(new Ui::PasswordEditWidget())
|
||||||
{
|
{
|
||||||
initComponent();
|
initComponent();
|
||||||
|
|
||||||
|
// Explicitly clear password on cancel
|
||||||
|
connect(this, &PasswordEditWidget::editCanceled, this, [this] { setPassword({}); });
|
||||||
}
|
}
|
||||||
|
|
||||||
PasswordEditWidget::~PasswordEditWidget()
|
PasswordEditWidget::~PasswordEditWidget()
|
||||||
@ -61,7 +64,7 @@ bool PasswordEditWidget::isPasswordVisible() const
|
|||||||
|
|
||||||
bool PasswordEditWidget::isEmpty() const
|
bool PasswordEditWidget::isEmpty() const
|
||||||
{
|
{
|
||||||
return (visiblePage() == Page::Edit) && m_compUi->enterPasswordEdit->text().isEmpty();
|
return visiblePage() != Page::Edit || m_compUi->enterPasswordEdit->text().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
PasswordHealth::Quality PasswordEditWidget::getPasswordQuality() const
|
PasswordHealth::Quality PasswordEditWidget::getPasswordQuality() const
|
||||||
@ -85,8 +88,7 @@ void PasswordEditWidget::initComponentEditWidget(QWidget* widget)
|
|||||||
{
|
{
|
||||||
Q_UNUSED(widget);
|
Q_UNUSED(widget);
|
||||||
Q_ASSERT(m_compEditWidget);
|
Q_ASSERT(m_compEditWidget);
|
||||||
m_compUi->enterPasswordEdit->setFocus();
|
setFocusProxy(m_compUi->enterPasswordEdit);
|
||||||
|
|
||||||
m_compUi->enterPasswordEdit->setQualityVisible(true);
|
m_compUi->enterPasswordEdit->setQualityVisible(true);
|
||||||
m_compUi->repeatPasswordEdit->setQualityVisible(false);
|
m_compUi->repeatPasswordEdit->setQualityVisible(false);
|
||||||
}
|
}
|
||||||
@ -105,16 +107,6 @@ void PasswordEditWidget::initComponent()
|
|||||||
"<p>Good passwords are long and unique. KeePassXC can generate one for you.</p>"));
|
"<p>Good passwords are long and unique. KeePassXC can generate one for you.</p>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PasswordEditWidget::hideEvent(QHideEvent* event)
|
|
||||||
{
|
|
||||||
if (!isVisible() && m_compUi->enterPasswordEdit) {
|
|
||||||
m_compUi->enterPasswordEdit->setText("");
|
|
||||||
m_compUi->repeatPasswordEdit->setText("");
|
|
||||||
}
|
|
||||||
|
|
||||||
QWidget::hideEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PasswordEditWidget::validate(QString& errorMessage) const
|
bool PasswordEditWidget::validate(QString& errorMessage) const
|
||||||
{
|
{
|
||||||
if (m_compUi->enterPasswordEdit->text() != m_compUi->repeatPasswordEdit->text()) {
|
if (m_compUi->enterPasswordEdit->text() != m_compUi->repeatPasswordEdit->text()) {
|
||||||
|
@ -47,7 +47,6 @@ protected:
|
|||||||
QWidget* componentEditWidget() override;
|
QWidget* componentEditWidget() override;
|
||||||
void initComponentEditWidget(QWidget* widget) override;
|
void initComponentEditWidget(QWidget* widget) override;
|
||||||
void initComponent() override;
|
void initComponent() override;
|
||||||
void hideEvent(QHideEvent* event) override;
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void setPassword(const QString& password);
|
void setPassword(const QString& password);
|
||||||
|
@ -91,33 +91,29 @@ DatabaseSettingsDialog::DatabaseSettingsDialog(QWidget* parent)
|
|||||||
scrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents);
|
scrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents);
|
||||||
scrollArea->setWidgetResizable(true);
|
scrollArea->setWidgetResizable(true);
|
||||||
scrollArea->setWidget(m_databaseKeyWidget);
|
scrollArea->setWidget(m_databaseKeyWidget);
|
||||||
|
|
||||||
m_securityTabWidget->addTab(scrollArea, tr("Database Credentials"));
|
m_securityTabWidget->addTab(scrollArea, tr("Database Credentials"));
|
||||||
|
|
||||||
m_securityTabWidget->addTab(m_encryptionWidget, tr("Encryption Settings"));
|
m_securityTabWidget->addTab(m_encryptionWidget, tr("Encryption Settings"));
|
||||||
|
|
||||||
#if defined(WITH_XC_KEESHARE)
|
|
||||||
addSettingsPage(new DatabaseSettingsPageKeeShare());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(WITH_XC_FDOSECRETS)
|
|
||||||
addSettingsPage(new DatabaseSettingsPageFdoSecrets());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_ui->stackedWidget->setCurrentIndex(0);
|
|
||||||
m_securityTabWidget->setCurrentIndex(0);
|
m_securityTabWidget->setCurrentIndex(0);
|
||||||
|
|
||||||
connect(m_securityTabWidget, SIGNAL(currentChanged(int)), SLOT(pageChanged()));
|
|
||||||
connect(m_ui->categoryList, SIGNAL(categoryChanged(int)), m_ui->stackedWidget, SLOT(setCurrentIndex(int)));
|
|
||||||
|
|
||||||
#ifdef WITH_XC_BROWSER
|
#ifdef WITH_XC_BROWSER
|
||||||
m_ui->categoryList->addCategory(tr("Browser Integration"), icons()->icon("internet-web-browser"));
|
m_ui->categoryList->addCategory(tr("Browser Integration"), icons()->icon("internet-web-browser"));
|
||||||
m_ui->stackedWidget->addWidget(m_browserWidget);
|
m_ui->stackedWidget->addWidget(m_browserWidget);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef WITH_XC_KEESHARE
|
||||||
|
addSettingsPage(new DatabaseSettingsPageKeeShare());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef WITH_XC_FDOSECRETS
|
||||||
|
addSettingsPage(new DatabaseSettingsPageFdoSecrets());
|
||||||
|
#endif
|
||||||
|
|
||||||
m_ui->categoryList->addCategory(tr("Maintenance"), icons()->icon("hammer-wrench"));
|
m_ui->categoryList->addCategory(tr("Maintenance"), icons()->icon("hammer-wrench"));
|
||||||
m_ui->stackedWidget->addWidget(m_maintenanceWidget);
|
m_ui->stackedWidget->addWidget(m_maintenanceWidget);
|
||||||
|
|
||||||
pageChanged();
|
m_ui->stackedWidget->setCurrentIndex(0);
|
||||||
|
connect(m_ui->categoryList, SIGNAL(categoryChanged(int)), m_ui->stackedWidget, SLOT(setCurrentIndex(int)));
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseSettingsDialog::~DatabaseSettingsDialog()
|
DatabaseSettingsDialog::~DatabaseSettingsDialog()
|
||||||
@ -163,17 +159,24 @@ void DatabaseSettingsDialog::showDatabaseKeySettings()
|
|||||||
void DatabaseSettingsDialog::save()
|
void DatabaseSettingsDialog::save()
|
||||||
{
|
{
|
||||||
if (!m_generalWidget->save()) {
|
if (!m_generalWidget->save()) {
|
||||||
|
m_ui->categoryList->setCurrentCategory(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_databaseKeyWidget->save()) {
|
if (!m_databaseKeyWidget->save()) {
|
||||||
|
m_ui->categoryList->setCurrentCategory(1);
|
||||||
|
m_securityTabWidget->setCurrentIndex(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_encryptionWidget->save()) {
|
if (!m_encryptionWidget->save()) {
|
||||||
|
m_ui->categoryList->setCurrentCategory(1);
|
||||||
|
m_securityTabWidget->setCurrentIndex(1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Browser settings don't have anything to save
|
||||||
|
|
||||||
for (const ExtraPage& extraPage : asConst(m_extraPages)) {
|
for (const ExtraPage& extraPage : asConst(m_extraPages)) {
|
||||||
extraPage.saveSettings();
|
extraPage.saveSettings();
|
||||||
}
|
}
|
||||||
@ -183,10 +186,12 @@ void DatabaseSettingsDialog::save()
|
|||||||
|
|
||||||
void DatabaseSettingsDialog::reject()
|
void DatabaseSettingsDialog::reject()
|
||||||
{
|
{
|
||||||
|
m_generalWidget->discard();
|
||||||
|
m_databaseKeyWidget->discard();
|
||||||
|
m_encryptionWidget->discard();
|
||||||
|
#ifdef WITH_XC_BROWSER
|
||||||
|
m_browserWidget->discard();
|
||||||
|
#endif
|
||||||
|
|
||||||
emit editFinished(false);
|
emit editFinished(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseSettingsDialog::pageChanged()
|
|
||||||
{
|
|
||||||
m_ui->stackedWidget->currentIndex();
|
|
||||||
}
|
|
||||||
|
@ -70,7 +70,6 @@ signals:
|
|||||||
private slots:
|
private slots:
|
||||||
void save();
|
void save();
|
||||||
void reject();
|
void reject();
|
||||||
void pageChanged();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum Page
|
enum Page
|
||||||
|
@ -87,28 +87,30 @@ void DatabaseSettingsWidgetDatabaseKey::load(QSharedPointer<Database> db)
|
|||||||
// database has no key, we are about to add a new one
|
// database has no key, we are about to add a new one
|
||||||
m_passwordEditWidget->changeVisiblePage(KeyComponentWidget::Page::Edit);
|
m_passwordEditWidget->changeVisiblePage(KeyComponentWidget::Page::Edit);
|
||||||
m_passwordEditWidget->setPasswordVisible(true);
|
m_passwordEditWidget->setPasswordVisible(true);
|
||||||
}
|
// Focus won't work until the UI settles
|
||||||
|
QTimer::singleShot(0, m_passwordEditWidget, SLOT(setFocus()));
|
||||||
bool hasAdditionalKeys = false;
|
} else {
|
||||||
for (const auto& key : m_db->key()->keys()) {
|
bool hasAdditionalKeys = false;
|
||||||
if (key->uuid() == PasswordKey::UUID) {
|
for (const auto& key : m_db->key()->keys()) {
|
||||||
m_passwordEditWidget->setComponentAdded(true);
|
if (key->uuid() == PasswordKey::UUID) {
|
||||||
} else if (key->uuid() == FileKey::UUID) {
|
m_passwordEditWidget->setComponentAdded(true);
|
||||||
m_keyFileEditWidget->setComponentAdded(true);
|
} else if (key->uuid() == FileKey::UUID) {
|
||||||
hasAdditionalKeys = true;
|
m_keyFileEditWidget->setComponentAdded(true);
|
||||||
|
hasAdditionalKeys = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef WITH_XC_YUBIKEY
|
#ifdef WITH_XC_YUBIKEY
|
||||||
for (const auto& key : m_db->key()->challengeResponseKeys()) {
|
for (const auto& key : m_db->key()->challengeResponseKeys()) {
|
||||||
if (key->uuid() == ChallengeResponseKey::UUID) {
|
if (key->uuid() == ChallengeResponseKey::UUID) {
|
||||||
m_yubiKeyEditWidget->setComponentAdded(true);
|
m_yubiKeyEditWidget->setComponentAdded(true);
|
||||||
hasAdditionalKeys = true;
|
hasAdditionalKeys = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
setAdditionalKeyOptionsVisible(hasAdditionalKeys);
|
setAdditionalKeyOptionsVisible(hasAdditionalKeys);
|
||||||
|
}
|
||||||
|
|
||||||
connect(m_passwordEditWidget->findChild<QPushButton*>("removeButton"), SIGNAL(clicked()), SLOT(markDirty()));
|
connect(m_passwordEditWidget->findChild<QPushButton*>("removeButton"), SIGNAL(clicked()), SLOT(markDirty()));
|
||||||
connect(m_keyFileEditWidget->findChild<QPushButton*>("removeButton"), SIGNAL(clicked()), SLOT(markDirty()));
|
connect(m_keyFileEditWidget->findChild<QPushButton*>("removeButton"), SIGNAL(clicked()), SLOT(markDirty()));
|
||||||
@ -185,31 +187,32 @@ bool DatabaseSettingsWidgetDatabaseKey::save()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show warning if database password is weak
|
if (!m_passwordEditWidget->isEmpty()) {
|
||||||
if (!m_passwordEditWidget->isEmpty()
|
// Prevent setting password with a quality less than the minimum required
|
||||||
&& m_passwordEditWidget->getPasswordQuality() < PasswordHealth::Quality::Good) {
|
auto minQuality = qBound(0, config()->get(Config::Security_DatabasePasswordMinimumQuality).toInt(), 4);
|
||||||
auto dialogResult = MessageBox::warning(this,
|
if (m_passwordEditWidget->getPasswordQuality() < static_cast<PasswordHealth::Quality>(minQuality)) {
|
||||||
tr("Weak password"),
|
MessageBox::critical(this,
|
||||||
tr("This is a weak password! For better protection of your secrets, "
|
tr("Weak password"),
|
||||||
"you should choose a stronger password."),
|
tr("The provided password does not meet the minimum quality requirement."),
|
||||||
MessageBox::ContinueWithWeakPass | MessageBox::Cancel,
|
MessageBox::Ok,
|
||||||
MessageBox::Cancel);
|
MessageBox::Ok);
|
||||||
|
|
||||||
if (dialogResult == MessageBox::Cancel) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If enforced in the config file, deny users from continuing with a weak password
|
// Show warning if database password is weak or poor
|
||||||
auto minQuality =
|
if (m_passwordEditWidget->getPasswordQuality() < PasswordHealth::Quality::Good) {
|
||||||
static_cast<PasswordHealth::Quality>(config()->get(Config::Security_DatabasePasswordMinimumQuality).toInt());
|
auto dialogResult =
|
||||||
if (!m_passwordEditWidget->isEmpty() && m_passwordEditWidget->getPasswordQuality() < minQuality) {
|
MessageBox::warning(this,
|
||||||
MessageBox::critical(this,
|
tr("Weak password"),
|
||||||
tr("Weak password"),
|
tr("This is a weak password! For better protection of your secrets, "
|
||||||
tr("You must enter a stronger password to protect your database."),
|
"you should choose a stronger password."),
|
||||||
MessageBox::Ok,
|
MessageBox::ContinueWithWeakPass | MessageBox::Cancel,
|
||||||
MessageBox::Ok);
|
MessageBox::Cancel);
|
||||||
return false;
|
|
||||||
|
if (dialogResult == MessageBox::Cancel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!addToCompositeKey(m_keyFileEditWidget, newKey, oldFileKey)) {
|
if (!addToCompositeKey(m_keyFileEditWidget, newKey, oldFileKey)) {
|
||||||
@ -244,11 +247,16 @@ bool DatabaseSettingsWidgetDatabaseKey::save()
|
|||||||
m_db->markAsModified();
|
m_db->markAsModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset fields
|
||||||
|
initialize();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseSettingsWidgetDatabaseKey::discard()
|
void DatabaseSettingsWidgetDatabaseKey::discard()
|
||||||
{
|
{
|
||||||
|
// Reset fields
|
||||||
|
initialize();
|
||||||
emit editFinished(false);
|
emit editFinished(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1499,12 +1499,20 @@ void TestGui::testDatabaseSettings()
|
|||||||
passwordWidgets[0]->setText("b");
|
passwordWidgets[0]->setText("b");
|
||||||
passwordWidgets[1]->setText("b");
|
passwordWidgets[1]->setText("b");
|
||||||
|
|
||||||
// Cancel password change
|
// Toggle between tabs to ensure the password remains
|
||||||
|
securityTabWidget->setCurrentIndex(1);
|
||||||
|
QApplication::processEvents();
|
||||||
|
securityTabWidget->setCurrentIndex(0);
|
||||||
|
QApplication::processEvents();
|
||||||
|
QCOMPARE(passwordWidgets[0]->text(), QString("b"));
|
||||||
|
|
||||||
|
// Cancel password change and confirm password is cleared
|
||||||
auto cancelPasswordButton = passwordEditWidget->findChild<QPushButton*>("cancelButton");
|
auto cancelPasswordButton = passwordEditWidget->findChild<QPushButton*>("cancelButton");
|
||||||
QVERIFY(cancelPasswordButton);
|
QVERIFY(cancelPasswordButton);
|
||||||
QTest::mouseClick(cancelPasswordButton, Qt::LeftButton);
|
QTest::mouseClick(cancelPasswordButton, Qt::LeftButton);
|
||||||
QApplication::processEvents();
|
QApplication::processEvents();
|
||||||
QVERIFY(!passwordWidgets[0]->isVisible());
|
QVERIFY(!passwordWidgets[0]->isVisible());
|
||||||
|
QCOMPARE(passwordWidgets[0]->text(), QString(""));
|
||||||
QVERIFY(editPasswordButton->isVisible());
|
QVERIFY(editPasswordButton->isVisible());
|
||||||
|
|
||||||
// Switch to encryption tab and interact with various settings
|
// Switch to encryption tab and interact with various settings
|
||||||
|
Loading…
x
Reference in New Issue
Block a user