mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-28 16:59:44 -05:00
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
This commit is contained in:
parent
71a39c37ec
commit
a81c6469a8
BIN
share/demo.kdbx
BIN
share/demo.kdbx
Binary file not shown.
1
share/icons/application/scalable/actions/health.svg
Normal file
1
share/icons/application/scalable/actions/health.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="mdi-heart-pulse" width="24" height="24" viewBox="0 0 24 24"><path d="M7.5,4A5.5,5.5 0 0,0 2,9.5C2,10 2.09,10.5 2.22,11H6.3L7.57,7.63C7.87,6.83 9.05,6.75 9.43,7.63L11.5,13L12.09,11.58C12.22,11.25 12.57,11 13,11H21.78C21.91,10.5 22,10 22,9.5A5.5,5.5 0 0,0 16.5,4C14.64,4 13,4.93 12,6.34C11,4.93 9.36,4 7.5,4V4M3,12.5A1,1 0 0,0 2,13.5A1,1 0 0,0 3,14.5H5.44L11,20C12,20.9 12,20.9 13,20L18.56,14.5H21A1,1 0 0,0 22,13.5A1,1 0 0,0 21,12.5H13.4L12.47,14.8C12.07,15.81 10.92,15.67 10.55,14.83L8.5,9.5L7.54,11.83C7.39,12.21 7.05,12.5 6.6,12.5H3Z" /></svg>
|
After Width: | Height: | Size: 782 B |
@ -48,6 +48,7 @@ set(keepassx_SOURCES
|
||||
core/Merger.cpp
|
||||
core/Metadata.cpp
|
||||
core/PasswordGenerator.cpp
|
||||
core/PasswordHealth.cpp
|
||||
core/PassphraseGenerator.cpp
|
||||
core/SignalMultiplexer.cpp
|
||||
core/ScreenLockListener.cpp
|
||||
@ -149,8 +150,12 @@ set(keepassx_SOURCES
|
||||
gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.cpp
|
||||
gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp
|
||||
gui/dbsettings/DatabaseSettingsWidgetMasterKey.cpp
|
||||
gui/dbsettings/DatabaseSettingsWidgetStatistics.cpp
|
||||
gui/dbsettings/DatabaseSettingsPageStatistics.cpp
|
||||
gui/reports/ReportsWidget.cpp
|
||||
gui/reports/ReportsDialog.cpp
|
||||
gui/reports/ReportsWidgetHealthcheck.cpp
|
||||
gui/reports/ReportsPageHealthcheck.cpp
|
||||
gui/reports/ReportsWidgetStatistics.cpp
|
||||
gui/reports/ReportsPageStatistics.cpp
|
||||
gui/settings/SettingsWidget.cpp
|
||||
gui/widgets/ElidedLabel.cpp
|
||||
gui/widgets/PopupHelpWidget.cpp
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "BrowserSettings.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/PasswordHealth.h"
|
||||
|
||||
BrowserSettings* BrowserSettings::m_instance(nullptr);
|
||||
|
||||
@ -541,7 +542,7 @@ QJsonObject BrowserSettings::generatePassword()
|
||||
m_passwordGenerator.setCharClasses(passwordCharClasses());
|
||||
m_passwordGenerator.setFlags(passwordGeneratorFlags());
|
||||
const QString pw = m_passwordGenerator.generatePassword();
|
||||
password["entropy"] = m_passwordGenerator.estimateEntropy(pw);
|
||||
password["entropy"] = PasswordHealth(pw).entropy();
|
||||
password["password"] = pw;
|
||||
} else {
|
||||
m_passPhraseGenerator.setWordCount(passPhraseWordCount());
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "cli/Utils.h"
|
||||
|
||||
#include "cli/TextStream.h"
|
||||
#include "core/PasswordHealth.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -49,10 +50,9 @@ static void estimate(const char* pwd, bool advanced)
|
||||
{
|
||||
TextStream out(Utils::STDOUT, QIODevice::WriteOnly);
|
||||
|
||||
double e = 0.0;
|
||||
int len = static_cast<int>(strlen(pwd));
|
||||
if (!advanced) {
|
||||
e = ZxcvbnMatch(pwd, nullptr, nullptr);
|
||||
const auto e = PasswordHealth(pwd).entropy();
|
||||
// clang-format off
|
||||
out << QObject::tr("Length %1").arg(len, 0) << '\t'
|
||||
<< QObject::tr("Entropy %1").arg(e, 0, 'f', 3) << '\t'
|
||||
@ -62,7 +62,7 @@ static void estimate(const char* pwd, bool advanced)
|
||||
int ChkLen = 0;
|
||||
ZxcMatch_t *info, *p;
|
||||
double m = 0.0;
|
||||
e = ZxcvbnMatch(pwd, nullptr, &info);
|
||||
const auto e = ZxcvbnMatch(pwd, nullptr, &info);
|
||||
for (p = info; p; p = p->Next) {
|
||||
m += p->Entrpy;
|
||||
}
|
||||
|
@ -19,7 +19,6 @@
|
||||
#include "PasswordGenerator.h"
|
||||
|
||||
#include "crypto/Random.h"
|
||||
#include <zxcvbn.h>
|
||||
|
||||
const char* PasswordGenerator::DefaultExcludedChars = "";
|
||||
|
||||
@ -31,11 +30,6 @@ PasswordGenerator::PasswordGenerator()
|
||||
{
|
||||
}
|
||||
|
||||
double PasswordGenerator::estimateEntropy(const QString& password)
|
||||
{
|
||||
return ZxcvbnMatch(password.toLatin1(), nullptr, nullptr);
|
||||
}
|
||||
|
||||
void PasswordGenerator::setLength(int length)
|
||||
{
|
||||
if (length <= 0) {
|
||||
|
@ -57,7 +57,6 @@ public:
|
||||
public:
|
||||
PasswordGenerator();
|
||||
|
||||
double estimateEntropy(const QString& password);
|
||||
void setLength(int length);
|
||||
void setCharClasses(const CharClasses& classes);
|
||||
void setFlags(const GeneratorFlags& flags);
|
||||
|
188
src/core/PasswordHealth.cpp
Normal file
188
src/core/PasswordHealth.cpp
Normal file
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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 <QApplication>
|
||||
#include <QString>
|
||||
|
||||
#include "Database.h"
|
||||
#include "Entry.h"
|
||||
#include "Group.h"
|
||||
#include "PasswordHealth.h"
|
||||
#include "zxcvbn.h"
|
||||
|
||||
PasswordHealth::PasswordHealth(double entropy)
|
||||
: m_score(entropy)
|
||||
, m_entropy(entropy)
|
||||
{
|
||||
switch (quality()) {
|
||||
case Quality::Bad:
|
||||
case Quality::Poor:
|
||||
m_scoreReasons << QApplication::tr("Very weak password");
|
||||
m_scoreDetails << QApplication::tr("Password entropy is %1 bits").arg(QString::number(m_entropy, 'f', 2));
|
||||
break;
|
||||
|
||||
case Quality::Weak:
|
||||
m_scoreReasons << QApplication::tr("Weak password");
|
||||
m_scoreDetails << QApplication::tr("Password entropy is %1 bits").arg(QString::number(m_entropy, 'f', 2));
|
||||
break;
|
||||
|
||||
default:
|
||||
// No reason or details for good and excellent passwords
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PasswordHealth::PasswordHealth(QString pwd)
|
||||
: PasswordHealth(ZxcvbnMatch(pwd.toLatin1(), nullptr, nullptr))
|
||||
{
|
||||
}
|
||||
|
||||
void PasswordHealth::setScore(int score)
|
||||
{
|
||||
m_score = score;
|
||||
}
|
||||
|
||||
void PasswordHealth::adjustScore(int amount)
|
||||
{
|
||||
m_score += amount;
|
||||
}
|
||||
|
||||
QString PasswordHealth::scoreReason() const
|
||||
{
|
||||
return m_scoreReasons.join("\n");
|
||||
}
|
||||
|
||||
void PasswordHealth::addScoreReason(QString reason)
|
||||
{
|
||||
m_scoreReasons << reason;
|
||||
}
|
||||
|
||||
QString PasswordHealth::scoreDetails() const
|
||||
{
|
||||
return m_scoreDetails.join("\n");
|
||||
}
|
||||
|
||||
void PasswordHealth::addScoreDetails(QString details)
|
||||
{
|
||||
m_scoreDetails.append(details);
|
||||
}
|
||||
|
||||
PasswordHealth::Quality PasswordHealth::quality() const
|
||||
{
|
||||
if (m_score <= 0) {
|
||||
return Quality::Bad;
|
||||
} else if (m_score < 40) {
|
||||
return Quality::Poor;
|
||||
} else if (m_score < 65) {
|
||||
return Quality::Weak;
|
||||
} else if (m_score < 100) {
|
||||
return Quality::Good;
|
||||
}
|
||||
return Quality::Excellent;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class provides additional information about password health
|
||||
* than can be derived from the password itself (re-use, expiry).
|
||||
*/
|
||||
HealthChecker::HealthChecker(QSharedPointer<Database> db)
|
||||
{
|
||||
// Build the cache of re-used passwords
|
||||
for (const auto* entry : db->rootGroup()->entriesRecursive()) {
|
||||
if (!entry->isRecycled()) {
|
||||
m_reuse[entry->password()]
|
||||
<< QApplication::tr("Used in %1/%2").arg(entry->group()->hierarchy().join('/'), entry->title());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call operator of the Health Checker class.
|
||||
*
|
||||
* Returns the health of the password in `entry`, considering
|
||||
* password entropy, re-use, expiration, etc.
|
||||
*/
|
||||
QSharedPointer<PasswordHealth> HealthChecker::evaluate(const Entry* entry)
|
||||
{
|
||||
if (!entry) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Return from cache if we saw it before
|
||||
if (m_cache.contains(entry->uuid())) {
|
||||
return m_cache[entry->uuid()];
|
||||
}
|
||||
|
||||
// First analyse the password itself
|
||||
const auto pwd = entry->password();
|
||||
auto health = QSharedPointer<PasswordHealth>(new PasswordHealth(pwd));
|
||||
|
||||
// Second, if the password is in the database more than once,
|
||||
// reduce the score accordingly
|
||||
const auto& used = m_reuse[pwd];
|
||||
const auto count = used.size();
|
||||
if (count > 1) {
|
||||
constexpr auto penalty = 15;
|
||||
health->adjustScore(-penalty * (count - 1));
|
||||
health->addScoreReason(QApplication::tr("Password is used %1 times").arg(QString::number(count)));
|
||||
// Add the first 20 uses of the password to prevent the details display from growing too large
|
||||
for (int i = 0; i < used.size(); ++i) {
|
||||
health->addScoreDetails(used[i]);
|
||||
if (i == 19) {
|
||||
health->addScoreDetails(QStringLiteral("..."));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't allow re-used passwords to be considered "good"
|
||||
// no matter how great their entropy is.
|
||||
if (health->score() > 64) {
|
||||
health->setScore(64);
|
||||
}
|
||||
}
|
||||
|
||||
// Third, if the password has already expired, reduce score to 0;
|
||||
// or, if the password is going to expire in the next 30 days,
|
||||
// reduce score by 2 points per day.
|
||||
if (entry->isExpired()) {
|
||||
health->setScore(0);
|
||||
health->addScoreReason(QApplication::tr("Password has expired"));
|
||||
health->addScoreDetails(QApplication::tr("Password expiry was %1")
|
||||
.arg(entry->timeInfo().expiryTime().toString(Qt::DefaultLocaleShortDate)));
|
||||
} else if (entry->timeInfo().expires()) {
|
||||
const auto days = QDateTime::currentDateTime().daysTo(entry->timeInfo().expiryTime());
|
||||
if (days <= 30) {
|
||||
// First bring the score down into the "weak" range
|
||||
// so that the entry appears in Health Check. Then
|
||||
// reduce the score by 2 points for every day that
|
||||
// we get closer to expiry. days<=0 has already
|
||||
// been handled above ("isExpired()").
|
||||
if (health->score() > 60) {
|
||||
health->setScore(60);
|
||||
}
|
||||
health->adjustScore((30 - days) * -2);
|
||||
health->addScoreReason(days <= 2 ? QApplication::tr("Password is about to expire")
|
||||
: days <= 10 ? QApplication::tr("Password expires in %1 days").arg(days)
|
||||
: QApplication::tr("Password will expire soon"));
|
||||
health->addScoreDetails(QApplication::tr("Password expires on %1")
|
||||
.arg(entry->timeInfo().expiryTime().toString(Qt::DefaultLocaleShortDate)));
|
||||
}
|
||||
}
|
||||
|
||||
// Return the result
|
||||
return m_cache.insert(entry->uuid(), health).value();
|
||||
}
|
113
src/core/PasswordHealth.h
Normal file
113
src/core/PasswordHealth.h
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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_PASSWORDHEALTH_H
|
||||
#define KEEPASSX_PASSWORDHEALTH_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QSharedPointer>
|
||||
#include <QStringList>
|
||||
|
||||
class Database;
|
||||
class Entry;
|
||||
|
||||
/**
|
||||
* Health status of a single password.
|
||||
*
|
||||
* @see HealthChecker
|
||||
*/
|
||||
class PasswordHealth
|
||||
{
|
||||
public:
|
||||
explicit PasswordHealth(double entropy);
|
||||
explicit PasswordHealth(QString pwd);
|
||||
|
||||
/*
|
||||
* The password score is defined to be the greater the better
|
||||
* (more secure) the password is. It doesn't have a dimension,
|
||||
* there are no defined maximum or minimum values, and score
|
||||
* values may change with different versions of the software.
|
||||
*/
|
||||
int score() const
|
||||
{
|
||||
return m_score;
|
||||
}
|
||||
|
||||
void setScore(int score);
|
||||
void adjustScore(int amount);
|
||||
|
||||
/*
|
||||
* A text description for the password's quality assessment
|
||||
* (translated into the application language), and additional
|
||||
* information. Empty if nothing is wrong with the password.
|
||||
* May contain more than line, separated by '\n'.
|
||||
*/
|
||||
QString scoreReason() const;
|
||||
void addScoreReason(QString reason);
|
||||
|
||||
QString scoreDetails() const;
|
||||
void addScoreDetails(QString details);
|
||||
|
||||
/*
|
||||
* The password quality assessment (based on the score).
|
||||
*/
|
||||
enum class Quality
|
||||
{
|
||||
Bad,
|
||||
Poor,
|
||||
Weak,
|
||||
Good,
|
||||
Excellent
|
||||
};
|
||||
Quality quality() const;
|
||||
|
||||
/*
|
||||
* The password's raw entropy value, in bits.
|
||||
*/
|
||||
double entropy() const
|
||||
{
|
||||
return m_entropy;
|
||||
}
|
||||
|
||||
private:
|
||||
int m_score = 0;
|
||||
double m_entropy = 0.0;
|
||||
QStringList m_scoreReasons;
|
||||
QStringList m_scoreDetails;
|
||||
};
|
||||
|
||||
/**
|
||||
* Password health check for all entries of a database.
|
||||
*
|
||||
* @see PasswordHealth
|
||||
*/
|
||||
class HealthChecker
|
||||
{
|
||||
public:
|
||||
explicit HealthChecker(QSharedPointer<Database>);
|
||||
|
||||
// Get the health status of an entry in the database
|
||||
QSharedPointer<PasswordHealth> evaluate(const Entry* entry);
|
||||
|
||||
private:
|
||||
// Result cache (first=entry UUID)
|
||||
QHash<QUuid, QSharedPointer<PasswordHealth>> m_cache;
|
||||
// first = password, second = entries that use it
|
||||
QHash<QString, QStringList> m_reuse;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_PASSWORDHEALTH_H
|
@ -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>
|
||||
|
@ -457,6 +457,11 @@ void DatabaseTabWidget::changeMasterKey()
|
||||
currentDatabaseWidget()->switchToMasterKeyChange();
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::changeReports()
|
||||
{
|
||||
currentDatabaseWidget()->switchToReports();
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::changeDatabaseSettings()
|
||||
{
|
||||
currentDatabaseWidget()->switchToDatabaseSettings();
|
||||
|
@ -78,6 +78,7 @@ public slots:
|
||||
void relockPendingDatabase();
|
||||
|
||||
void changeMasterKey();
|
||||
void changeReports();
|
||||
void changeDatabaseSettings();
|
||||
void performGlobalAutoType();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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 &key...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionReports">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
128
src/gui/reports/ReportsDialog.cpp
Normal file
128
src/gui/reports/ReportsDialog.cpp
Normal file
@ -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();
|
||||
}
|
||||
}
|
85
src/gui/reports/ReportsDialog.h
Normal file
85
src/gui/reports/ReportsDialog.h
Normal file
@ -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
|
43
src/gui/reports/ReportsDialog.ui
Normal file
43
src/gui/reports/ReportsDialog.ui
Normal file
@ -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>
|
55
src/gui/reports/ReportsPageHealthcheck.cpp
Normal file
55
src/gui/reports/ReportsPageHealthcheck.cpp
Normal file
@ -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();
|
||||
}
|
41
src/gui/reports/ReportsPageHealthcheck.h
Normal file
41
src/gui/reports/ReportsPageHealthcheck.h
Normal file
@ -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
|
@ -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();
|
||||
}
|
@ -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
|
44
src/gui/reports/ReportsWidget.cpp
Normal file
44
src/gui/reports/ReportsWidget.cpp
Normal file
@ -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;
|
||||
}
|
53
src/gui/reports/ReportsWidget.h
Normal file
53
src/gui/reports/ReportsWidget.h
Normal file
@ -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
|
237
src/gui/reports/ReportsWidgetHealthcheck.cpp
Normal file
237
src/gui/reports/ReportsWidgetHealthcheck.cpp
Normal file
@ -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
|
||||
}
|
70
src/gui/reports/ReportsWidgetHealthcheck.h
Normal file
70
src/gui/reports/ReportsWidgetHealthcheck.h
Normal file
@ -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
|
79
src/gui/reports/ReportsWidgetHealthcheck.ui
Normal file
79
src/gui/reports/ReportsWidgetHealthcheck.ui
Normal file
@ -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>
|
@ -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
|
||||
}
|
@ -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
|
@ -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>
|
@ -176,6 +176,9 @@ add_unit_test(NAME testmerge SOURCES TestMerge.cpp
|
||||
add_unit_test(NAME testpasswordgenerator SOURCES TestPasswordGenerator.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testpasswordhealth SOURCES TestPasswordHealth.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testpassphrasegenerator SOURCES TestPassphraseGenerator.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
|
65
tests/TestPasswordHealth.cpp
Normal file
65
tests/TestPasswordHealth.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 "TestPasswordHealth.h"
|
||||
#include "TestGlobal.h"
|
||||
|
||||
#include "core/PasswordHealth.h"
|
||||
|
||||
QTEST_GUILESS_MAIN(TestPasswordHealth)
|
||||
|
||||
void TestPasswordHealth::initTestCase()
|
||||
{
|
||||
}
|
||||
|
||||
void TestPasswordHealth::testNoDb()
|
||||
{
|
||||
const auto empty = PasswordHealth("");
|
||||
QCOMPARE(empty.score(), 0);
|
||||
QCOMPARE(empty.entropy(), 0.0);
|
||||
QCOMPARE(empty.quality(), PasswordHealth::Quality::Bad);
|
||||
QVERIFY(!empty.scoreReason().isEmpty());
|
||||
QVERIFY(!empty.scoreDetails().isEmpty());
|
||||
|
||||
const auto poor = PasswordHealth("secret");
|
||||
QCOMPARE(poor.score(), 6);
|
||||
QCOMPARE(int(poor.entropy()), 6);
|
||||
QCOMPARE(poor.quality(), PasswordHealth::Quality::Poor);
|
||||
QVERIFY(!poor.scoreReason().isEmpty());
|
||||
QVERIFY(!poor.scoreDetails().isEmpty());
|
||||
|
||||
const auto weak = PasswordHealth("Yohb2ChR4");
|
||||
QCOMPARE(weak.score(), 47);
|
||||
QCOMPARE(int(weak.entropy()), 47);
|
||||
QCOMPARE(weak.quality(), PasswordHealth::Quality::Weak);
|
||||
QVERIFY(!weak.scoreReason().isEmpty());
|
||||
QVERIFY(!weak.scoreDetails().isEmpty());
|
||||
|
||||
const auto good = PasswordHealth("MIhIN9UKrgtPL2hp");
|
||||
QCOMPARE(good.score(), 78);
|
||||
QCOMPARE(int(good.entropy()), 78);
|
||||
QCOMPARE(good.quality(), PasswordHealth::Quality::Good);
|
||||
QVERIFY(good.scoreReason().isEmpty());
|
||||
QVERIFY(good.scoreDetails().isEmpty());
|
||||
|
||||
const auto excellent = PasswordHealth("prompter-ream-oversleep-step-extortion-quarrel-reflected-prefix");
|
||||
QCOMPARE(excellent.score(), 164);
|
||||
QCOMPARE(int(excellent.entropy()), 164);
|
||||
QCOMPARE(excellent.quality(), PasswordHealth::Quality::Excellent);
|
||||
QVERIFY(excellent.scoreReason().isEmpty());
|
||||
QVERIFY(excellent.scoreDetails().isEmpty());
|
||||
}
|
32
tests/TestPasswordHealth.h
Normal file
32
tests/TestPasswordHealth.h
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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_TESTPASSWORDHEALTH_H
|
||||
#define KEEPASSX_TESTPASSWORDHEALTH_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class TestPasswordHealth : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void testNoDb();
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TESTPASSWORDHEALTH_H
|
@ -99,6 +99,7 @@ map() {
|
||||
group-edit) echo folder-edit-outline ;;
|
||||
group-empty-trash) echo trash-can-outline ;;
|
||||
group-new) echo folder-plus-outline ;;
|
||||
health) echo heart-pulse ;;
|
||||
help-about) echo information-outline ;;
|
||||
internet-web-browser) echo web ;;
|
||||
key-enter) echo keyboard-variant ;;
|
||||
|
Loading…
Reference in New Issue
Block a user