Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
authorWolfram Rösler <wolfram@roesler-ac.de>2020-02-01 16:42:34 +0300
committerJonathan White <support@dmapps.us>2020-02-01 17:30:12 +0300
commita81c6469a87783abdd4434b03f616465c830b57f (patch)
tree7c57aafb3ac3660929f1c256f0619aea967f4164 /src/gui
parent71a39c37eca9b080a2f06e768ad2e83fe6ff6cb8 (diff)
Implement Password Health Report
Introduce a password health check to the application that evaluates every entry in a database. Entries that fail various tests are listed for user review and action. Also moves the statistics panel to the new Database -> Reports widget. Recycled entries are excluded from the results. We now have two classes, PasswordHealth to deal with a single password and HealthChecker to deal with all passwords of a database. Tests include passwords that are expired, re-used, and weak. * Closes #551 * Move zxcvbn usage to a centralized class (PasswordHealth) and replace its usages across the application to ensure standardized interpretation of entropy calculations. * Add new icons for the database reports view * Updated the demo database to show off the reports
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/AboutDialog.cpp2
-rw-r--r--src/gui/DatabaseTabWidget.cpp5
-rw-r--r--src/gui/DatabaseTabWidget.h1
-rw-r--r--src/gui/DatabaseWidget.cpp11
-rw-r--r--src/gui/DatabaseWidget.h3
-rw-r--r--src/gui/MainWindow.cpp5
-rw-r--r--src/gui/MainWindow.ui15
-rw-r--r--src/gui/PasswordGeneratorWidget.cpp38
-rw-r--r--src/gui/PasswordGeneratorWidget.h3
-rw-r--r--src/gui/dbsettings/DatabaseSettingsDialog.cpp3
-rw-r--r--src/gui/reports/ReportsDialog.cpp128
-rw-r--r--src/gui/reports/ReportsDialog.h85
-rw-r--r--src/gui/reports/ReportsDialog.ui43
-rw-r--r--src/gui/reports/ReportsPageHealthcheck.cpp55
-rw-r--r--src/gui/reports/ReportsPageHealthcheck.h41
-rw-r--r--src/gui/reports/ReportsPageStatistics.cpp (renamed from src/gui/dbsettings/DatabaseSettingsPageStatistics.cpp)22
-rw-r--r--src/gui/reports/ReportsPageStatistics.h (renamed from src/gui/dbsettings/DatabaseSettingsPageStatistics.h)10
-rw-r--r--src/gui/reports/ReportsWidget.cpp44
-rw-r--r--src/gui/reports/ReportsWidget.h53
-rw-r--r--src/gui/reports/ReportsWidgetHealthcheck.cpp237
-rw-r--r--src/gui/reports/ReportsWidgetHealthcheck.h70
-rw-r--r--src/gui/reports/ReportsWidgetHealthcheck.ui79
-rw-r--r--src/gui/reports/ReportsWidgetStatistics.cpp (renamed from src/gui/dbsettings/DatabaseSettingsWidgetStatistics.cpp)38
-rw-r--r--src/gui/reports/ReportsWidgetStatistics.h (renamed from src/gui/dbsettings/DatabaseSettingsWidgetStatistics.h)16
-rw-r--r--src/gui/reports/ReportsWidgetStatistics.ui (renamed from src/gui/dbsettings/DatabaseSettingsWidgetStatistics.ui)4
25 files changed, 949 insertions, 62 deletions
diff --git a/src/gui/AboutDialog.cpp b/src/gui/AboutDialog.cpp
index 4b9fe5f85..bd24cf165 100644
--- a/src/gui/AboutDialog.cpp
+++ b/src/gui/AboutDialog.cpp
@@ -76,7 +76,7 @@ static const QString aboutContributors = R"(
<li>fonic (Entry Table View)</li>
<li>kylemanna (YubiKey)</li>
<li>c4rlo (Offline HIBP Checker)</li>
- <li>wolframroesler (HTML Exporter)</li>
+ <li>wolframroesler (HTML Export, Statistics, Password Health)</li>
<li>mdaniel (OpVault Importer)</li>
<li>keithbennett (KeePassHTTP)</li>
<li>Typz (KeePassHTTP)</li>
diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp
index c37e6c5ea..7e158406b 100644
--- a/src/gui/DatabaseTabWidget.cpp
+++ b/src/gui/DatabaseTabWidget.cpp
@@ -457,6 +457,11 @@ void DatabaseTabWidget::changeMasterKey()
currentDatabaseWidget()->switchToMasterKeyChange();
}
+void DatabaseTabWidget::changeReports()
+{
+ currentDatabaseWidget()->switchToReports();
+}
+
void DatabaseTabWidget::changeDatabaseSettings()
{
currentDatabaseWidget()->switchToDatabaseSettings();
diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h
index 5c55bc63c..29019a2d2 100644
--- a/src/gui/DatabaseTabWidget.h
+++ b/src/gui/DatabaseTabWidget.h
@@ -78,6 +78,7 @@ public slots:
void relockPendingDatabase();
void changeMasterKey();
+ void changeReports();
void changeDatabaseSettings();
void performGlobalAutoType();
diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp
index eb33c09c0..fd579b04a 100644
--- a/src/gui/DatabaseWidget.cpp
+++ b/src/gui/DatabaseWidget.cpp
@@ -59,6 +59,7 @@
#include "gui/entry/EntryView.h"
#include "gui/group/EditGroupWidget.h"
#include "gui/group/GroupView.h"
+#include "gui/reports/ReportsDialog.h"
#include "keeshare/KeeShare.h"
#include "touchid/TouchID.h"
@@ -88,6 +89,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
, m_editEntryWidget(new EditEntryWidget(this))
, m_editGroupWidget(new EditGroupWidget(this))
, m_historyEditEntryWidget(new EditEntryWidget(this))
+ , m_reportsDialog(new ReportsDialog(this))
, m_databaseSettingDialog(new DatabaseSettingsDialog(this))
, m_databaseOpenWidget(new DatabaseOpenWidget(this))
, m_keepass1OpenWidget(new KeePass1OpenWidget(this))
@@ -165,6 +167,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
m_editEntryWidget->setObjectName("editEntryWidget");
m_editGroupWidget->setObjectName("editGroupWidget");
m_csvImportWizard->setObjectName("csvImportWizard");
+ m_reportsDialog->setObjectName("reportsDialog");
m_databaseSettingDialog->setObjectName("databaseSettingsDialog");
m_databaseOpenWidget->setObjectName("databaseOpenWidget");
m_keepass1OpenWidget->setObjectName("keepass1OpenWidget");
@@ -173,6 +176,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
addChildWidget(m_mainWidget);
addChildWidget(m_editEntryWidget);
addChildWidget(m_editGroupWidget);
+ addChildWidget(m_reportsDialog);
addChildWidget(m_databaseSettingDialog);
addChildWidget(m_historyEditEntryWidget);
addChildWidget(m_databaseOpenWidget);
@@ -196,6 +200,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
connect(m_editEntryWidget, SIGNAL(historyEntryActivated(Entry*)), SLOT(switchToHistoryView(Entry*)));
connect(m_historyEditEntryWidget, SIGNAL(editFinished(bool)), SLOT(switchBackToEntryEdit()));
connect(m_editGroupWidget, SIGNAL(editFinished(bool)), SLOT(switchToMainView(bool)));
+ connect(m_reportsDialog, SIGNAL(editFinished(bool)), SLOT(switchToMainView(bool)));
connect(m_databaseSettingDialog, SIGNAL(editFinished(bool)), SLOT(switchToMainView(bool)));
connect(m_databaseOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
connect(m_keepass1OpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
@@ -1105,6 +1110,12 @@ void DatabaseWidget::entryActivationSignalReceived(Entry* entry, EntryModel::Mod
}
}
+void DatabaseWidget::switchToReports()
+{
+ m_reportsDialog->load(m_db);
+ setCurrentWidget(m_reportsDialog);
+}
+
void DatabaseWidget::switchToDatabaseSettings()
{
m_databaseSettingDialog->load(m_db);
diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h
index 9f0c5c976..6420a3b24 100644
--- a/src/gui/DatabaseWidget.h
+++ b/src/gui/DatabaseWidget.h
@@ -34,6 +34,7 @@ class DatabaseOpenWidget;
class KeePass1OpenWidget;
class OpVaultOpenWidget;
class DatabaseSettingsDialog;
+class ReportsDialog;
class Database;
class FileWatcher;
class EditEntryWidget;
@@ -181,6 +182,7 @@ public slots:
void sortGroupsAsc();
void sortGroupsDesc();
void switchToMasterKeyChange();
+ void switchToReports();
void switchToDatabaseSettings();
void switchToOpenDatabase();
void switchToOpenDatabase(const QString& filePath);
@@ -251,6 +253,7 @@ private:
QPointer<EditEntryWidget> m_editEntryWidget;
QPointer<EditGroupWidget> m_editGroupWidget;
QPointer<EditEntryWidget> m_historyEditEntryWidget;
+ QPointer<ReportsDialog> m_reportsDialog;
QPointer<DatabaseSettingsDialog> m_databaseSettingDialog;
QPointer<DatabaseOpenWidget> m_databaseOpenWidget;
QPointer<KeePass1OpenWidget> m_keepass1OpenWidget;
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
index e9c150dd5..2d52331ff 100644
--- a/src/gui/MainWindow.cpp
+++ b/src/gui/MainWindow.cpp
@@ -332,6 +332,7 @@ MainWindow::MainWindow()
m_ui->actionDatabaseSave->setIcon(filePath()->icon("actions", "document-save"));
m_ui->actionDatabaseSaveAs->setIcon(filePath()->icon("actions", "document-save-as"));
m_ui->actionDatabaseClose->setIcon(filePath()->icon("actions", "document-close"));
+ m_ui->actionReports->setIcon(filePath()->icon("actions", "help-about"));
m_ui->actionChangeDatabaseSettings->setIcon(filePath()->icon("actions", "document-edit"));
m_ui->actionChangeMasterKey->setIcon(filePath()->icon("actions", "database-change-key"));
m_ui->actionLockDatabases->setIcon(filePath()->icon("actions", "database-lock"));
@@ -403,6 +404,7 @@ MainWindow::MainWindow()
connect(m_ui->actionDatabaseClose, SIGNAL(triggered()), m_ui->tabWidget, SLOT(closeCurrentDatabaseTab()));
connect(m_ui->actionDatabaseMerge, SIGNAL(triggered()), m_ui->tabWidget, SLOT(mergeDatabase()));
connect(m_ui->actionChangeMasterKey, SIGNAL(triggered()), m_ui->tabWidget, SLOT(changeMasterKey()));
+ connect(m_ui->actionReports, SIGNAL(triggered()), m_ui->tabWidget, SLOT(changeReports()));
connect(m_ui->actionChangeDatabaseSettings, SIGNAL(triggered()), m_ui->tabWidget, SLOT(changeDatabaseSettings()));
connect(m_ui->actionImportCsv, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importCsv()));
connect(m_ui->actionImportKeePass1, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importKeePass1Database()));
@@ -673,6 +675,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionGroupDownloadFavicons->setEnabled(groupSelected && currentGroupHasEntries
&& !recycleBinSelected);
m_ui->actionChangeMasterKey->setEnabled(true);
+ m_ui->actionReports->setEnabled(true);
m_ui->actionChangeDatabaseSettings->setEnabled(true);
m_ui->actionDatabaseSave->setEnabled(m_ui->tabWidget->canSave());
m_ui->actionDatabaseSaveAs->setEnabled(true);
@@ -719,6 +722,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
}
m_ui->actionChangeMasterKey->setEnabled(false);
+ m_ui->actionReports->setEnabled(false);
m_ui->actionChangeDatabaseSettings->setEnabled(false);
m_ui->actionDatabaseSave->setEnabled(false);
m_ui->actionDatabaseSaveAs->setEnabled(false);
@@ -746,6 +750,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
}
m_ui->actionChangeMasterKey->setEnabled(false);
+ m_ui->actionReports->setEnabled(false);
m_ui->actionChangeDatabaseSettings->setEnabled(false);
m_ui->actionDatabaseSave->setEnabled(false);
m_ui->actionDatabaseSaveAs->setEnabled(false);
diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui
index e09c91dd7..aec0efb37 100644
--- a/src/gui/MainWindow.ui
+++ b/src/gui/MainWindow.ui
@@ -236,6 +236,7 @@
<addaction name="actionDatabaseClose"/>
<addaction name="separator"/>
<addaction name="actionChangeMasterKey"/>
+ <addaction name="actionReports"/>
<addaction name="actionChangeDatabaseSettings"/>
<addaction name="separator"/>
<addaction name="actionDatabaseMerge"/>
@@ -532,6 +533,20 @@
<string>Change master &amp;key...</string>
</property>
</action>
+ <action name="actionReports">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&amp;Reports...</string>
+ </property>
+ <property name="toolTip">
+ <string>Statistics, health check, etc.</string>
+ </property>
+ <property name="menuRole">
+ <enum>QAction::NoRole</enum>
+ </property>
+ </action>
<action name="actionChangeDatabaseSettings">
<property name="enabled">
<bool>false</bool>
diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp
index e0f8fbe5f..c04487c0e 100644
--- a/src/gui/PasswordGeneratorWidget.cpp
+++ b/src/gui/PasswordGeneratorWidget.cpp
@@ -26,6 +26,7 @@
#include "core/Config.h"
#include "core/FilePath.h"
#include "core/PasswordGenerator.h"
+#include "core/PasswordHealth.h"
#include "gui/Clipboard.h"
PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
@@ -261,21 +262,17 @@ void PasswordGeneratorWidget::updateButtonsEnabled(const QString& password)
void PasswordGeneratorWidget::updatePasswordStrength(const QString& password)
{
- double entropy = 0.0;
- if (m_ui->tabWidget->currentIndex() == Password) {
- entropy = m_passwordGenerator->estimateEntropy(password);
- } else {
- entropy = m_dicewareGenerator->estimateEntropy();
+ PasswordHealth health(password);
+ if (m_ui->tabWidget->currentIndex() == Diceware) {
+ // Diceware estimates entropy differently
+ health = PasswordHealth(m_dicewareGenerator->estimateEntropy());
}
- m_ui->entropyLabel->setText(tr("Entropy: %1 bit").arg(QString::number(entropy, 'f', 2)));
+ m_ui->entropyLabel->setText(tr("Entropy: %1 bit").arg(QString::number(health.entropy(), 'f', 2)));
- if (entropy > m_ui->entropyProgressBar->maximum()) {
- entropy = m_ui->entropyProgressBar->maximum();
- }
- m_ui->entropyProgressBar->setValue(entropy);
+ m_ui->entropyProgressBar->setValue(std::min(int(health.entropy()), m_ui->entropyProgressBar->maximum()));
- colorStrengthIndicator(entropy);
+ colorStrengthIndicator(health);
}
void PasswordGeneratorWidget::applyPassword()
@@ -384,7 +381,7 @@ void PasswordGeneratorWidget::excludeHexChars()
m_ui->editExcludedChars->setText("GHIJKLMNOPQRSTUVWXYZghijklmnopqrstuvwxyz");
}
-void PasswordGeneratorWidget::colorStrengthIndicator(double entropy)
+void PasswordGeneratorWidget::colorStrengthIndicator(const PasswordHealth& health)
{
// Take the existing stylesheet and convert the text and background color to arguments
QString style = m_ui->entropyProgressBar->styleSheet();
@@ -395,18 +392,27 @@ void PasswordGeneratorWidget::colorStrengthIndicator(double entropy)
// Set the color and background based on entropy
// colors are taking from the KDE breeze palette
// <https://community.kde.org/KDE_Visual_Design_Group/HIG/Color>
- if (entropy < 40) {
+ switch (health.quality()) {
+ case PasswordHealth::Quality::Bad:
+ case PasswordHealth::Quality::Poor:
m_ui->entropyProgressBar->setStyleSheet(style.arg("#c0392b"));
m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Poor", "Password quality")));
- } else if (entropy >= 40 && entropy < 65) {
+ break;
+
+ case PasswordHealth::Quality::Weak:
m_ui->entropyProgressBar->setStyleSheet(style.arg("#f39c1f"));
m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Weak", "Password quality")));
- } else if (entropy >= 65 && entropy < 100) {
+ break;
+
+ case PasswordHealth::Quality::Good:
m_ui->entropyProgressBar->setStyleSheet(style.arg("#11d116"));
m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Good", "Password quality")));
- } else {
+ break;
+
+ case PasswordHealth::Quality::Excellent:
m_ui->entropyProgressBar->setStyleSheet(style.arg("#27ae60"));
m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Excellent", "Password quality")));
+ break;
}
}
diff --git a/src/gui/PasswordGeneratorWidget.h b/src/gui/PasswordGeneratorWidget.h
index b39a2f10f..eba7f815f 100644
--- a/src/gui/PasswordGeneratorWidget.h
+++ b/src/gui/PasswordGeneratorWidget.h
@@ -32,6 +32,7 @@ namespace Ui
}
class PasswordGenerator;
+class PasswordHealth;
class PassphraseGenerator;
class PasswordGeneratorWidget : public QWidget
@@ -77,7 +78,7 @@ private slots:
void passwordSpinBoxChanged();
void dicewareSliderMoved();
void dicewareSpinBoxChanged();
- void colorStrengthIndicator(double entropy);
+ void colorStrengthIndicator(const PasswordHealth& health);
void updateGenerator();
diff --git a/src/gui/dbsettings/DatabaseSettingsDialog.cpp b/src/gui/dbsettings/DatabaseSettingsDialog.cpp
index 33c4df2c4..e0e6765a4 100644
--- a/src/gui/dbsettings/DatabaseSettingsDialog.cpp
+++ b/src/gui/dbsettings/DatabaseSettingsDialog.cpp
@@ -19,7 +19,6 @@
#include "DatabaseSettingsDialog.h"
#include "ui_DatabaseSettingsDialog.h"
-#include "DatabaseSettingsPageStatistics.h"
#include "DatabaseSettingsWidgetEncryption.h"
#include "DatabaseSettingsWidgetGeneral.h"
#include "DatabaseSettingsWidgetMasterKey.h"
@@ -85,8 +84,6 @@ DatabaseSettingsDialog::DatabaseSettingsDialog(QWidget* parent)
m_securityTabWidget->addTab(m_masterKeyWidget, tr("Master Key"));
m_securityTabWidget->addTab(m_encryptionWidget, tr("Encryption Settings"));
- addSettingsPage(new DatabaseSettingsPageStatistics());
-
#if defined(WITH_XC_KEESHARE)
addSettingsPage(new DatabaseSettingsPageKeeShare());
#endif
diff --git a/src/gui/reports/ReportsDialog.cpp b/src/gui/reports/ReportsDialog.cpp
new file mode 100644
index 000000000..22ebab41a
--- /dev/null
+++ b/src/gui/reports/ReportsDialog.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
+ *
+ * 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
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ReportsDialog.h"
+#include "ui_ReportsDialog.h"
+
+#include "ReportsPageHealthcheck.h"
+#include "ReportsPageStatistics.h"
+#include "ReportsWidgetHealthcheck.h"
+
+#include "core/Global.h"
+#include "touchid/TouchID.h"
+#include <core/Entry.h>
+#include <core/Group.h>
+
+class ReportsDialog::ExtraPage
+{
+public:
+ ExtraPage(QSharedPointer<IReportsPage> p, QWidget* w)
+ : page(p)
+ , widget(w)
+ {
+ }
+ void loadSettings(QSharedPointer<Database> db) const
+ {
+ page->loadSettings(widget, db);
+ }
+ void saveSettings() const
+ {
+ page->saveSettings(widget);
+ }
+
+private:
+ QSharedPointer<IReportsPage> page;
+ QWidget* widget;
+};
+
+ReportsDialog::ReportsDialog(QWidget* parent)
+ : DialogyWidget(parent)
+ , m_ui(new Ui::ReportsDialog())
+ , m_healthPage(new ReportsPageHealthcheck())
+ , m_statPage(new ReportsPageStatistics())
+ , m_editEntryWidget(new EditEntryWidget(this))
+{
+ m_ui->setupUi(this);
+
+ connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject()));
+ addPage(m_healthPage);
+ addPage(m_statPage);
+
+ m_ui->stackedWidget->setCurrentIndex(0);
+
+ m_editEntryWidget->setObjectName("editEntryWidget");
+ m_editEntryWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
+ m_ui->stackedWidget->addWidget(m_editEntryWidget);
+ adjustSize();
+
+ connect(m_ui->categoryList, SIGNAL(categoryChanged(int)), m_ui->stackedWidget, SLOT(setCurrentIndex(int)));
+ connect(m_healthPage->m_healthWidget,
+ SIGNAL(entryActivated(const Group*, Entry*)),
+ SLOT(entryActivationSignalReceived(const Group*, Entry*)));
+ connect(m_editEntryWidget, SIGNAL(editFinished(bool)), SLOT(switchToMainView(bool)));
+}
+
+ReportsDialog::~ReportsDialog()
+{
+}
+
+void ReportsDialog::load(const QSharedPointer<Database>& db)
+{
+ m_ui->categoryList->setCurrentCategory(0);
+ for (const ExtraPage& page : asConst(m_extraPages)) {
+ page.loadSettings(db);
+ }
+ m_db = db;
+}
+
+void ReportsDialog::addPage(QSharedPointer<IReportsPage> page)
+{
+ const auto category = m_ui->categoryList->currentCategory();
+ const auto widget = page->createWidget();
+ widget->setParent(this);
+ m_extraPages.append(ExtraPage(page, widget));
+ m_ui->stackedWidget->addWidget(widget);
+ m_ui->categoryList->addCategory(page->name(), page->icon());
+ m_ui->categoryList->setCurrentCategory(category);
+}
+
+void ReportsDialog::reject()
+{
+ for (const ExtraPage& extraPage : asConst(m_extraPages)) {
+ extraPage.saveSettings();
+ }
+
+#ifdef WITH_XC_TOUCHID
+ TouchID::getInstance().reset(m_db ? m_db->filePath() : "");
+#endif
+
+ emit editFinished(true);
+}
+
+void ReportsDialog::entryActivationSignalReceived(const Group* group, Entry* entry)
+{
+ m_editEntryWidget->loadEntry(entry, false, false, group->hierarchy().join(" > "), m_db);
+ m_ui->stackedWidget->setCurrentWidget(m_editEntryWidget);
+}
+
+void ReportsDialog::switchToMainView(bool previousDialogAccepted)
+{
+ m_ui->stackedWidget->setCurrentWidget(m_healthPage->m_healthWidget);
+ if (previousDialogAccepted) {
+ m_healthPage->m_healthWidget->calculateHealth();
+ }
+}
diff --git a/src/gui/reports/ReportsDialog.h b/src/gui/reports/ReportsDialog.h
new file mode 100644
index 000000000..7a53623c3
--- /dev/null
+++ b/src/gui/reports/ReportsDialog.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
+ *
+ * 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
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KEEPASSX_REPORTSWIDGET_H
+#define KEEPASSX_REPORTSWIDGET_H
+
+#include "config-keepassx.h"
+#include "gui/DialogyWidget.h"
+#include "gui/entry/EditEntryWidget.h"
+
+#include <QPointer>
+#include <QScopedPointer>
+#include <QSharedPointer>
+
+class Database;
+class Entry;
+class Group;
+class QTabWidget;
+class ReportsPageHealthcheck;
+class ReportsPageStatistics;
+
+namespace Ui
+{
+ class ReportsDialog;
+}
+
+class IReportsPage
+{
+public:
+ virtual ~IReportsPage()
+ {
+ }
+ virtual QString name() = 0;
+ virtual QIcon icon() = 0;
+ virtual QWidget* createWidget() = 0;
+ virtual void loadSettings(QWidget* widget, QSharedPointer<Database> db) = 0;
+ virtual void saveSettings(QWidget* widget) = 0;
+};
+
+class ReportsDialog : public DialogyWidget
+{
+ Q_OBJECT
+
+public:
+ explicit ReportsDialog(QWidget* parent = nullptr);
+ ~ReportsDialog() override;
+ Q_DISABLE_COPY(ReportsDialog);
+
+ void load(const QSharedPointer<Database>& db);
+ void addPage(QSharedPointer<IReportsPage> page);
+
+signals:
+ void editFinished(bool accepted);
+
+private slots:
+ void reject();
+ void entryActivationSignalReceived(const Group*, Entry* entry);
+ void switchToMainView(bool previousDialogAccepted);
+
+private:
+ QSharedPointer<Database> m_db;
+ const QScopedPointer<Ui::ReportsDialog> m_ui;
+ const QSharedPointer<ReportsPageHealthcheck> m_healthPage;
+ const QSharedPointer<ReportsPageStatistics> m_statPage;
+ QPointer<EditEntryWidget> m_editEntryWidget;
+
+ class ExtraPage;
+ QList<ExtraPage> m_extraPages;
+};
+
+#endif // KEEPASSX_REPORTSWIDGET_H
diff --git a/src/gui/reports/ReportsDialog.ui b/src/gui/reports/ReportsDialog.ui
new file mode 100644
index 000000000..773981a10
--- /dev/null
+++ b/src/gui/reports/ReportsDialog.ui
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ReportsDialog</class>
+ <widget class="QWidget" name="ReportsDialog">
+ <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,1">
+ <item>
+ <widget class="CategoryListWidget" name="categoryList" native="true"/>
+ </item>
+ <item>
+ <widget class="QStackedWidget" name="stackedWidget">
+ <property name="currentIndex">
+ <number>-1</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>CategoryListWidget</class>
+ <extends>QWidget</extends>
+ <header>gui/CategoryListWidget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/gui/reports/ReportsPageHealthcheck.cpp b/src/gui/reports/ReportsPageHealthcheck.cpp
new file mode 100644
index 000000000..41fa40625
--- /dev/null
+++ b/src/gui/reports/ReportsPageHealthcheck.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
+ *
+ * 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
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ReportsPageHealthcheck.h"
+
+#include "ReportsWidgetHealthcheck.h"
+#include "core/FilePath.h"
+
+#include <QApplication>
+
+ReportsPageHealthcheck::ReportsPageHealthcheck()
+ : m_healthWidget(new ReportsWidgetHealthcheck())
+{
+}
+
+QString ReportsPageHealthcheck::name()
+{
+ return QApplication::tr("Health Check");
+}
+
+QIcon ReportsPageHealthcheck::icon()
+{
+ return FilePath::instance()->icon("actions", "health");
+}
+
+QWidget* ReportsPageHealthcheck::createWidget()
+{
+ return m_healthWidget;
+}
+
+void ReportsPageHealthcheck::loadSettings(QWidget* widget, QSharedPointer<Database> db)
+{
+ const auto settingsWidget = reinterpret_cast<ReportsWidgetHealthcheck*>(widget);
+ settingsWidget->loadSettings(db);
+}
+
+void ReportsPageHealthcheck::saveSettings(QWidget* widget)
+{
+ const auto settingsWidget = reinterpret_cast<ReportsWidgetHealthcheck*>(widget);
+ settingsWidget->saveSettings();
+}
diff --git a/src/gui/reports/ReportsPageHealthcheck.h b/src/gui/reports/ReportsPageHealthcheck.h
new file mode 100644
index 000000000..8a85b2d20
--- /dev/null
+++ b/src/gui/reports/ReportsPageHealthcheck.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
+ *
+ * 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
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KEEPASSXC_REPORTSPAGEHEALTHCHECK_H
+#define KEEPASSXC_REPORTSPAGEHEALTHCHECK_H
+
+#include <QWidget>
+
+#include "ReportsDialog.h"
+
+class ReportsWidgetHealthcheck;
+
+class ReportsPageHealthcheck : public IReportsPage
+{
+public:
+ ReportsWidgetHealthcheck* m_healthWidget;
+
+ ReportsPageHealthcheck();
+
+ QString name() override;
+ QIcon icon() override;
+ QWidget* createWidget() override;
+ void loadSettings(QWidget* widget, QSharedPointer<Database> db) override;
+ void saveSettings(QWidget* widget) override;
+};
+
+#endif // KEEPASSXC_REPORTSPAGEHEALTHCHECK_H
diff --git a/src/gui/dbsettings/DatabaseSettingsPageStatistics.cpp b/src/gui/reports/ReportsPageStatistics.cpp
index 6fe24ff0f..e4570e172 100644
--- a/src/gui/dbsettings/DatabaseSettingsPageStatistics.cpp
+++ b/src/gui/reports/ReportsPageStatistics.cpp
@@ -15,38 +15,36 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "DatabaseSettingsPageStatistics.h"
+#include "ReportsPageStatistics.h"
-#include "DatabaseSettingsWidgetStatistics.h"
-#include "core/Database.h"
+#include "ReportsWidgetStatistics.h"
#include "core/FilePath.h"
-#include "core/Group.h"
#include <QApplication>
-QString DatabaseSettingsPageStatistics::name()
+QString ReportsPageStatistics::name()
{
return QApplication::tr("Statistics");
}
-QIcon DatabaseSettingsPageStatistics::icon()
+QIcon ReportsPageStatistics::icon()
{
return FilePath::instance()->icon("actions", "statistics");
}
-QWidget* DatabaseSettingsPageStatistics::createWidget()
+QWidget* ReportsPageStatistics::createWidget()
{
- return new DatabaseSettingsWidgetStatistics();
+ return new ReportsWidgetStatistics();
}
-void DatabaseSettingsPageStatistics::loadSettings(QWidget* widget, QSharedPointer<Database> db)
+void ReportsPageStatistics::loadSettings(QWidget* widget, QSharedPointer<Database> db)
{
- DatabaseSettingsWidgetStatistics* settingsWidget = reinterpret_cast<DatabaseSettingsWidgetStatistics*>(widget);
+ ReportsWidgetStatistics* settingsWidget = reinterpret_cast<ReportsWidgetStatistics*>(widget);
settingsWidget->loadSettings(db);
}
-void DatabaseSettingsPageStatistics::saveSettings(QWidget* widget)
+void ReportsPageStatistics::saveSettings(QWidget* widget)
{
- DatabaseSettingsWidgetStatistics* settingsWidget = reinterpret_cast<DatabaseSettingsWidgetStatistics*>(widget);
+ ReportsWidgetStatistics* settingsWidget = reinterpret_cast<ReportsWidgetStatistics*>(widget);
settingsWidget->saveSettings();
}
diff --git a/src/gui/dbsettings/DatabaseSettingsPageStatistics.h b/src/gui/reports/ReportsPageStatistics.h
index c890f3b81..00d611ee3 100644
--- a/src/gui/dbsettings/DatabaseSettingsPageStatistics.h
+++ b/src/gui/reports/ReportsPageStatistics.h
@@ -15,14 +15,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef KEEPASSXC_DATABASESETTINGSPAGESTATISTICS_H
-#define KEEPASSXC_DATABASESETTINGSPAGESTATISTICS_H
+#ifndef KEEPASSXC_REPORTSPAGESTATISTICS_H
+#define KEEPASSXC_REPORTSPAGESTATISTICS_H
#include <QWidget>
-#include "DatabaseSettingsDialog.h"
+#include "ReportsDialog.h"
-class DatabaseSettingsPageStatistics : public IDatabaseSettingsPage
+class ReportsPageStatistics : public IReportsPage
{
public:
QString name() override;
@@ -32,4 +32,4 @@ public:
void saveSettings(QWidget* widget) override;
};
-#endif // KEEPASSXC_DATABASESETTINGSPAGESTATISTICS_H
+#endif // KEEPASSXC_REPORTSPAGESTATISTICS_H
diff --git a/src/gui/reports/ReportsWidget.cpp b/src/gui/reports/ReportsWidget.cpp
new file mode 100644
index 000000000..184434116
--- /dev/null
+++ b/src/gui/reports/ReportsWidget.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
+ *
+ * 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
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ReportsWidget.h"
+
+ReportsWidget::ReportsWidget(QWidget* parent)
+ : SettingsWidget(parent)
+{
+}
+
+ReportsWidget::~ReportsWidget()
+{
+}
+
+/**
+ * Load the database to be configured by this page and initialize the page.
+ * The page will NOT take ownership of the database.
+ *
+ * @param db database object to be configured
+ */
+void ReportsWidget::load(QSharedPointer<Database> db)
+{
+ m_db = std::move(db);
+ initialize();
+}
+
+const QSharedPointer<Database> ReportsWidget::getDatabase() const
+{
+ return m_db;
+}
diff --git a/src/gui/reports/ReportsWidget.h b/src/gui/reports/ReportsWidget.h
new file mode 100644
index 000000000..631490405
--- /dev/null
+++ b/src/gui/reports/ReportsWidget.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
+ *
+ * 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
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KEEPASSXC_REPORTSWIDGET_H
+#define KEEPASSXC_REPORTSWIDGET_H
+
+#include "gui/settings/SettingsWidget.h"
+
+#include <QSharedPointer>
+
+class Database;
+
+/**
+ * Pure-virtual base class for KeePassXC database settings widgets.
+ */
+class ReportsWidget : public SettingsWidget
+{
+ Q_OBJECT
+
+public:
+ explicit ReportsWidget(QWidget* parent = nullptr);
+ Q_DISABLE_COPY(ReportsWidget);
+ ~ReportsWidget() override;
+
+ virtual void load(QSharedPointer<Database> db);
+
+ const QSharedPointer<Database> getDatabase() const;
+
+signals:
+ /**
+ * Can be emitted to indicate size changes and allow parents widgets to adjust properly.
+ */
+ void sizeChanged();
+
+protected:
+ QSharedPointer<Database> m_db;
+};
+
+#endif // KEEPASSXC_REPORTSWIDGET_H
diff --git a/src/gui/reports/ReportsWidgetHealthcheck.cpp b/src/gui/reports/ReportsWidgetHealthcheck.cpp
new file mode 100644
index 000000000..c668b3495
--- /dev/null
+++ b/src/gui/reports/ReportsWidgetHealthcheck.cpp
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
+ *
+ * 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
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ReportsWidgetHealthcheck.h"
+#include "ui_ReportsWidgetHealthcheck.h"
+
+#include "core/AsyncTask.h"
+#include "core/Database.h"
+#include "core/FilePath.h"
+#include "core/Group.h"
+#include "core/PasswordHealth.h"
+
+#include <QSharedPointer>
+#include <QStandardItemModel>
+#include <QVector>
+
+namespace
+{
+ class Health
+ {
+ public:
+ struct Item
+ {
+ QPointer<const Group> group;
+ QPointer<const Entry> entry;
+ QSharedPointer<PasswordHealth> health;
+
+ Item(const Group* g, const Entry* e, QSharedPointer<PasswordHealth> h)
+ : group(g)
+ , entry(e)
+ , health(h)
+ {
+ }
+
+ bool operator<(const Item& rhs) const
+ {
+ return health->score() < rhs.health->score();
+ }
+ };
+
+ explicit Health(QSharedPointer<Database>);
+
+ const QList<QSharedPointer<Item>>& items() const
+ {
+ return m_items;
+ }
+
+ private:
+ QSharedPointer<Database> m_db;
+ HealthChecker m_checker;
+ QList<QSharedPointer<Item>> m_items;
+ };
+} // namespace
+
+Health::Health(QSharedPointer<Database> db)
+ : m_db(db)
+ , m_checker(db)
+{
+ for (const auto* group : db->rootGroup()->groupsRecursive(true)) {
+ // Skip recycle bin
+ if (group->isRecycled()) {
+ continue;
+ }
+
+ for (const auto* entry : group->entries()) {
+ if (entry->isRecycled()) {
+ continue;
+ }
+
+ // Skip entries with empty password
+ if (entry->password().isEmpty()) {
+ continue;
+ }
+
+ // Add entry if its password isn't at least "good"
+ const auto item = QSharedPointer<Item>(new Item(group, entry, m_checker.evaluate(entry)));
+ if (item->health->quality() < PasswordHealth::Quality::Good) {
+ m_items.append(item);
+ }
+ }
+ }
+
+ // Sort the result so that the worst passwords (least score)
+ // are at the top
+ std::sort(m_items.begin(), m_items.end(), [](QSharedPointer<Item> x, QSharedPointer<Item> y) { return *x < *y; });
+}
+
+ReportsWidgetHealthcheck::ReportsWidgetHealthcheck(QWidget* parent)
+ : QWidget(parent)
+ , m_ui(new Ui::ReportsWidgetHealthcheck())
+ , m_errorIcon(FilePath::instance()->icon("status", "dialog-error"))
+{
+ m_ui->setupUi(this);
+
+ m_referencesModel.reset(new QStandardItemModel());
+ m_ui->healthcheckTableView->setModel(m_referencesModel.data());
+ m_ui->healthcheckTableView->setSelectionMode(QAbstractItemView::NoSelection);
+ m_ui->healthcheckTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
+
+ connect(m_ui->healthcheckTableView, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex)));
+}
+
+ReportsWidgetHealthcheck::~ReportsWidgetHealthcheck()
+{
+}
+
+void ReportsWidgetHealthcheck::addHealthRow(QSharedPointer<PasswordHealth> health,
+ const Group* group,
+ const Entry* entry)
+{
+ QString descr, tip;
+ QColor qualityColor;
+ const auto quality = health->quality();
+ switch (quality) {
+ case PasswordHealth::Quality::Bad:
+ descr = tr("Bad", "Password quality");
+ tip = tr("Bad — password must be changed");
+ qualityColor.setNamedColor("red");
+ break;
+
+ case PasswordHealth::Quality::Poor:
+ descr = tr("Poor", "Password quality");
+ tip = tr("Poor — password should be changed");
+ qualityColor.setNamedColor("orange");
+ break;
+
+ case PasswordHealth::Quality::Weak:
+ descr = tr("Weak", "Password quality");
+ tip = tr("Weak — consider changing the password");
+ qualityColor.setNamedColor("yellow");
+ break;
+
+ case PasswordHealth::Quality::Good:
+ case PasswordHealth::Quality::Excellent:
+ qualityColor.setNamedColor("green");
+ break;
+ }
+
+ auto row = QList<QStandardItem*>();
+ row << new QStandardItem(descr);
+ row << new QStandardItem(entry->iconPixmap(), entry->title());
+ row << new QStandardItem(group->iconPixmap(), group->hierarchy().join("/"));
+ row << new QStandardItem(QString::number(health->score()));
+ row << new QStandardItem(health->scoreReason());
+
+ // Set background color of first column according to password quality.
+ // Set the same as foreground color so the description is usually
+ // invisible, it's just for screen readers etc.
+ QBrush brush(qualityColor);
+ row[0]->setForeground(brush);
+ row[0]->setBackground(brush);
+
+ // Set tooltips
+ row[0]->setToolTip(tip);
+ row[4]->setToolTip(health->scoreDetails());
+
+ // Store entry pointer per table row (used in double click handler)
+ m_referencesModel->appendRow(row);
+ m_rowToEntry.append({group, entry});
+}
+
+void ReportsWidgetHealthcheck::loadSettings(QSharedPointer<Database> db)
+{
+ m_db = std::move(db);
+ m_healthCalculated = false;
+ m_referencesModel->clear();
+ m_rowToEntry.clear();
+
+ auto row = QList<QStandardItem*>();
+ row << new QStandardItem(tr("Please wait, health data is being calculated..."));
+ m_referencesModel->appendRow(row);
+}
+
+void ReportsWidgetHealthcheck::showEvent(QShowEvent* event)
+{
+ QWidget::showEvent(event);
+
+ if (!m_healthCalculated) {
+ // Perform stats calculation on next event loop to allow widget to appear
+ m_healthCalculated = true;
+ QTimer::singleShot(0, this, SLOT(calculateHealth()));
+ }
+}
+
+void ReportsWidgetHealthcheck::calculateHealth()
+{
+ m_referencesModel->clear();
+
+ const QScopedPointer<Health> health(AsyncTask::runAndWaitForFuture([this] { return new Health(m_db); }));
+ if (health->items().empty()) {
+ // No findings
+ m_referencesModel->clear();
+ m_referencesModel->setHorizontalHeaderLabels(QStringList() << tr("Congratulations, everything is healthy!"));
+ } else {
+ // Show our findings
+ m_referencesModel->setHorizontalHeaderLabels(QStringList() << tr("") << tr("Title") << tr("Path") << tr("Score")
+ << tr("Reason"));
+ for (const auto& item : health->items()) {
+ addHealthRow(item->health, item->group, item->entry);
+ }
+ }
+
+ m_ui->healthcheckTableView->resizeRowsToContents();
+}
+
+void ReportsWidgetHealthcheck::emitEntryActivated(const QModelIndex& index)
+{
+ if (!index.isValid()) {
+ return;
+ }
+
+ const auto row = m_rowToEntry[index.row()];
+ const auto group = row.first;
+ const auto entry = row.second;
+ if (group && entry) {
+ emit entryActivated(group, const_cast<Entry*>(entry));
+ }
+}
+
+void ReportsWidgetHealthcheck::saveSettings()
+{
+ // nothing to do - the tab is passive
+}
diff --git a/src/gui/reports/ReportsWidgetHealthcheck.h b/src/gui/reports/ReportsWidgetHealthcheck.h
new file mode 100644
index 000000000..bf0cf531e
--- /dev/null
+++ b/src/gui/reports/ReportsWidgetHealthcheck.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
+ *
+ * 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
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KEEPASSXC_REPORTSWIDGETHEALTHCHECK_H
+#define KEEPASSXC_REPORTSWIDGETHEALTHCHECK_H
+
+#include "gui/entry/EntryModel.h"
+#include <QHash>
+#include <QIcon>
+#include <QPair>
+#include <QWidget>
+
+class Database;
+class Entry;
+class Group;
+class PasswordHealth;
+class QStandardItemModel;
+
+namespace Ui
+{
+ class ReportsWidgetHealthcheck;
+}
+
+class ReportsWidgetHealthcheck : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit ReportsWidgetHealthcheck(QWidget* parent = nullptr);
+ ~ReportsWidgetHealthcheck();
+
+ void loadSettings(QSharedPointer<Database> db);
+ void saveSettings();
+
+protected:
+ void showEvent(QShowEvent* event) override;
+
+signals:
+ void entryActivated(const Group* group, Entry* entry);
+
+public slots:
+ void calculateHealth();
+ void emitEntryActivated(const QModelIndex& index);
+
+private:
+ void addHealthRow(QSharedPointer<PasswordHealth>, const Group*, const Entry*);
+
+ QScopedPointer<Ui::ReportsWidgetHealthcheck> m_ui;
+
+ bool m_healthCalculated = false;
+ QIcon m_errorIcon;
+ QScopedPointer<QStandardItemModel> m_referencesModel;
+ QSharedPointer<Database> m_db;
+ QList<QPair<const Group*, const Entry*>> m_rowToEntry;
+};
+
+#endif // KEEPASSXC_REPORTSWIDGETHEALTHCHECK_H
diff --git a/src/gui/reports/ReportsWidgetHealthcheck.ui b/src/gui/reports/ReportsWidgetHealthcheck.ui
new file mode 100644
index 000000000..48d8df07f
--- /dev/null
+++ b/src/gui/reports/ReportsWidgetHealthcheck.ui
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ReportsWidgetHealthcheck</class>
+ <widget class="QWidget" name="ReportsWidgetHealthcheck">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>327</width>
+ <height>379</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout" stretch="0">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="healthcheckGroupBox">
+ <property name="title">
+ <string>Health Check</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QTableView" name="healthcheckTableView">
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="showDropIndicator" stdset="0">
+ <bool>false</bool>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="textElideMode">
+ <enum>Qt::ElideMiddle</enum>
+ </property>
+ <property name="sortingEnabled">
+ <bool>false</bool>
+ </property>
+ <attribute name="horizontalHeaderVisible">
+ <bool>true</bool>
+ </attribute>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ <attribute name="verticalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="tipLabel">
+ <property name="font">
+ <font>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>Hover over reason to show additional details. Double-click entries to edit.</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetStatistics.cpp b/src/gui/reports/ReportsWidgetStatistics.cpp
index b02741adb..bc642af78 100644
--- a/src/gui/dbsettings/DatabaseSettingsWidgetStatistics.cpp
+++ b/src/gui/reports/ReportsWidgetStatistics.cpp
@@ -15,15 +15,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "DatabaseSettingsWidgetStatistics.h"
-#include "ui_DatabaseSettingsWidgetStatistics.h"
+#include "ReportsWidgetStatistics.h"
+#include "ui_ReportsWidgetStatistics.h"
#include "core/AsyncTask.h"
#include "core/Database.h"
#include "core/FilePath.h"
#include "core/Group.h"
#include "core/Metadata.h"
-#include "zxcvbn.h"
+#include "core/PasswordHealth.h"
#include <QFileInfo>
#include <QHash>
@@ -48,6 +48,7 @@ namespace
// Ctor does all the work
explicit Stats(QSharedPointer<Database> db)
: modified(QFileInfo(db->filePath()).lastModified())
+ , m_db(db)
{
gatherStats(db->rootGroup()->groupsRecursive(true));
}
@@ -92,19 +93,27 @@ namespace
}
private:
+ QSharedPointer<Database> m_db;
QHash<QString, int> m_passwords;
void gatherStats(const QList<Group*>& groups)
{
+ auto checker = HealthChecker(m_db);
+
for (const auto* group : groups) {
// Don't count anything in the recycle bin
- if (group == group->database()->metadata()->recycleBin()) {
+ if (group->isRecycled()) {
continue;
}
++nGroups;
for (const auto* entry : group->entries()) {
+ // Don't count anything in the recycle bin
+ if (entry->isRecycled()) {
+ continue;
+ }
+
++nEntries;
if (entry->isExpired()) {
@@ -125,7 +134,7 @@ namespace
}
// Speed up Zxcvbn process by excluding very long passwords and most passphrases
- if (pwd.size() < 25 && ZxcvbnMatch(pwd.toLatin1(), nullptr, nullptr) < 65) {
+ if (pwd.size() < 25 && checker.evaluate(entry)->quality() <= PasswordHealth::Quality::Weak) {
++nPwdsWeak;
}
@@ -138,9 +147,9 @@ namespace
};
} // namespace
-DatabaseSettingsWidgetStatistics::DatabaseSettingsWidgetStatistics(QWidget* parent)
+ReportsWidgetStatistics::ReportsWidgetStatistics(QWidget* parent)
: QWidget(parent)
- , m_ui(new Ui::DatabaseSettingsWidgetStatistics())
+ , m_ui(new Ui::ReportsWidgetStatistics())
, m_errIcon(FilePath::instance()->icon("status", "dialog-error"))
{
m_ui->setupUi(this);
@@ -148,14 +157,15 @@ DatabaseSettingsWidgetStatistics::DatabaseSettingsWidgetStatistics(QWidget* pare
m_referencesModel.reset(new QStandardItemModel());
m_referencesModel->setHorizontalHeaderLabels(QStringList() << tr("Name") << tr("Value"));
m_ui->statisticsTableView->setModel(m_referencesModel.data());
+ m_ui->statisticsTableView->setSelectionMode(QAbstractItemView::NoSelection);
m_ui->statisticsTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
}
-DatabaseSettingsWidgetStatistics::~DatabaseSettingsWidgetStatistics()
+ReportsWidgetStatistics::~ReportsWidgetStatistics()
{
}
-void DatabaseSettingsWidgetStatistics::addStatsRow(QString name, QString value, bool bad, QString badMsg)
+void ReportsWidgetStatistics::addStatsRow(QString name, QString value, bool bad, QString badMsg)
{
auto row = QList<QStandardItem*>();
row << new QStandardItem(name);
@@ -170,7 +180,7 @@ void DatabaseSettingsWidgetStatistics::addStatsRow(QString name, QString value,
}
};
-void DatabaseSettingsWidgetStatistics::loadSettings(QSharedPointer<Database> db)
+void ReportsWidgetStatistics::loadSettings(QSharedPointer<Database> db)
{
m_db = std::move(db);
m_statsCalculated = false;
@@ -178,7 +188,7 @@ void DatabaseSettingsWidgetStatistics::loadSettings(QSharedPointer<Database> db)
addStatsRow(tr("Please wait, database statistics are being calculated..."), "");
}
-void DatabaseSettingsWidgetStatistics::showEvent(QShowEvent* event)
+void ReportsWidgetStatistics::showEvent(QShowEvent* event)
{
QWidget::showEvent(event);
@@ -189,9 +199,9 @@ void DatabaseSettingsWidgetStatistics::showEvent(QShowEvent* event)
}
}
-void DatabaseSettingsWidgetStatistics::calculateStats()
+void ReportsWidgetStatistics::calculateStats()
{
- const auto stats = AsyncTask::runAndWaitForFuture([this] { return new Stats(m_db); });
+ const QScopedPointer<Stats> stats(AsyncTask::runAndWaitForFuture([this] { return new Stats(m_db); }));
m_referencesModel->clear();
addStatsRow(tr("Database name"), m_db->metadata()->name());
@@ -231,7 +241,7 @@ void DatabaseSettingsWidgetStatistics::calculateStats()
tr("Average password length is less than ten characters. Longer passwords provide more security."));
}
-void DatabaseSettingsWidgetStatistics::saveSettings()
+void ReportsWidgetStatistics::saveSettings()
{
// nothing to do - the tab is passive
}
diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetStatistics.h b/src/gui/reports/ReportsWidgetStatistics.h
index 2bd42f13d..cc11a75f5 100644
--- a/src/gui/dbsettings/DatabaseSettingsWidgetStatistics.h
+++ b/src/gui/reports/ReportsWidgetStatistics.h
@@ -15,8 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef KEEPASSXC_DATABASESETTINGSWIDGETSTATISTICS_H
-#define KEEPASSXC_DATABASESETTINGSWIDGETSTATISTICS_H
+#ifndef KEEPASSXC_REPORTSWIDGETSTATISTICS_H
+#define KEEPASSXC_REPORTSWIDGETSTATISTICS_H
#include <QIcon>
#include <QWidget>
@@ -26,15 +26,15 @@ class QStandardItemModel;
namespace Ui
{
- class DatabaseSettingsWidgetStatistics;
+ class ReportsWidgetStatistics;
}
-class DatabaseSettingsWidgetStatistics : public QWidget
+class ReportsWidgetStatistics : public QWidget
{
Q_OBJECT
public:
- explicit DatabaseSettingsWidgetStatistics(QWidget* parent = nullptr);
- ~DatabaseSettingsWidgetStatistics();
+ explicit ReportsWidgetStatistics(QWidget* parent = nullptr);
+ ~ReportsWidgetStatistics();
void loadSettings(QSharedPointer<Database> db);
void saveSettings();
@@ -46,7 +46,7 @@ private slots:
void calculateStats();
private:
- QScopedPointer<Ui::DatabaseSettingsWidgetStatistics> m_ui;
+ QScopedPointer<Ui::ReportsWidgetStatistics> m_ui;
bool m_statsCalculated = false;
QIcon m_errIcon;
@@ -56,4 +56,4 @@ private:
void addStatsRow(QString name, QString value, bool bad = false, QString badMsg = "");
};
-#endif // KEEPASSXC_DATABASESETTINGSWIDGETSTATISTICS_H
+#endif // KEEPASSXC_REPORTSWIDGETSTATISTICS_H
diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetStatistics.ui b/src/gui/reports/ReportsWidgetStatistics.ui
index ed9d6346e..1f3bf5fea 100644
--- a/src/gui/dbsettings/DatabaseSettingsWidgetStatistics.ui
+++ b/src/gui/reports/ReportsWidgetStatistics.ui
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>DatabaseSettingsWidgetStatistics</class>
- <widget class="QWidget" name="DatabaseSettingsWidgetStatistics">
+ <class>ReportsWidgetStatistics</class>
+ <widget class="QWidget" name="ReportsWidgetStatistics">
<property name="geometry">
<rect>
<x>0</x>