Skip to content

Commit

Permalink
Add db statistic output to CLI db-info command.
Browse files Browse the repository at this point in the history
Closes #6920
  • Loading branch information
glysbaysb authored and droidmonkey committed Dec 9, 2021
1 parent 6c4a82b commit 77c62a8
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 126 deletions.
68 changes: 68 additions & 0 deletions share/translations/keepassxc_en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7561,6 +7561,74 @@ Please consider generating a new key file.</source>
<source>Use custom character set</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Location</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Database created</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Last saved</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unsaved changes</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>yes</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>no</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Number of groups</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Number of entries</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Number of expired entries</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unique passwords</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Non-unique passwords</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Maximum password reuse</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Number of short passwords</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Number of weak passwords</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Entries excluded from reports</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Average password length</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>%1 characters</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QtIOCompressor</name>
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ set(keepassx_SOURCES
core/Config.cpp
core/CustomData.cpp
core/Database.cpp
core/DatabaseStats.cpp
core/Entry.cpp
core/EntryAttachments.cpp
core/EntryAttributes.cpp
Expand Down
22 changes: 22 additions & 0 deletions src/cli/Info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
#include "Info.h"

#include "Utils.h"
#include "core/DatabaseStats.h"
#include "core/Global.h"
#include "core/Group.h"
#include "core/Metadata.h"

#include <QCommandLineParser>
Expand Down Expand Up @@ -47,5 +49,25 @@ int Info::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
} else {
out << QObject::tr("Recycle bin is not enabled.") << endl;
}

DatabaseStats stats(database);
out << QObject::tr("Location") << ": " << database->filePath() << endl;
out << QObject::tr("Database created") << ": "
<< database->rootGroup()->timeInfo().creationTime().toString(Qt::DefaultLocaleShortDate) << endl;
out << QObject::tr("Last saved") << ": " << stats.modified.toString(Qt::DefaultLocaleShortDate) << endl;
out << QObject::tr("Unsaved changes") << ": " << (database->isModified() ? QObject::tr("yes") : QObject::tr("no"))
<< endl;
out << QObject::tr("Number of groups") << ": " << QString::number(stats.groupCount) << endl;
out << QObject::tr("Number of entries") << ": " << QString::number(stats.entryCount) << endl;
out << QObject::tr("Number of expired entries") << ": " << QString::number(stats.expiredEntries) << endl;
out << QObject::tr("Unique passwords") << ": " << QString::number(stats.uniquePasswords) << endl;
out << QObject::tr("Non-unique passwords") << ": " << QString::number(stats.reusedPasswords) << endl;
out << QObject::tr("Maximum password reuse") << ": " << QString::number(stats.maxPwdReuse()) << endl;
out << QObject::tr("Number of short passwords") << ": " << QString::number(stats.shortPasswords) << endl;
out << QObject::tr("Number of weak passwords") << ": " << QString::number(stats.weakPasswords) << endl;
out << QObject::tr("Entries excluded from reports") << ": " << QString::number(stats.excludedEntries) << endl;
out << QObject::tr("Average password length") << ": " << QObject::tr("%1 characters").arg(stats.averagePwdLength())
<< endl;

return EXIT_SUCCESS;
}
119 changes: 119 additions & 0 deletions src/core/DatabaseStats.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright (C) 2021 KeePassXC Team <[email protected]>
*
* 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 "DatabaseStats.h"

// Ctor does all the work
DatabaseStats::DatabaseStats(QSharedPointer<Database> db)
: modified(QFileInfo(db->filePath()).lastModified())
, m_db(db)
{
gatherStats(db->rootGroup()->groupsRecursive(true));
}

// Get average password length
int DatabaseStats::averagePwdLength() const
{
const auto passwords = uniquePasswords + reusedPasswords;
return passwords == 0 ? 0 : std::round(totalPasswordLength / double(passwords));
}

// Get max number of password reuse (=how many entries
// share the same password)
int DatabaseStats::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 DatabaseStats::isAnyExpired() const
{
return expiredEntries > 0;
}

bool DatabaseStats::areTooManyPwdsReused() const
{
return reusedPasswords > uniquePasswords / 10;
}

bool DatabaseStats::arePwdsReusedTooOften() const
{
return maxPwdReuse() > 3;
}

bool DatabaseStats::isAvgPwdTooShort() const
{
return averagePwdLength() < 10;
}

void DatabaseStats::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->isRecycled()) {
continue;
}

++groupCount;

for (const auto* entry : group->entries()) {
// Don't count anything in the recycle bin
if (entry->isRecycled()) {
continue;
}

++entryCount;

if (entry->isExpired()) {
++expiredEntries;
}

// Get password statistics
const auto pwd = entry->password();
if (!pwd.isEmpty()) {
if (!m_passwords.contains(pwd)) {
++uniquePasswords;
} else {
++reusedPasswords;
}

if (pwd.size() < PasswordHealth::Length::Short) {
++shortPasswords;
}

// Speed up Zxcvbn process by excluding very long passwords and most passphrases
if (pwd.size() < PasswordHealth::Length::Long
&& checker.evaluate(entry)->quality() <= PasswordHealth::Quality::Weak) {
++weakPasswords;
}

if (entry->excludeFromReports()) {
++excludedEntries;
}

totalPasswordLength += pwd.size();
m_passwords[pwd]++;
}
}
}
}
59 changes: 59 additions & 0 deletions src/core/DatabaseStats.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (C) 2021 KeePassXC Team <[email protected]>
*
* 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_DATABASESTATS_H
#define KEEPASSXC_DATABASESTATS_H
#include "PasswordHealth.h"
#include "core/Group.h"
#include <QFileInfo>
#include <cmath>
class DatabaseStats
{
public:
// The statistics we collect:
QDateTime modified; // File modification time
int groupCount = 0; // Number of groups in the database
int entryCount = 0; // Number of entries (across all groups)
int expiredEntries = 0; // Number of expired entries
int excludedEntries = 0; // Number of known bad entries
int weakPasswords = 0; // Number of weak or poor passwords
int shortPasswords = 0; // Number of passwords 8 characters or less in size
int uniquePasswords = 0; // Number of unique passwords
int reusedPasswords = 0; // Number of non-unique passwords
int totalPasswordLength = 0; // Total length of all passwords

explicit DatabaseStats(QSharedPointer<Database> db);

int averagePwdLength() const;

int maxPwdReuse() const;

bool isAnyExpired() const;

bool areTooManyPwdsReused() const;

bool arePwdsReusedTooOften() const;

bool isAvgPwdTooShort() const;

private:
QSharedPointer<Database> m_db;
QHash<QString, int> m_passwords;

void gatherStats(const QList<Group*>& groups);
};
#endif // KEEPASSXC_DATABASESTATS_H
6 changes: 6 additions & 0 deletions src/core/PasswordHealth.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ class PasswordHealth
return m_entropy;
}

struct Length
{
static const int Short = 8;
static const int Long = 25;
};

private:
int m_score = 0;
double m_entropy = 0.0;
Expand Down
Loading

0 comments on commit 77c62a8

Please sign in to comment.