mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-04-05 05:27:39 +03:00
Reports: Add "Known Bad" flag for entries
* Fixes #4168 * Introduce a custom data element stored with an entry to indicate that it is a "Known Bad" entry. This flag causes database reports to skip these entries. * The current number of known bad entries is displayed in the statistics report. * Add context menu to reports to easily exclude entries.
This commit is contained in:
parent
ce8f32e797
commit
3c19fdd193
20 changed files with 620 additions and 279 deletions
2
COPYING
2
COPYING
|
@ -182,6 +182,8 @@ Files: share/icons/application/scalable/categories/preferences-other.svg
|
||||||
share/icons/application/scalable/actions/document-save-as.svg
|
share/icons/application/scalable/actions/document-save-as.svg
|
||||||
share/icons/application/scalable/actions/refresh.svg
|
share/icons/application/scalable/actions/refresh.svg
|
||||||
share/icons/application/scalable/actions/clipboard-text.svg
|
share/icons/application/scalable/actions/clipboard-text.svg
|
||||||
|
share/icons/application/scalable/actions/reports.svg
|
||||||
|
share/icons/application/scalable/actions/reports-exclude.svg
|
||||||
Copyright: 2019 Austin Andrews <http://templarian.com/>
|
Copyright: 2019 Austin Andrews <http://templarian.com/>
|
||||||
License: SIL OPEN FONT LICENSE Version 1.1
|
License: SIL OPEN FONT LICENSE Version 1.1
|
||||||
Comment: Taken from Material Design icon set (https://github.com/templarian/MaterialDesign/)
|
Comment: Taken from Material Design icon set (https://github.com/templarian/MaterialDesign/)
|
||||||
|
|
BIN
share/demo.kdbx
BIN
share/demo.kdbx
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="mdi-lightbulb-off-outline" width="24" height="24" viewBox="0 0 24 24"><path d="M12,2C9.76,2 7.78,3.05 6.5,4.68L7.93,6.11C8.84,4.84 10.32,4 12,4A5,5 0 0,1 17,9C17,10.68 16.16,12.16 14.89,13.06L16.31,14.5C17.94,13.21 19,11.24 19,9A7,7 0 0,0 12,2M3.28,4L2,5.27L5.04,8.3C5,8.53 5,8.76 5,9C5,11.38 6.19,13.47 8,14.74V17A1,1 0 0,0 9,18H14.73L18.73,22L20,20.72L3.28,4M7.23,10.5L12.73,16H10V13.58C8.68,13 7.66,11.88 7.23,10.5M9,20V21A1,1 0 0,0 10,22H14A1,1 0 0,0 15,21V20H9Z" /></svg>
|
After Width: | Height: | Size: 713 B |
1
share/icons/application/scalable/actions/reports.svg
Normal file
1
share/icons/application/scalable/actions/reports.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="mdi-lightbulb-on-outline" width="24" height="24" viewBox="0 0 24 24"><path d="M20,11H23V13H20V11M1,11H4V13H1V11M13,1V4H11V1H13M4.92,3.5L7.05,5.64L5.63,7.05L3.5,4.93L4.92,3.5M16.95,5.63L19.07,3.5L20.5,4.93L18.37,7.05L16.95,5.63M12,6A6,6 0 0,1 18,12C18,14.22 16.79,16.16 15,17.2V19A1,1 0 0,1 14,20H10A1,1 0 0,1 9,19V17.2C7.21,16.16 6,14.22 6,12A6,6 0 0,1 12,6M14,21V22A1,1 0 0,1 13,23H11A1,1 0 0,1 10,22V21H14M11,18H13V15.87C14.73,15.43 16,13.86 16,12A4,4 0 0,0 12,8A4,4 0 0,0 8,12C8,13.86 9.27,15.43 11,15.87V18Z" /></svg>
|
After Width: | Height: | Size: 758 B |
|
@ -51,6 +51,8 @@
|
||||||
<file>application/scalable/actions/password-show-off.svg</file>
|
<file>application/scalable/actions/password-show-off.svg</file>
|
||||||
<file>application/scalable/actions/password-show-on.svg</file>
|
<file>application/scalable/actions/password-show-on.svg</file>
|
||||||
<file>application/scalable/actions/refresh.svg</file>
|
<file>application/scalable/actions/refresh.svg</file>
|
||||||
|
<file>application/scalable/actions/reports.svg</file>
|
||||||
|
<file>application/scalable/actions/reports-exclude.svg</file>
|
||||||
<file>application/scalable/actions/sort-alphabetical-ascending.svg</file>
|
<file>application/scalable/actions/sort-alphabetical-ascending.svg</file>
|
||||||
<file>application/scalable/actions/sort-alphabetical-descending.svg</file>
|
<file>application/scalable/actions/sort-alphabetical-descending.svg</file>
|
||||||
<file>application/scalable/actions/statistics.svg</file>
|
<file>application/scalable/actions/statistics.svg</file>
|
||||||
|
|
|
@ -24,6 +24,9 @@
|
||||||
#include "PasswordHealth.h"
|
#include "PasswordHealth.h"
|
||||||
#include "zxcvbn.h"
|
#include "zxcvbn.h"
|
||||||
|
|
||||||
|
// Define the static member variable with the custom field name
|
||||||
|
const QString PasswordHealth::OPTION_KNOWN_BAD = QStringLiteral("KnownBad");
|
||||||
|
|
||||||
PasswordHealth::PasswordHealth(double entropy)
|
PasswordHealth::PasswordHealth(double entropy)
|
||||||
: m_score(entropy)
|
: m_score(entropy)
|
||||||
, m_entropy(entropy)
|
, m_entropy(entropy)
|
||||||
|
|
|
@ -83,6 +83,14 @@ public:
|
||||||
return m_entropy;
|
return m_entropy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of custom data field that holds the "this is a known
|
||||||
|
* bad password" flag. Legal values of the field are TRUE_STR
|
||||||
|
* and FALSE_STR, the default (used if the field doesn't exist)
|
||||||
|
* is false.
|
||||||
|
*/
|
||||||
|
static const QString OPTION_KNOWN_BAD;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_score = 0;
|
int m_score = 0;
|
||||||
double m_entropy = 0.0;
|
double m_entropy = 0.0;
|
||||||
|
|
|
@ -355,7 +355,7 @@ MainWindow::MainWindow()
|
||||||
m_ui->actionDatabaseSave->setIcon(resources()->icon("document-save"));
|
m_ui->actionDatabaseSave->setIcon(resources()->icon("document-save"));
|
||||||
m_ui->actionDatabaseSaveAs->setIcon(resources()->icon("document-save-as"));
|
m_ui->actionDatabaseSaveAs->setIcon(resources()->icon("document-save-as"));
|
||||||
m_ui->actionDatabaseClose->setIcon(resources()->icon("document-close"));
|
m_ui->actionDatabaseClose->setIcon(resources()->icon("document-close"));
|
||||||
m_ui->actionReports->setIcon(resources()->icon("help-about"));
|
m_ui->actionReports->setIcon(resources()->icon("reports"));
|
||||||
m_ui->actionChangeDatabaseSettings->setIcon(resources()->icon("document-edit"));
|
m_ui->actionChangeDatabaseSettings->setIcon(resources()->icon("document-edit"));
|
||||||
m_ui->actionChangeMasterKey->setIcon(resources()->icon("database-change-key"));
|
m_ui->actionChangeMasterKey->setIcon(resources()->icon("database-change-key"));
|
||||||
m_ui->actionLockDatabases->setIcon(resources()->icon("database-lock"));
|
m_ui->actionLockDatabases->setIcon(resources()->icon("database-lock"));
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
#include "core/Database.h"
|
#include "core/Database.h"
|
||||||
#include "core/Entry.h"
|
#include "core/Entry.h"
|
||||||
#include "core/Metadata.h"
|
#include "core/Metadata.h"
|
||||||
|
#include "core/PasswordHealth.h"
|
||||||
#include "core/Resources.h"
|
#include "core/Resources.h"
|
||||||
#include "core/TimeDelta.h"
|
#include "core/TimeDelta.h"
|
||||||
#include "core/Tools.h"
|
#include "core/Tools.h"
|
||||||
|
@ -423,6 +424,7 @@ void EditEntryWidget::setupEntryUpdate()
|
||||||
// Advanced tab
|
// Advanced tab
|
||||||
connect(m_advancedUi->attributesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
|
connect(m_advancedUi->attributesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
|
||||||
connect(m_advancedUi->protectAttributeButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
connect(m_advancedUi->protectAttributeButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
||||||
|
connect(m_advancedUi->knownBadCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
||||||
connect(m_advancedUi->fgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
connect(m_advancedUi->fgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
||||||
connect(m_advancedUi->bgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
connect(m_advancedUi->bgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
||||||
connect(m_advancedUi->attachmentsWidget, SIGNAL(widgetUpdated()), this, SLOT(setModified()));
|
connect(m_advancedUi->attachmentsWidget, SIGNAL(widgetUpdated()), this, SLOT(setModified()));
|
||||||
|
@ -827,6 +829,9 @@ void EditEntryWidget::setForms(Entry* entry, bool restore)
|
||||||
editTriggers = QAbstractItemView::DoubleClicked;
|
editTriggers = QAbstractItemView::DoubleClicked;
|
||||||
}
|
}
|
||||||
m_advancedUi->attributesView->setEditTriggers(editTriggers);
|
m_advancedUi->attributesView->setEditTriggers(editTriggers);
|
||||||
|
m_advancedUi->knownBadCheckBox->setChecked(entry->customData()->contains(PasswordHealth::OPTION_KNOWN_BAD)
|
||||||
|
&& entry->customData()->value(PasswordHealth::OPTION_KNOWN_BAD)
|
||||||
|
== TRUE_STR);
|
||||||
setupColorButton(true, entry->foregroundColor());
|
setupColorButton(true, entry->foregroundColor());
|
||||||
setupColorButton(false, entry->backgroundColor());
|
setupColorButton(false, entry->backgroundColor());
|
||||||
m_iconsWidget->setEnabled(!m_history);
|
m_iconsWidget->setEnabled(!m_history);
|
||||||
|
@ -1031,6 +1036,13 @@ void EditEntryWidget::updateEntryData(Entry* entry) const
|
||||||
|
|
||||||
entry->setNotes(m_mainUi->notesEdit->toPlainText());
|
entry->setNotes(m_mainUi->notesEdit->toPlainText());
|
||||||
|
|
||||||
|
const auto wasKnownBad = entry->customData()->contains(PasswordHealth::OPTION_KNOWN_BAD)
|
||||||
|
&& entry->customData()->value(PasswordHealth::OPTION_KNOWN_BAD) == TRUE_STR;
|
||||||
|
const auto isKnownBad = m_advancedUi->knownBadCheckBox->isChecked();
|
||||||
|
if (isKnownBad != wasKnownBad) {
|
||||||
|
entry->customData()->set(PasswordHealth::OPTION_KNOWN_BAD, isKnownBad ? TRUE_STR : FALSE_STR);
|
||||||
|
}
|
||||||
|
|
||||||
if (m_advancedUi->fgColorCheckBox->isChecked() && m_advancedUi->fgColorButton->property("color").isValid()) {
|
if (m_advancedUi->fgColorCheckBox->isChecked() && m_advancedUi->fgColorButton->property("color").isValid()) {
|
||||||
entry->setForegroundColor(m_advancedUi->fgColorButton->property("color").toString());
|
entry->setForegroundColor(m_advancedUi->fgColorButton->property("color").toString());
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>532</width>
|
<width>532</width>
|
||||||
<height>374</height>
|
<height>469</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
@ -174,9 +174,31 @@
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="knownBadCheckBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>If checked, the entry will not appear in reports like Health Check and HIBP even if it doesn't match the quality requirements (e. g. password entropy or re-use). You can set the check mark if the password is beyond your control (e. g. if it needs to be a four-digit PIN) to prevent it from cluttering the reports.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Exclude from database reports</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QWidget" name="colorsBox" native="true">
|
<widget class="QWidget" name="colorsBox" native="true">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<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>
|
<item>
|
||||||
<widget class="QCheckBox" name="fgColorCheckBox">
|
<widget class="QCheckBox" name="fgColorCheckBox">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -293,6 +315,7 @@
|
||||||
<tabstop>editAttributeButton</tabstop>
|
<tabstop>editAttributeButton</tabstop>
|
||||||
<tabstop>protectAttributeButton</tabstop>
|
<tabstop>protectAttributeButton</tabstop>
|
||||||
<tabstop>revealAttributeButton</tabstop>
|
<tabstop>revealAttributeButton</tabstop>
|
||||||
|
<tabstop>knownBadCheckBox</tabstop>
|
||||||
<tabstop>fgColorCheckBox</tabstop>
|
<tabstop>fgColorCheckBox</tabstop>
|
||||||
<tabstop>fgColorButton</tabstop>
|
<tabstop>fgColorButton</tabstop>
|
||||||
<tabstop>bgColorCheckBox</tabstop>
|
<tabstop>bgColorCheckBox</tabstop>
|
||||||
|
|
|
@ -20,11 +20,13 @@
|
||||||
|
|
||||||
#include "core/AsyncTask.h"
|
#include "core/AsyncTask.h"
|
||||||
#include "core/Database.h"
|
#include "core/Database.h"
|
||||||
|
#include "core/Global.h"
|
||||||
#include "core/Group.h"
|
#include "core/Group.h"
|
||||||
#include "core/PasswordHealth.h"
|
#include "core/PasswordHealth.h"
|
||||||
#include "core/Resources.h"
|
#include "core/Resources.h"
|
||||||
#include "gui/styles/StateColorPalette.h"
|
#include "gui/styles/StateColorPalette.h"
|
||||||
|
|
||||||
|
#include <QMenu>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
|
|
||||||
|
@ -38,11 +40,14 @@ namespace
|
||||||
QPointer<const Group> group;
|
QPointer<const Group> group;
|
||||||
QPointer<const Entry> entry;
|
QPointer<const Entry> entry;
|
||||||
QSharedPointer<PasswordHealth> health;
|
QSharedPointer<PasswordHealth> health;
|
||||||
|
bool knownBad = false;
|
||||||
|
|
||||||
Item(const Group* g, const Entry* e, QSharedPointer<PasswordHealth> h)
|
Item(const Group* g, const Entry* e, QSharedPointer<PasswordHealth> h)
|
||||||
: group(g)
|
: group(g)
|
||||||
, entry(e)
|
, entry(e)
|
||||||
, health(h)
|
, health(h)
|
||||||
|
, knownBad(e->customData()->contains(PasswordHealth::OPTION_KNOWN_BAD)
|
||||||
|
&& e->customData()->value(PasswordHealth::OPTION_KNOWN_BAD) == TRUE_STR)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,10 +64,16 @@ namespace
|
||||||
return m_items;
|
return m_items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool anyKnownBad() const
|
||||||
|
{
|
||||||
|
return m_anyKnownBad;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSharedPointer<Database> m_db;
|
QSharedPointer<Database> m_db;
|
||||||
HealthChecker m_checker;
|
HealthChecker m_checker;
|
||||||
QList<QSharedPointer<Item>> m_items;
|
QList<QSharedPointer<Item>> m_items;
|
||||||
|
bool m_anyKnownBad = false;
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -86,8 +97,13 @@ Health::Health(QSharedPointer<Database> db)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add entry if its password isn't at least "good"
|
// Evaluate this entry
|
||||||
const auto item = QSharedPointer<Item>(new Item(group, entry, m_checker.evaluate(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) {
|
if (item->health->quality() < PasswordHealth::Quality::Good) {
|
||||||
m_items.append(item);
|
m_items.append(item);
|
||||||
}
|
}
|
||||||
|
@ -110,8 +126,10 @@ ReportsWidgetHealthcheck::ReportsWidgetHealthcheck(QWidget* parent)
|
||||||
m_ui->healthcheckTableView->setModel(m_referencesModel.data());
|
m_ui->healthcheckTableView->setModel(m_referencesModel.data());
|
||||||
m_ui->healthcheckTableView->setSelectionMode(QAbstractItemView::NoSelection);
|
m_ui->healthcheckTableView->setSelectionMode(QAbstractItemView::NoSelection);
|
||||||
m_ui->healthcheckTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
m_ui->healthcheckTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||||
|
connect(m_ui->healthcheckTableView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(customMenuRequested(QPoint)));
|
||||||
|
|
||||||
connect(m_ui->healthcheckTableView, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex)));
|
connect(m_ui->healthcheckTableView, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex)));
|
||||||
|
connect(m_ui->showKnownBadCheckBox, SIGNAL(stateChanged(int)), this, SLOT(calculateHealth()));
|
||||||
}
|
}
|
||||||
|
|
||||||
ReportsWidgetHealthcheck::~ReportsWidgetHealthcheck()
|
ReportsWidgetHealthcheck::~ReportsWidgetHealthcheck()
|
||||||
|
@ -120,7 +138,8 @@ ReportsWidgetHealthcheck::~ReportsWidgetHealthcheck()
|
||||||
|
|
||||||
void ReportsWidgetHealthcheck::addHealthRow(QSharedPointer<PasswordHealth> health,
|
void ReportsWidgetHealthcheck::addHealthRow(QSharedPointer<PasswordHealth> health,
|
||||||
const Group* group,
|
const Group* group,
|
||||||
const Entry* entry)
|
const Entry* entry,
|
||||||
|
bool knownBad)
|
||||||
{
|
{
|
||||||
QString descr, tip;
|
QString descr, tip;
|
||||||
QColor qualityColor;
|
QColor qualityColor;
|
||||||
|
@ -151,9 +170,14 @@ void ReportsWidgetHealthcheck::addHealthRow(QSharedPointer<PasswordHealth> healt
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto title = entry->title();
|
||||||
|
if (knownBad) {
|
||||||
|
title.append(tr(" (Excluded)"));
|
||||||
|
}
|
||||||
|
|
||||||
auto row = QList<QStandardItem*>();
|
auto row = QList<QStandardItem*>();
|
||||||
row << new QStandardItem(descr);
|
row << new QStandardItem(descr);
|
||||||
row << new QStandardItem(entry->iconPixmap(), entry->title());
|
row << new QStandardItem(entry->iconPixmap(), title);
|
||||||
row << new QStandardItem(group->iconPixmap(), group->hierarchy().join("/"));
|
row << new QStandardItem(group->iconPixmap(), group->hierarchy().join("/"));
|
||||||
row << new QStandardItem(QString::number(health->score()));
|
row << new QStandardItem(QString::number(health->score()));
|
||||||
row << new QStandardItem(health->scoreReason());
|
row << new QStandardItem(health->scoreReason());
|
||||||
|
@ -167,6 +191,9 @@ void ReportsWidgetHealthcheck::addHealthRow(QSharedPointer<PasswordHealth> healt
|
||||||
|
|
||||||
// Set tooltips
|
// Set tooltips
|
||||||
row[0]->setToolTip(tip);
|
row[0]->setToolTip(tip);
|
||||||
|
if (knownBad) {
|
||||||
|
row[1]->setToolTip(tr("This entry is being excluded from reports"));
|
||||||
|
}
|
||||||
row[4]->setToolTip(health->scoreDetails());
|
row[4]->setToolTip(health->scoreDetails());
|
||||||
|
|
||||||
// Store entry pointer per table row (used in double click handler)
|
// Store entry pointer per table row (used in double click handler)
|
||||||
|
@ -201,21 +228,41 @@ void ReportsWidgetHealthcheck::calculateHealth()
|
||||||
{
|
{
|
||||||
m_referencesModel->clear();
|
m_referencesModel->clear();
|
||||||
|
|
||||||
|
// Perform the health check
|
||||||
const QScopedPointer<Health> health(AsyncTask::runAndWaitForFuture([this] { return new Health(m_db); }));
|
const QScopedPointer<Health> health(AsyncTask::runAndWaitForFuture([this] { return new Health(m_db); }));
|
||||||
if (health->items().empty()) {
|
|
||||||
// No findings
|
// Display entries that are marked as "known bad"?
|
||||||
m_referencesModel->clear();
|
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!"));
|
m_referencesModel->setHorizontalHeaderLabels(QStringList() << tr("Congratulations, everything is healthy!"));
|
||||||
} else {
|
} else {
|
||||||
// Show our findings
|
|
||||||
m_referencesModel->setHorizontalHeaderLabels(QStringList() << tr("") << tr("Title") << tr("Path") << tr("Score")
|
m_referencesModel->setHorizontalHeaderLabels(QStringList() << tr("") << tr("Title") << tr("Path") << tr("Score")
|
||||||
<< tr("Reason"));
|
<< tr("Reason"));
|
||||||
for (const auto& item : health->items()) {
|
|
||||||
addHealthRow(item->health, item->group, item->entry);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ui->healthcheckTableView->resizeRowsToContents();
|
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)
|
void ReportsWidgetHealthcheck::emitEntryActivated(const QModelIndex& index)
|
||||||
|
@ -232,6 +279,57 @@ void ReportsWidgetHealthcheck::emitEntryActivated(const QModelIndex& index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ReportsWidgetHealthcheck::customMenuRequested(QPoint pos)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Find which entry has been clicked
|
||||||
|
const auto index = m_ui->healthcheckTableView->indexAt(pos);
|
||||||
|
if (!index.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_contextmenuEntry = const_cast<Entry*>(m_rowToEntry[index.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()
|
void ReportsWidgetHealthcheck::saveSettings()
|
||||||
{
|
{
|
||||||
// nothing to do - the tab is passive
|
// nothing to do - the tab is passive
|
||||||
|
|
|
@ -54,9 +54,12 @@ signals:
|
||||||
public slots:
|
public slots:
|
||||||
void calculateHealth();
|
void calculateHealth();
|
||||||
void emitEntryActivated(const QModelIndex& index);
|
void emitEntryActivated(const QModelIndex& index);
|
||||||
|
void customMenuRequested(QPoint);
|
||||||
|
void editFromContextmenu();
|
||||||
|
void toggleKnownBad(bool);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void addHealthRow(QSharedPointer<PasswordHealth>, const Group*, const Entry*);
|
void addHealthRow(QSharedPointer<PasswordHealth>, const Group*, const Entry*, bool knownBad);
|
||||||
|
|
||||||
QScopedPointer<Ui::ReportsWidgetHealthcheck> m_ui;
|
QScopedPointer<Ui::ReportsWidgetHealthcheck> m_ui;
|
||||||
|
|
||||||
|
@ -65,6 +68,7 @@ private:
|
||||||
QScopedPointer<QStandardItemModel> m_referencesModel;
|
QScopedPointer<QStandardItemModel> m_referencesModel;
|
||||||
QSharedPointer<Database> m_db;
|
QSharedPointer<Database> m_db;
|
||||||
QList<QPair<const Group*, const Entry*>> m_rowToEntry;
|
QList<QPair<const Group*, const Entry*>> m_rowToEntry;
|
||||||
|
Entry* m_contextmenuEntry = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSXC_REPORTSWIDGETHEALTHCHECK_H
|
#endif // KEEPASSXC_REPORTSWIDGETHEALTHCHECK_H
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>327</width>
|
<width>505</width>
|
||||||
<height>379</height>
|
<height>379</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0">
|
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
|
||||||
<property name="leftMargin">
|
<property name="leftMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
|
@ -23,14 +23,11 @@
|
||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="healthcheckGroupBox">
|
|
||||||
<property name="title">
|
|
||||||
<string>Health Check</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTableView" name="healthcheckTableView">
|
<widget class="QTableView" name="healthcheckTableView">
|
||||||
|
<property name="contextMenuPolicy">
|
||||||
|
<enum>Qt::CustomContextMenu</enum>
|
||||||
|
</property>
|
||||||
<property name="editTriggers">
|
<property name="editTriggers">
|
||||||
<set>QAbstractItemView::NoEditTriggers</set>
|
<set>QAbstractItemView::NoEditTriggers</set>
|
||||||
</property>
|
</property>
|
||||||
|
@ -47,7 +44,7 @@
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
<attribute name="horizontalHeaderVisible">
|
<attribute name="horizontalHeaderVisible">
|
||||||
<bool>true</bool>
|
<bool>false</bool>
|
||||||
</attribute>
|
</attribute>
|
||||||
<attribute name="horizontalHeaderStretchLastSection">
|
<attribute name="horizontalHeaderStretchLastSection">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
@ -57,6 +54,13 @@
|
||||||
</attribute>
|
</attribute>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="showKnownBadCheckBox">
|
||||||
|
<property name="text">
|
||||||
|
<string>Also show entries that have been excluded from reports</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="tipLabel">
|
<widget class="QLabel" name="tipLabel">
|
||||||
<property name="font">
|
<property name="font">
|
||||||
|
@ -71,9 +75,6 @@
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
|
|
@ -20,11 +20,32 @@
|
||||||
|
|
||||||
#include "config-keepassx.h"
|
#include "config-keepassx.h"
|
||||||
#include "core/Database.h"
|
#include "core/Database.h"
|
||||||
|
#include "core/Global.h"
|
||||||
#include "core/Group.h"
|
#include "core/Group.h"
|
||||||
|
#include "core/PasswordHealth.h"
|
||||||
|
#include "core/Resources.h"
|
||||||
#include "gui/MessageBox.h"
|
#include "gui/MessageBox.h"
|
||||||
|
|
||||||
|
#include <QMenu>
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Check if an entry has been marked as "known bad password".
|
||||||
|
* These entries are to be excluded from the HIBP report.
|
||||||
|
*
|
||||||
|
* Question to reviewer: Should this be a member function of Entry?
|
||||||
|
* It's duplicated in EditEntryWidget::setForms, EditEntryWidget::updateEntryData,
|
||||||
|
* ReportsWidgetHealthcheck::customMenuRequested, and Health::Item::Item.
|
||||||
|
*/
|
||||||
|
bool isKnownBad(const Entry* entry)
|
||||||
|
{
|
||||||
|
return entry->customData()->contains(PasswordHealth::OPTION_KNOWN_BAD)
|
||||||
|
&& entry->customData()->value(PasswordHealth::OPTION_KNOWN_BAD) == TRUE_STR;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
ReportsWidgetHibp::ReportsWidgetHibp(QWidget* parent)
|
ReportsWidgetHibp::ReportsWidgetHibp(QWidget* parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, m_ui(new Ui::ReportsWidgetHibp())
|
, m_ui(new Ui::ReportsWidgetHibp())
|
||||||
|
@ -37,6 +58,8 @@ ReportsWidgetHibp::ReportsWidgetHibp(QWidget* parent)
|
||||||
m_ui->hibpTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
m_ui->hibpTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||||
|
|
||||||
connect(m_ui->hibpTableView, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex)));
|
connect(m_ui->hibpTableView, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex)));
|
||||||
|
connect(m_ui->hibpTableView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(customMenuRequested(QPoint)));
|
||||||
|
connect(m_ui->showKnownBadCheckBox, SIGNAL(stateChanged(int)), this, SLOT(makeHibpTable()));
|
||||||
#ifdef WITH_XC_NETWORKING
|
#ifdef WITH_XC_NETWORKING
|
||||||
connect(&m_downloader, SIGNAL(hibpResult(QString, int)), SLOT(addHibpResult(QString, int)));
|
connect(&m_downloader, SIGNAL(hibpResult(QString, int)), SLOT(addHibpResult(QString, int)));
|
||||||
connect(&m_downloader, SIGNAL(fetchFailed(QString)), SLOT(fetchFailed(QString)));
|
connect(&m_downloader, SIGNAL(fetchFailed(QString)), SLOT(fetchFailed(QString)));
|
||||||
|
@ -104,18 +127,43 @@ void ReportsWidgetHibp::makeHibpTable()
|
||||||
return lhs.second > rhs.second;
|
return lhs.second > rhs.second;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Display entries that are marked as "known bad"?
|
||||||
|
const auto showKnownBad = m_ui->showKnownBadCheckBox->isChecked();
|
||||||
|
|
||||||
|
// The colors for table cells
|
||||||
|
const auto red = QBrush("red");
|
||||||
|
|
||||||
// Build the table
|
// Build the table
|
||||||
|
bool anyKnownBad = false;
|
||||||
for (const auto& item : items) {
|
for (const auto& item : items) {
|
||||||
const auto entry = item.first;
|
const auto entry = item.first;
|
||||||
const auto group = entry->group();
|
const auto group = entry->group();
|
||||||
const auto count = item.second;
|
const auto count = item.second;
|
||||||
|
auto title = entry->title();
|
||||||
|
|
||||||
|
// If the entry is marked as known bad, hide it unless the
|
||||||
|
// checkbox is set.
|
||||||
|
bool knownBad = isKnownBad(entry);
|
||||||
|
if (knownBad) {
|
||||||
|
anyKnownBad = true;
|
||||||
|
if (!showKnownBad) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
title.append(tr(" (Excluded)"));
|
||||||
|
}
|
||||||
|
|
||||||
auto row = QList<QStandardItem*>();
|
auto row = QList<QStandardItem*>();
|
||||||
row << new QStandardItem(entry->iconPixmap(), entry->title())
|
row << new QStandardItem(entry->iconPixmap(), title)
|
||||||
<< new QStandardItem(group->iconPixmap(), group->hierarchy().join("/"))
|
<< new QStandardItem(group->iconPixmap(), group->hierarchy().join("/"))
|
||||||
<< new QStandardItem(countToText(count));
|
<< new QStandardItem(countToText(count));
|
||||||
|
|
||||||
|
if (knownBad) {
|
||||||
|
row[1]->setToolTip(tr("This entry is being excluded from reports"));
|
||||||
|
}
|
||||||
|
|
||||||
|
row[2]->setForeground(red);
|
||||||
m_referencesModel->appendRow(row);
|
m_referencesModel->appendRow(row);
|
||||||
row[2]->setForeground(QBrush(QColor("red")));
|
|
||||||
|
|
||||||
// Store entry pointer per table row (used in double click handler)
|
// Store entry pointer per table row (used in double click handler)
|
||||||
m_rowToEntry.append(entry);
|
m_rowToEntry.append(entry);
|
||||||
|
@ -129,6 +177,22 @@ void ReportsWidgetHibp::makeHibpTable()
|
||||||
row[0]->setForeground(QBrush(QColor("red")));
|
row[0]->setForeground(QBrush(QColor("red")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're done and everything is good, display a motivational message
|
||||||
|
#ifdef WITH_XC_NETWORKING
|
||||||
|
if (m_downloader.passwordsRemaining() == 0 && m_pwndPasswords.isEmpty() && m_error.isEmpty()) {
|
||||||
|
m_referencesModel->clear();
|
||||||
|
m_referencesModel->setHorizontalHeaderLabels(QStringList() << tr("Congratulations, no exposed passwords!"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Show the "show known bad entries" checkbox if there's any known
|
||||||
|
// bad entry in the database.
|
||||||
|
if (anyKnownBad) {
|
||||||
|
m_ui->showKnownBadCheckBox->show();
|
||||||
|
} else {
|
||||||
|
m_ui->showKnownBadCheckBox->hide();
|
||||||
|
}
|
||||||
|
|
||||||
m_ui->hibpTableView->resizeRowsToContents();
|
m_ui->hibpTableView->resizeRowsToContents();
|
||||||
|
|
||||||
m_ui->stackedWidget->setCurrentIndex(1);
|
m_ui->stackedWidget->setCurrentIndex(1);
|
||||||
|
@ -176,7 +240,8 @@ void ReportsWidgetHibp::startValidation()
|
||||||
{
|
{
|
||||||
#ifdef WITH_XC_NETWORKING
|
#ifdef WITH_XC_NETWORKING
|
||||||
// Collect all passwords in the database (unless recycled, and
|
// Collect all passwords in the database (unless recycled, and
|
||||||
// unless empty) and submit them to the downloader.
|
// unless empty, and unless marked as "known bad") and submit them
|
||||||
|
// to the downloader.
|
||||||
for (const auto* entry : m_db->rootGroup()->entriesRecursive()) {
|
for (const auto* entry : m_db->rootGroup()->entriesRecursive()) {
|
||||||
if (!entry->isRecycled() && !entry->password().isEmpty()) {
|
if (!entry->isRecycled() && !entry->password().isEmpty()) {
|
||||||
m_downloader.add(entry->password());
|
m_downloader.add(entry->password());
|
||||||
|
@ -238,6 +303,7 @@ void ReportsWidgetHibp::emitEntryActivated(const QModelIndex& index)
|
||||||
// Found it, invoke entry editor
|
// Found it, invoke entry editor
|
||||||
m_editedEntry = entry;
|
m_editedEntry = entry;
|
||||||
m_editedPassword = entry->password();
|
m_editedPassword = entry->password();
|
||||||
|
m_editedKnownBad = isKnownBad(entry);
|
||||||
emit entryActivated(const_cast<Entry*>(entry));
|
emit entryActivated(const_cast<Entry*>(entry));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -253,8 +319,13 @@ void ReportsWidgetHibp::refreshAfterEdit()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No need to re-validate if there was no change
|
// No need to re-validate if there was no change that affects
|
||||||
if (m_editedEntry->password() == m_editedPassword) {
|
// the HIBP result (i. e., change to the password or to the
|
||||||
|
// "known bad" flag)
|
||||||
|
if (m_editedEntry->password() == m_editedPassword && isKnownBad(m_editedEntry) == m_editedKnownBad) {
|
||||||
|
// Don't go through HIBP but still rebuild the table, the user might
|
||||||
|
// have edited the entry title.
|
||||||
|
makeHibpTable();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,6 +341,57 @@ void ReportsWidgetHibp::refreshAfterEdit()
|
||||||
m_editedEntry = nullptr;
|
m_editedEntry = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ReportsWidgetHibp::customMenuRequested(QPoint pos)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Find which entry has been clicked
|
||||||
|
const auto index = m_ui->hibpTableView->indexAt(pos);
|
||||||
|
if (!index.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_contextmenuEntry = const_cast<Entry*>(m_rowToEntry[index.row()]);
|
||||||
|
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->hibpTableView->viewport()->mapToGlobal(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReportsWidgetHibp::editFromContextmenu()
|
||||||
|
{
|
||||||
|
if (m_contextmenuEntry) {
|
||||||
|
emit entryActivated(m_contextmenuEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReportsWidgetHibp::toggleKnownBad(bool isKnownBad)
|
||||||
|
{
|
||||||
|
if (!m_contextmenuEntry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_contextmenuEntry->customData()->set(PasswordHealth::OPTION_KNOWN_BAD, isKnownBad ? TRUE_STR : FALSE_STR);
|
||||||
|
|
||||||
|
makeHibpTable();
|
||||||
|
}
|
||||||
|
|
||||||
void ReportsWidgetHibp::saveSettings()
|
void ReportsWidgetHibp::saveSettings()
|
||||||
{
|
{
|
||||||
// nothing to do - the tab is passive
|
// nothing to do - the tab is passive
|
||||||
|
|
|
@ -58,9 +58,12 @@ public slots:
|
||||||
void emitEntryActivated(const QModelIndex&);
|
void emitEntryActivated(const QModelIndex&);
|
||||||
void addHibpResult(const QString&, int);
|
void addHibpResult(const QString&, int);
|
||||||
void fetchFailed(const QString& error);
|
void fetchFailed(const QString& error);
|
||||||
|
void makeHibpTable();
|
||||||
|
void customMenuRequested(QPoint);
|
||||||
|
void editFromContextmenu();
|
||||||
|
void toggleKnownBad(bool);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void makeHibpTable();
|
|
||||||
void startValidation();
|
void startValidation();
|
||||||
static QString countToText(int count);
|
static QString countToText(int count);
|
||||||
|
|
||||||
|
@ -73,6 +76,8 @@ private:
|
||||||
QList<const Entry*> m_rowToEntry; // List index is table row
|
QList<const Entry*> m_rowToEntry; // List index is table row
|
||||||
QPointer<const Entry> m_editedEntry; // The entry we're currently editing
|
QPointer<const Entry> m_editedEntry; // The entry we're currently editing
|
||||||
QString m_editedPassword; // The old password of the entry we're editing
|
QString m_editedPassword; // The old password of the entry we're editing
|
||||||
|
bool m_editedKnownBad; // The old "known bad" flag of the entry we're editing
|
||||||
|
Entry* m_contextmenuEntry = nullptr; // The entry that was right-clicked
|
||||||
|
|
||||||
#ifdef WITH_XC_NETWORKING
|
#ifdef WITH_XC_NETWORKING
|
||||||
HibpDownloader m_downloader; // This performs the actual HIBP online query
|
HibpDownloader m_downloader; // This performs the actual HIBP online query
|
||||||
|
|
|
@ -11,16 +11,22 @@
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<property name="leftMargin">
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<number>0</number>
|
||||||
<property name="title">
|
</property>
|
||||||
<string>Have I Been Pwned?</string>
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QStackedWidget" name="stackedWidget">
|
<widget class="QStackedWidget" name="stackedWidget">
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>0</number>
|
<number>1</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="confirmation">
|
<widget class="QWidget" name="confirmation">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
@ -39,6 +45,19 @@
|
||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_3">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
<item>
|
<item>
|
||||||
|
@ -82,13 +101,6 @@
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QProgressBar" name="progressBar">
|
|
||||||
<property name="value">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer">
|
<spacer name="verticalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
|
@ -102,6 +114,13 @@
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="progressBar">
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="resultsTable">
|
<widget class="QWidget" name="resultsTable">
|
||||||
|
@ -120,6 +139,9 @@
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTableView" name="hibpTableView">
|
<widget class="QTableView" name="hibpTableView">
|
||||||
|
<property name="contextMenuPolicy">
|
||||||
|
<enum>Qt::CustomContextMenu</enum>
|
||||||
|
</property>
|
||||||
<property name="editTriggers">
|
<property name="editTriggers">
|
||||||
<set>QAbstractItemView::NoEditTriggers</set>
|
<set>QAbstractItemView::NoEditTriggers</set>
|
||||||
</property>
|
</property>
|
||||||
|
@ -135,6 +157,9 @@
|
||||||
<property name="sortingEnabled">
|
<property name="sortingEnabled">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
|
<attribute name="horizontalHeaderVisible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
<attribute name="horizontalHeaderStretchLastSection">
|
<attribute name="horizontalHeaderStretchLastSection">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</attribute>
|
</attribute>
|
||||||
|
@ -143,10 +168,30 @@
|
||||||
</attribute>
|
</attribute>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="showKnownBadCheckBox">
|
||||||
|
<property name="text">
|
||||||
|
<string>Also show entries that have been excluded from reports</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="noNetwork">
|
<widget class="QWidget" name="noNetwork">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_4">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="networkNoticeLabel">
|
<widget class="QLabel" name="networkNoticeLabel">
|
||||||
<property name="maximumSize">
|
<property name="maximumSize">
|
||||||
|
@ -182,9 +227,6 @@
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
#include "core/AsyncTask.h"
|
#include "core/AsyncTask.h"
|
||||||
#include "core/Database.h"
|
#include "core/Database.h"
|
||||||
|
#include "core/Global.h"
|
||||||
#include "core/Group.h"
|
#include "core/Group.h"
|
||||||
#include "core/Metadata.h"
|
#include "core/Metadata.h"
|
||||||
#include "core/PasswordHealth.h"
|
#include "core/PasswordHealth.h"
|
||||||
|
@ -43,6 +44,7 @@ namespace
|
||||||
int nPwdsShort = 0; // Number of passwords 8 characters or less in size
|
int nPwdsShort = 0; // Number of passwords 8 characters or less in size
|
||||||
int nPwdsUnique = 0; // Number of unique passwords
|
int nPwdsUnique = 0; // Number of unique passwords
|
||||||
int nPwdsReused = 0; // Number of non-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
|
int pwdTotalLen = 0; // Total length of all passwords
|
||||||
|
|
||||||
// Ctor does all the work
|
// Ctor does all the work
|
||||||
|
@ -138,6 +140,11 @@ namespace
|
||||||
++nPwdsWeak;
|
++nPwdsWeak;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entry->customData()->contains(PasswordHealth::OPTION_KNOWN_BAD)
|
||||||
|
&& entry->customData()->value(PasswordHealth::OPTION_KNOWN_BAD) == TRUE_STR) {
|
||||||
|
++nKnownBad;
|
||||||
|
}
|
||||||
|
|
||||||
pwdTotalLen += pwd.size();
|
pwdTotalLen += pwd.size();
|
||||||
m_passwords[pwd]++;
|
m_passwords[pwd]++;
|
||||||
}
|
}
|
||||||
|
@ -235,6 +242,11 @@ void ReportsWidgetStatistics::calculateStats()
|
||||||
QString::number(stats->nPwdsWeak),
|
QString::number(stats->nPwdsWeak),
|
||||||
stats->nPwdsWeak > 0,
|
stats->nPwdsWeak > 0,
|
||||||
tr("Recommend using long, randomized passwords with a rating of 'good' or 'excellent'."));
|
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"),
|
addStatsRow(tr("Average password length"),
|
||||||
tr("%1 characters").arg(stats->averagePwdLength()),
|
tr("%1 characters").arg(stats->averagePwdLength()),
|
||||||
stats->isAvgPwdTooShort(),
|
stats->isAvgPwdTooShort(),
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>327</width>
|
<width>397</width>
|
||||||
<height>379</height>
|
<height>379</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0">
|
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
|
||||||
<property name="leftMargin">
|
<property name="leftMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
|
@ -23,12 +23,6 @@
|
||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="statisticsGroupBox">
|
|
||||||
<property name="title">
|
|
||||||
<string>Statistics</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTableView" name="statisticsTableView">
|
<widget class="QTableView" name="statisticsTableView">
|
||||||
<property name="editTriggers">
|
<property name="editTriggers">
|
||||||
|
@ -71,9 +65,6 @@
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
#include "core/Entry.h"
|
#include "core/Entry.h"
|
||||||
#include "core/Group.h"
|
#include "core/Group.h"
|
||||||
#include "core/Metadata.h"
|
#include "core/Metadata.h"
|
||||||
|
#include "core/PasswordHealth.h"
|
||||||
#include "core/Tools.h"
|
#include "core/Tools.h"
|
||||||
#include "crypto/Crypto.h"
|
#include "crypto/Crypto.h"
|
||||||
#include "crypto/kdf/AesKdf.h"
|
#include "crypto/kdf/AesKdf.h"
|
||||||
|
@ -442,6 +443,17 @@ void TestGui::testEditEntry()
|
||||||
QCOMPARE(entry->historyItems().size(), ++editCount);
|
QCOMPARE(entry->historyItems().size(), ++editCount);
|
||||||
QVERIFY(!applyButton->isEnabled());
|
QVERIFY(!applyButton->isEnabled());
|
||||||
|
|
||||||
|
// Test the "known bad" checkbox
|
||||||
|
editEntryWidget->setCurrentPage(1);
|
||||||
|
auto knownBadCheckBox = editEntryWidget->findChild<QCheckBox*>("knownBadCheckBox");
|
||||||
|
QVERIFY(knownBadCheckBox);
|
||||||
|
QCOMPARE(knownBadCheckBox->isChecked(), false);
|
||||||
|
knownBadCheckBox->setChecked(true);
|
||||||
|
QTest::mouseClick(applyButton, Qt::LeftButton);
|
||||||
|
QCOMPARE(entry->historyItems().size(), ++editCount);
|
||||||
|
QCOMPARE(entry->customData()->contains(PasswordHealth::OPTION_KNOWN_BAD), true);
|
||||||
|
QCOMPARE(entry->customData()->value(PasswordHealth::OPTION_KNOWN_BAD), TRUE_STR);
|
||||||
|
|
||||||
// Test entry colors (simulate choosing a color)
|
// Test entry colors (simulate choosing a color)
|
||||||
editEntryWidget->setCurrentPage(1);
|
editEntryWidget->setCurrentPage(1);
|
||||||
auto fgColor = QString("#FF0000");
|
auto fgColor = QString("#FF0000");
|
||||||
|
|
|
@ -117,6 +117,8 @@ map() {
|
||||||
preferences-other) echo file-document-edit-outline ;;
|
preferences-other) echo file-document-edit-outline ;;
|
||||||
preferences-desktop-icons) echo emoticon-happy-outline ;;
|
preferences-desktop-icons) echo emoticon-happy-outline ;;
|
||||||
preferences-system-network-sharing) echo lan ;;
|
preferences-system-network-sharing) echo lan ;;
|
||||||
|
reports) echo lightbulb-on-outline ;;
|
||||||
|
reports-exclude) echo lightbulb-off-outline ;;
|
||||||
security-high) echo shield-outline ;;
|
security-high) echo shield-outline ;;
|
||||||
sort-alphabetical-ascending) echo sort-alphabetical-ascending ;;
|
sort-alphabetical-ascending) echo sort-alphabetical-ascending ;;
|
||||||
sort-alphabetical-descending) echo sort-alphabetical-descending ;;
|
sort-alphabetical-descending) echo sort-alphabetical-descending ;;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue