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

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/reports/ReportsWidgetHealthcheck.cpp')
-rw-r--r--src/gui/reports/ReportsWidgetHealthcheck.cpp342
1 files changed, 342 insertions, 0 deletions
diff --git a/src/gui/reports/ReportsWidgetHealthcheck.cpp b/src/gui/reports/ReportsWidgetHealthcheck.cpp
new file mode 100644
index 000000000..5f502b16b
--- /dev/null
+++ b/src/gui/reports/ReportsWidgetHealthcheck.cpp
@@ -0,0 +1,342 @@
+/*
+ * 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/Global.h"
+#include "core/Group.h"
+#include "core/PasswordHealth.h"
+#include "core/Resources.h"
+#include "gui/styles/StateColorPalette.h"
+
+#include <QMenu>
+#include <QSharedPointer>
+#include <QSortFilterProxyModel>
+#include <QStandardItemModel>
+
+namespace
+{
+ class Health
+ {
+ public:
+ struct Item
+ {
+ QPointer<const Group> group;
+ QPointer<const Entry> entry;
+ QSharedPointer<PasswordHealth> health;
+ bool knownBad = false;
+
+ Item(const Group* g, const Entry* e, QSharedPointer<PasswordHealth> h)
+ : group(g)
+ , entry(e)
+ , health(h)
+ , knownBad(e->customData()->contains(PasswordHealth::OPTION_KNOWN_BAD)
+ && e->customData()->value(PasswordHealth::OPTION_KNOWN_BAD) == TRUE_STR)
+ {
+ }
+
+ 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;
+ }
+
+ bool anyKnownBad() const
+ {
+ return m_anyKnownBad;
+ }
+
+ private:
+ QSharedPointer<Database> m_db;
+ HealthChecker m_checker;
+ QList<QSharedPointer<Item>> m_items;
+ bool m_anyKnownBad = false;
+ };
+} // 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;
+ }
+
+ // Evaluate this entry
+ const auto item = QSharedPointer<Item>(new Item(group, entry, m_checker.evaluate(entry)));
+ if (item->knownBad) {
+ m_anyKnownBad = true;
+ }
+
+ // Add entry if its password isn't at least "good"
+ 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(Resources::instance()->icon("dialog-error"))
+ , m_referencesModel(new QStandardItemModel(this))
+ , m_modelProxy(new QSortFilterProxyModel(this))
+{
+ m_ui->setupUi(this);
+
+ m_modelProxy->setSourceModel(m_referencesModel.data());
+ m_ui->healthcheckTableView->setModel(m_modelProxy.data());
+ m_ui->healthcheckTableView->setSelectionMode(QAbstractItemView::NoSelection);
+ m_ui->healthcheckTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
+ m_ui->healthcheckTableView->setSortingEnabled(true);
+
+ connect(m_ui->healthcheckTableView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(customMenuRequested(QPoint)));
+ connect(m_ui->healthcheckTableView, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex)));
+ connect(m_ui->showKnownBadCheckBox, SIGNAL(stateChanged(int)), this, SLOT(calculateHealth()));
+}
+
+ReportsWidgetHealthcheck::~ReportsWidgetHealthcheck()
+{
+}
+
+void ReportsWidgetHealthcheck::addHealthRow(QSharedPointer<PasswordHealth> health,
+ const Group* group,
+ const Entry* entry,
+ bool knownBad)
+{
+ QString descr, tip;
+ QColor qualityColor;
+ StateColorPalette statePalette;
+ const auto quality = health->quality();
+ switch (quality) {
+ case PasswordHealth::Quality::Bad:
+ descr = tr("Bad", "Password quality");
+ tip = tr("Bad — password must be changed");
+ qualityColor = statePalette.color(StateColorPalette::HealthCritical);
+ break;
+
+ case PasswordHealth::Quality::Poor:
+ descr = tr("Poor", "Password quality");
+ tip = tr("Poor — password should be changed");
+ qualityColor = statePalette.color(StateColorPalette::HealthBad);
+ break;
+
+ case PasswordHealth::Quality::Weak:
+ descr = tr("Weak", "Password quality");
+ tip = tr("Weak — consider changing the password");
+ qualityColor = statePalette.color(StateColorPalette::HealthWeak);
+ break;
+
+ case PasswordHealth::Quality::Good:
+ case PasswordHealth::Quality::Excellent:
+ qualityColor = statePalette.color(StateColorPalette::HealthOk);
+ break;
+ }
+
+ auto title = entry->title();
+ if (knownBad) {
+ title.append(tr(" (Excluded)"));
+ }
+
+ auto row = QList<QStandardItem*>();
+ row << new QStandardItem(descr);
+ row << new QStandardItem(entry->iconPixmap(), 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);
+ if (knownBad) {
+ row[1]->setToolTip(tr("This entry is being excluded from reports"));
+ }
+ 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();
+
+ // Perform the health check
+ const QScopedPointer<Health> health(AsyncTask::runAndWaitForFuture([this] { return new Health(m_db); }));
+
+ // Display entries that are marked as "known bad"?
+ const auto showKnownBad = m_ui->showKnownBadCheckBox->isChecked();
+
+ // Display the entries
+ m_rowToEntry.clear();
+ for (const auto& item : health->items()) {
+ if (item->knownBad && !showKnownBad) {
+ // Exclude this entry from the report
+ continue;
+ }
+
+ // Show the entry in the report
+ addHealthRow(item->health, item->group, item->entry, item->knownBad);
+ }
+
+ // Set the table header
+ if (m_referencesModel->rowCount() == 0) {
+ m_referencesModel->setHorizontalHeaderLabels(QStringList() << tr("Congratulations, everything is healthy!"));
+ } else {
+ m_referencesModel->setHorizontalHeaderLabels(QStringList() << tr("") << tr("Title") << tr("Path") << tr("Score")
+ << tr("Reason"));
+ }
+
+ m_ui->healthcheckTableView->resizeRowsToContents();
+
+ // Show the "show known bad entries" checkbox if there's any known
+ // bad entry in the database.
+ if (health->anyKnownBad()) {
+ m_ui->showKnownBadCheckBox->show();
+ } else {
+ m_ui->showKnownBadCheckBox->hide();
+ }
+}
+
+void ReportsWidgetHealthcheck::emitEntryActivated(const QModelIndex& index)
+{
+ if (!index.isValid()) {
+ return;
+ }
+
+ auto mappedIndex = m_modelProxy->mapToSource(index);
+ const auto row = m_rowToEntry[mappedIndex.row()];
+ const auto group = row.first;
+ const auto entry = row.second;
+ if (group && entry) {
+ emit entryActivated(const_cast<Entry*>(entry));
+ }
+}
+
+void ReportsWidgetHealthcheck::customMenuRequested(QPoint pos)
+{
+
+ // Find which entry has been clicked
+ const auto index = m_ui->healthcheckTableView->indexAt(pos);
+ if (!index.isValid()) {
+ return;
+ }
+ auto mappedIndex = m_modelProxy->mapToSource(index);
+ m_contextmenuEntry = const_cast<Entry*>(m_rowToEntry[mappedIndex.row()].second);
+ if (!m_contextmenuEntry) {
+ return;
+ }
+
+ // Create the context menu
+ const auto menu = new QMenu(this);
+
+ // Create the "edit entry" menu item
+ const auto edit = new QAction(Resources::instance()->icon("entry-edit"), tr("Edit Entry..."), this);
+ menu->addAction(edit);
+ connect(edit, SIGNAL(triggered()), SLOT(editFromContextmenu()));
+
+ // Create the "exclude from reports" menu item
+ const auto knownbad = new QAction(Resources::instance()->icon("reports-exclude"), tr("Exclude from reports"), this);
+ knownbad->setCheckable(true);
+ knownbad->setChecked(m_contextmenuEntry->customData()->contains(PasswordHealth::OPTION_KNOWN_BAD)
+ && m_contextmenuEntry->customData()->value(PasswordHealth::OPTION_KNOWN_BAD) == TRUE_STR);
+ menu->addAction(knownbad);
+ connect(knownbad, SIGNAL(toggled(bool)), SLOT(toggleKnownBad(bool)));
+
+ // Show the context menu
+ menu->popup(m_ui->healthcheckTableView->viewport()->mapToGlobal(pos));
+}
+
+void ReportsWidgetHealthcheck::editFromContextmenu()
+{
+ if (m_contextmenuEntry) {
+ emit entryActivated(m_contextmenuEntry);
+ }
+}
+
+void ReportsWidgetHealthcheck::toggleKnownBad(bool isKnownBad)
+{
+ if (!m_contextmenuEntry) {
+ return;
+ }
+
+ m_contextmenuEntry->customData()->set(PasswordHealth::OPTION_KNOWN_BAD, isKnownBad ? TRUE_STR : FALSE_STR);
+
+ calculateHealth();
+}
+
+void ReportsWidgetHealthcheck::saveSettings()
+{
+ // nothing to do - the tab is passive
+}