mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-02-22 15:39:57 -05:00
Add "Statistics" page to Database Settings dialog (#2034)
Added new page "Statistics" to the Database Settings dialog that shows information like number of groups and entries, number of unique and re-used passwords, average password length, etc. Show warnings for problematic values with explainations for the user in tooltips. Fixes #2034 Database statistics icon: Downloaded from: https://www.flaticon.com/authors/freepik Original source: https://www.flaticon.com/free-icon/bars-chart_265733
This commit is contained in:
parent
ca0c4f5a3d
commit
8afb1f17b4
@ -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
|
||||
|
||||
|
3
COPYING
3
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
|
||||
|
BIN
share/icons/application/32x32/actions/statistics.png
Normal file
BIN
share/icons/application/32x32/actions/statistics.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@ -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
|
||||
|
@ -257,7 +257,7 @@ namespace Bootstrap
|
||||
nullptr, // do not change owner or group
|
||||
pACL, // DACL specified
|
||||
nullptr // do not change SACL
|
||||
);
|
||||
);
|
||||
|
||||
Cleanup:
|
||||
|
||||
|
@ -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
|
||||
|
52
src/gui/dbsettings/DatabaseSettingsPageStatistics.cpp
Normal file
52
src/gui/dbsettings/DatabaseSettingsPageStatistics.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 "DatabaseSettingsPageStatistics.h"
|
||||
|
||||
#include "DatabaseSettingsWidgetStatistics.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/FilePath.h"
|
||||
#include "core/Group.h"
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
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<Database> db)
|
||||
{
|
||||
DatabaseSettingsWidgetStatistics* settingsWidget = reinterpret_cast<DatabaseSettingsWidgetStatistics*>(widget);
|
||||
settingsWidget->loadSettings(db);
|
||||
}
|
||||
|
||||
void DatabaseSettingsPageStatistics::saveSettings(QWidget* widget)
|
||||
{
|
||||
DatabaseSettingsWidgetStatistics* settingsWidget = reinterpret_cast<DatabaseSettingsWidgetStatistics*>(widget);
|
||||
settingsWidget->saveSettings();
|
||||
}
|
35
src/gui/dbsettings/DatabaseSettingsPageStatistics.h
Normal file
35
src/gui/dbsettings/DatabaseSettingsPageStatistics.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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_DATABASESETTINGSPAGESTATISTICS_H
|
||||
#define KEEPASSXC_DATABASESETTINGSPAGESTATISTICS_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "DatabaseSettingsDialog.h"
|
||||
|
||||
class DatabaseSettingsPageStatistics : public IDatabaseSettingsPage
|
||||
{
|
||||
public:
|
||||
QString name() override;
|
||||
QIcon icon() override;
|
||||
QWidget* createWidget() override;
|
||||
void loadSettings(QWidget* widget, QSharedPointer<Database> db) override;
|
||||
void saveSettings(QWidget* widget) override;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_DATABASESETTINGSPAGESTATISTICS_H
|
@ -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;
|
||||
}
|
||||
|
||||
|
215
src/gui/dbsettings/DatabaseSettingsWidgetStatistics.cpp
Normal file
215
src/gui/dbsettings/DatabaseSettingsWidgetStatistics.cpp
Normal file
@ -0,0 +1,215 @@
|
||||
/*
|
||||
* 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 "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 <QFileInfo>
|
||||
#include <QHash>
|
||||
#include <QStandardItemModel>
|
||||
|
||||
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<Database> 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<QString, int> m_passwords;
|
||||
|
||||
void gatherStats(const QList<Group*>& 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<QStandardItem*>();
|
||||
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<Database> 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
|
||||
}
|
51
src/gui/dbsettings/DatabaseSettingsWidgetStatistics.h
Normal file
51
src/gui/dbsettings/DatabaseSettingsWidgetStatistics.h
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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_DATABASESETTINGSWIDGETSTATISTICS_H
|
||||
#define KEEPASSXC_DATABASESETTINGSWIDGETSTATISTICS_H
|
||||
|
||||
#include <QIcon>
|
||||
#include <QWidget>
|
||||
|
||||
class Database;
|
||||
class QStandardItemModel;
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class DatabaseSettingsWidgetStatistics;
|
||||
}
|
||||
|
||||
class DatabaseSettingsWidgetStatistics : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DatabaseSettingsWidgetStatistics(QWidget* parent = nullptr);
|
||||
~DatabaseSettingsWidgetStatistics();
|
||||
|
||||
void loadSettings(QSharedPointer<Database> db);
|
||||
void saveSettings();
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::DatabaseSettingsWidgetStatistics> m_ui;
|
||||
|
||||
QIcon m_errIcon;
|
||||
QScopedPointer<QStandardItemModel> m_referencesModel;
|
||||
|
||||
void addStatsRow(QString name, QString value, bool bad = false, QString badMsg = "");
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_DATABASESETTINGSWIDGETSTATISTICS_H
|
79
src/gui/dbsettings/DatabaseSettingsWidgetStatistics.ui
Normal file
79
src/gui/dbsettings/DatabaseSettingsWidgetStatistics.ui
Normal file
@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DatabaseSettingsWidgetStatistics</class>
|
||||
<widget class="QWidget" name="DatabaseSettingsWidgetStatistics">
|
||||
<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="enableGroupBox">
|
||||
<property name="title">
|
||||
<string>Statistics</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QTableView" name="sharedGroupsView">
|
||||
<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>false</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Hover over lines with error icons for further information.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Loading…
x
Reference in New Issue
Block a user