diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d1e89ffdf..54222ca656 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - CLI: Add group commands (mv, mkdir and rmdir) [#3313]. - CLI: Add interactive shell mode command `open` [#3224](https://github.com/keepassxreboot/keepassxc/issues/3224) - Add "Paper Backup" aka "Export to HTML file" to the "Database" menu [#3277](https://github.com/keepassxreboot/keepassxc/pull/3277) +- Add statistics panel with information about the database (number of entries, number of unique passwords, etc.) to the Database Settings dialog [#2034](https://github.com/keepassxreboot/keepassxc/issues/2034) ### Changed diff --git a/COPYING b/COPYING index a75f6c69d0..b7ba1abe4a 100644 --- a/COPYING +++ b/COPYING @@ -245,3 +245,6 @@ License: MIT Files: share/icons/application/scalable/apps/freedesktop.svg Copyright: GPL-2+ Comment: from Freedesktop.org website + +Files: share/icons/application/32x32/actions/statistics.png +Copyright: Icon made by Freepik from https://www.flaticon.com/free-icon/bars-chart_265733 diff --git a/share/icons/application/32x32/actions/statistics.png b/share/icons/application/32x32/actions/statistics.png new file mode 100644 index 0000000000..d5b67d0e5c Binary files /dev/null and b/share/icons/application/32x32/actions/statistics.png differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e3565de443..acc94785d1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -148,6 +148,8 @@ set(keepassx_SOURCES gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.cpp gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp gui/dbsettings/DatabaseSettingsWidgetMasterKey.cpp + gui/dbsettings/DatabaseSettingsWidgetStatistics.cpp + gui/dbsettings/DatabaseSettingsPageStatistics.cpp gui/settings/SettingsWidget.cpp gui/widgets/ElidedLabel.cpp gui/widgets/PopupHelpWidget.cpp diff --git a/src/core/Bootstrap.cpp b/src/core/Bootstrap.cpp index 204942f4de..2d1a3e0878 100644 --- a/src/core/Bootstrap.cpp +++ b/src/core/Bootstrap.cpp @@ -257,7 +257,7 @@ namespace Bootstrap nullptr, // do not change owner or group pACL, // DACL specified nullptr // do not change SACL - ); + ); Cleanup: diff --git a/src/gui/dbsettings/DatabaseSettingsDialog.cpp b/src/gui/dbsettings/DatabaseSettingsDialog.cpp index e0e6765a46..33c4df2c4d 100644 --- a/src/gui/dbsettings/DatabaseSettingsDialog.cpp +++ b/src/gui/dbsettings/DatabaseSettingsDialog.cpp @@ -19,6 +19,7 @@ #include "DatabaseSettingsDialog.h" #include "ui_DatabaseSettingsDialog.h" +#include "DatabaseSettingsPageStatistics.h" #include "DatabaseSettingsWidgetEncryption.h" #include "DatabaseSettingsWidgetGeneral.h" #include "DatabaseSettingsWidgetMasterKey.h" @@ -84,6 +85,8 @@ 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/dbsettings/DatabaseSettingsPageStatistics.cpp b/src/gui/dbsettings/DatabaseSettingsPageStatistics.cpp new file mode 100644 index 0000000000..6fe24ff0f3 --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsPageStatistics.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * 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 . + */ + +#include "DatabaseSettingsPageStatistics.h" + +#include "DatabaseSettingsWidgetStatistics.h" +#include "core/Database.h" +#include "core/FilePath.h" +#include "core/Group.h" + +#include + +QString DatabaseSettingsPageStatistics::name() +{ + return QApplication::tr("Statistics"); +} + +QIcon DatabaseSettingsPageStatistics::icon() +{ + return FilePath::instance()->icon("actions", "statistics"); +} + +QWidget* DatabaseSettingsPageStatistics::createWidget() +{ + return new DatabaseSettingsWidgetStatistics(); +} + +void DatabaseSettingsPageStatistics::loadSettings(QWidget* widget, QSharedPointer db) +{ + DatabaseSettingsWidgetStatistics* settingsWidget = reinterpret_cast(widget); + settingsWidget->loadSettings(db); +} + +void DatabaseSettingsPageStatistics::saveSettings(QWidget* widget) +{ + DatabaseSettingsWidgetStatistics* settingsWidget = reinterpret_cast(widget); + settingsWidget->saveSettings(); +} diff --git a/src/gui/dbsettings/DatabaseSettingsPageStatistics.h b/src/gui/dbsettings/DatabaseSettingsPageStatistics.h new file mode 100644 index 0000000000..c890f3b81c --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsPageStatistics.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * 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 . + */ + +#ifndef KEEPASSXC_DATABASESETTINGSPAGESTATISTICS_H +#define KEEPASSXC_DATABASESETTINGSPAGESTATISTICS_H + +#include + +#include "DatabaseSettingsDialog.h" + +class DatabaseSettingsPageStatistics : public IDatabaseSettingsPage +{ +public: + QString name() override; + QIcon icon() override; + QWidget* createWidget() override; + void loadSettings(QWidget* widget, QSharedPointer db) override; + void saveSettings(QWidget* widget) override; +}; + +#endif // KEEPASSXC_DATABASESETTINGSPAGESTATISTICS_H diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp b/src/gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp index 4ea30c1f6f..fac85c21e6 100644 --- a/src/gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp +++ b/src/gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp @@ -242,12 +242,12 @@ void DatabaseSettingsWidgetBrowser::convertAttributesToCustomData() { if (MessageBox::Yes != MessageBox::question( - this, - tr("Move KeePassHTTP attributes to custom data"), - tr("Do you really want to move all legacy browser integration data to the latest standard?\n" - "This is necessary to maintain compatibility with the browser plugin."), - MessageBox::Yes | MessageBox::Cancel, - MessageBox::Cancel)) { + this, + tr("Move KeePassHTTP attributes to custom data"), + tr("Do you really want to move all legacy browser integration data to the latest standard?\n" + "This is necessary to maintain compatibility with the browser plugin."), + MessageBox::Yes | MessageBox::Cancel, + MessageBox::Cancel)) { return; } diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetStatistics.cpp b/src/gui/dbsettings/DatabaseSettingsWidgetStatistics.cpp new file mode 100644 index 0000000000..307f6c45e5 --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsWidgetStatistics.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * 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 . + */ + +#include "DatabaseSettingsWidgetStatistics.h" +#include "ui_DatabaseSettingsWidgetStatistics.h" + +#include "core/Database.h" +#include "core/FilePath.h" +#include "core/Group.h" +#include "core/Metadata.h" +#include "zxcvbn/zxcvbn.h" + +#include +#include +#include + +namespace +{ + class Stats + { + public: + // The statistics we collect: + QDateTime modified; // File modification time + int nGroups = 0; // Number of groups in the database + int nEntries = 0; // Number of entries (across all groups) + int nExpired = 0; // Number of expired entries + int nPwdsWeak = 0; // Number of weak or poor passwords + int nPwdsShort = 0; // Number of passwords 8 characters or less in size + int nPwdsUnique = 0; // Number of unique passwords + int nPwdsReused = 0; // Number of non-unique passwords + int pwdTotalLen = 0; // Total length of all passwords + + // Ctor does all the work + explicit Stats(QSharedPointer db) + : modified(QFileInfo(db->filePath()).lastModified()) + { + gatherStats(db->rootGroup()->groupsRecursive(true)); + } + + // Get average password length + int averagePwdLength() const + { + return m_passwords.empty() ? 0 : pwdTotalLen / m_passwords.size(); + } + + // Get max number of password reuse (=how many entries + // share the same password) + int maxPwdReuse() const + { + int ret = 0; + for (const auto& count : m_passwords) { + ret = std::max(ret, count); + } + return ret; + } + + // A warning sign is displayed if one of the + // following returns true. + bool isAnyExpired() const + { + return nExpired > 0; + } + + bool areTooManyPwdsReused() const + { + return nPwdsReused > nPwdsUnique / 10; + } + + bool arePwdsReusedTooOften() const + { + return maxPwdReuse() > 3; + } + + bool isAvgPwdTooShort() const + { + return averagePwdLength() < 10; + } + + private: + QHash m_passwords; + + void gatherStats(const QList& groups) + { + for (const auto* group : groups) { + // Don't count anything in the recycle bin + if (group == group->database()->metadata()->recycleBin()) { + continue; + } + + ++nGroups; + + for (const auto* entry : group->entries()) { + ++nEntries; + + if (entry->isExpired()) { + ++nExpired; + } + + // Get password statistics + const auto pwd = entry->password(); + if (!pwd.isEmpty()) { + if (!m_passwords.contains(pwd)) { + ++nPwdsUnique; + } else { + ++nPwdsReused; + } + + if (pwd.size() < 8) { + ++nPwdsShort; + } + + if (ZxcvbnMatch(pwd.toLatin1(), nullptr, nullptr) < 65) { + ++nPwdsWeak; + } + + pwdTotalLen += pwd.size(); + m_passwords[pwd]++; + } + } + } + } + }; +} // namespace + +DatabaseSettingsWidgetStatistics::DatabaseSettingsWidgetStatistics(QWidget* parent) + : QWidget(parent) + , m_ui(new Ui::DatabaseSettingsWidgetStatistics()) + , m_errIcon(FilePath::instance()->icon("status", "dialog-error")) +{ + m_ui->setupUi(this); +} + +DatabaseSettingsWidgetStatistics::~DatabaseSettingsWidgetStatistics() +{ +} + +void DatabaseSettingsWidgetStatistics::addStatsRow(QString name, QString value, bool bad, QString badMsg) +{ + auto row = QList(); + row << new QStandardItem(name); + row << new QStandardItem(value); + m_referencesModel->appendRow(row); + + if (bad) { + m_referencesModel->item(m_referencesModel->rowCount() - 1, 1)->setIcon(m_errIcon); + if (!badMsg.isEmpty()) { + m_referencesModel->item(m_referencesModel->rowCount() - 1, 1)->setToolTip(badMsg); + } + } +}; + +void DatabaseSettingsWidgetStatistics::loadSettings(QSharedPointer db) +{ + m_referencesModel.reset(new QStandardItemModel()); + m_referencesModel->setHorizontalHeaderLabels(QStringList() << tr("Name") << tr("Value")); + + const auto stats = Stats(db); + addStatsRow(tr("Database name"), db->metadata()->name()); + addStatsRow(tr("Description"), db->metadata()->description()); + addStatsRow(tr("Location"), db->filePath()); + addStatsRow(tr("Last saved"), stats.modified.toString(Qt::DefaultLocaleShortDate)); + addStatsRow(tr("Unsaved changes"), + db->isModified() ? tr("yes") : tr("no"), + db->isModified(), + tr("The database was modified, but the changes have not yet been saved to disk.")); + addStatsRow(tr("Number of groups"), QString::number(stats.nGroups)); + addStatsRow(tr("Number of entries"), QString::number(stats.nEntries)); + addStatsRow(tr("Number of expired entries"), + QString::number(stats.nExpired), + stats.isAnyExpired(), + tr("The database contains entries that have expired.")); + addStatsRow(tr("Unique passwords"), QString::number(stats.nPwdsUnique)); + addStatsRow(tr("Non-unique passwords"), + QString::number(stats.nPwdsReused), + stats.areTooManyPwdsReused(), + tr("More than 10% of passwords are reused. Use unique passwords when possible.")); + addStatsRow(tr("Maximum password reuse"), + QString::number(stats.maxPwdReuse()), + stats.arePwdsReusedTooOften(), + tr("Some passwords are used more than three times. Use unique passwords when possible.")); + addStatsRow(tr("Number of short passwords"), + QString::number(stats.nPwdsShort), + stats.nPwdsShort > 0, + tr("Recommended minimum password length is at least 8 characters.")); + addStatsRow(tr("Number of weak passwords"), + QString::number(stats.nPwdsWeak), + stats.nPwdsWeak > 0, + tr("Recommend using long, randomized passwords with a rating of 'good' or 'excellent'.")); + addStatsRow(tr("Average password length"), + tr("%1 characters").arg(stats.averagePwdLength()), + stats.isAvgPwdTooShort(), + tr("Average password length is less than ten characters. Longer passwords provide more security.")); + + m_ui->sharedGroupsView->setModel(m_referencesModel.data()); + m_ui->sharedGroupsView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); +} + +void DatabaseSettingsWidgetStatistics::saveSettings() +{ + // nothing to do - the tab is passive +} diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetStatistics.h b/src/gui/dbsettings/DatabaseSettingsWidgetStatistics.h new file mode 100644 index 0000000000..189df41ffd --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsWidgetStatistics.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2019 KeePassXC Team + * + * 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 . + */ + +#ifndef KEEPASSXC_DATABASESETTINGSWIDGETSTATISTICS_H +#define KEEPASSXC_DATABASESETTINGSWIDGETSTATISTICS_H + +#include +#include + +class Database; +class QStandardItemModel; + +namespace Ui +{ + class DatabaseSettingsWidgetStatistics; +} + +class DatabaseSettingsWidgetStatistics : public QWidget +{ + Q_OBJECT +public: + explicit DatabaseSettingsWidgetStatistics(QWidget* parent = nullptr); + ~DatabaseSettingsWidgetStatistics(); + + void loadSettings(QSharedPointer db); + void saveSettings(); + +private: + QScopedPointer m_ui; + + QIcon m_errIcon; + QScopedPointer m_referencesModel; + + void addStatsRow(QString name, QString value, bool bad = false, QString badMsg = ""); +}; + +#endif // KEEPASSXC_DATABASESETTINGSWIDGETSTATISTICS_H diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetStatistics.ui b/src/gui/dbsettings/DatabaseSettingsWidgetStatistics.ui new file mode 100644 index 0000000000..6eef5b366c --- /dev/null +++ b/src/gui/dbsettings/DatabaseSettingsWidgetStatistics.ui @@ -0,0 +1,79 @@ + + + DatabaseSettingsWidgetStatistics + + + + 0 + 0 + 327 + 379 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Statistics + + + + + + QAbstractItemView::NoEditTriggers + + + false + + + true + + + Qt::ElideMiddle + + + false + + + false + + + true + + + false + + + + + + + + true + + + + Hover over lines with error icons for further information. + + + + + + + + + + +