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

ReportsWidgetStatistics.cpp « reports « gui « src - github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 400d82f29a2f4dd6bf6b391aaca3c57dd05ea665 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/*
 *  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 "ReportsWidgetStatistics.h"
#include "ui_ReportsWidgetStatistics.h"

#include "core/AsyncTask.h"
#include "core/Database.h"
#include "core/Global.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "core/PasswordHealth.h"
#include "core/Resources.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 nKnownBad = 0; // Number of known bad entries
        int pwdTotalLen = 0; // Total length of all passwords

        // Ctor does all the work
        explicit Stats(QSharedPointer<Database> db)
            : modified(QFileInfo(db->filePath()).lastModified())
            , m_db(db)
        {
            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:
        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->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()) {
                        ++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;
                        }

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

                        if (entry->customData()->contains(PasswordHealth::OPTION_KNOWN_BAD)
                            && entry->customData()->value(PasswordHealth::OPTION_KNOWN_BAD) == TRUE_STR) {
                            ++nKnownBad;
                        }

                        pwdTotalLen += pwd.size();
                        m_passwords[pwd]++;
                    }
                }
            }
        }
    };
} // namespace

ReportsWidgetStatistics::ReportsWidgetStatistics(QWidget* parent)
    : QWidget(parent)
    , m_ui(new Ui::ReportsWidgetStatistics())
    , m_errIcon(Resources::instance()->icon("dialog-error"))
{
    m_ui->setupUi(this);

    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);
}

ReportsWidgetStatistics::~ReportsWidgetStatistics()
{
}

void ReportsWidgetStatistics::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 ReportsWidgetStatistics::loadSettings(QSharedPointer<Database> db)
{
    m_db = std::move(db);
    m_statsCalculated = false;
    m_referencesModel->clear();
    addStatsRow(tr("Please wait, database statistics are being calculated..."), "");
}

void ReportsWidgetStatistics::showEvent(QShowEvent* event)
{
    QWidget::showEvent(event);

    if (!m_statsCalculated) {
        // Perform stats calculation on next event loop to allow widget to appear
        m_statsCalculated = true;
        QTimer::singleShot(0, this, SLOT(calculateStats()));
    }
}

void ReportsWidgetStatistics::calculateStats()
{
    const QScopedPointer<Stats> stats(AsyncTask::runAndWaitForFuture([this] { return new Stats(m_db); }));

    m_referencesModel->clear();
    addStatsRow(tr("Database name"), m_db->metadata()->name());
    addStatsRow(tr("Description"), m_db->metadata()->description());
    addStatsRow(tr("Location"), m_db->filePath());
    addStatsRow(tr("Last saved"), stats->modified.toString(Qt::DefaultLocaleShortDate));
    addStatsRow(tr("Unsaved changes"),
                m_db->isModified() ? tr("yes") : tr("no"),
                m_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("Entries excluded from reports"),
                QString::number(stats->nKnownBad),
                stats->nKnownBad > 0,
                tr("Excluding entries from reports, e. g. because they are known to have a poor password, isn't "
                   "necessarily a problem but you should keep an eye on them."));
    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."));
}

void ReportsWidgetStatistics::saveSettings()
{
    // nothing to do - the tab is passive
}