mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-02-22 07:29:52 -05: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
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/refresh.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/>
|
||||
License: SIL OPEN FONT LICENSE Version 1.1
|
||||
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 |
@ -50,7 +50,9 @@
|
||||
<file>application/scalable/actions/password-generator.svg</file>
|
||||
<file>application/scalable/actions/password-show-off.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-descending.svg</file>
|
||||
<file>application/scalable/actions/statistics.svg</file>
|
||||
|
@ -24,6 +24,9 @@
|
||||
#include "PasswordHealth.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)
|
||||
: m_score(entropy)
|
||||
, m_entropy(entropy)
|
||||
|
@ -83,6 +83,14 @@ public:
|
||||
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:
|
||||
int m_score = 0;
|
||||
double m_entropy = 0.0;
|
||||
|
@ -355,7 +355,7 @@ MainWindow::MainWindow()
|
||||
m_ui->actionDatabaseSave->setIcon(resources()->icon("document-save"));
|
||||
m_ui->actionDatabaseSaveAs->setIcon(resources()->icon("document-save-as"));
|
||||
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->actionChangeMasterKey->setIcon(resources()->icon("database-change-key"));
|
||||
m_ui->actionLockDatabases->setIcon(resources()->icon("database-lock"));
|
||||
|
@ -42,6 +42,7 @@
|
||||
#include "core/Database.h"
|
||||
#include "core/Entry.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "core/PasswordHealth.h"
|
||||
#include "core/Resources.h"
|
||||
#include "core/TimeDelta.h"
|
||||
#include "core/Tools.h"
|
||||
@ -423,6 +424,7 @@ void EditEntryWidget::setupEntryUpdate()
|
||||
// Advanced tab
|
||||
connect(m_advancedUi->attributesEdit, SIGNAL(textChanged()), 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->bgColorCheckBox, SIGNAL(stateChanged(int)), 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;
|
||||
}
|
||||
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(false, entry->backgroundColor());
|
||||
m_iconsWidget->setEnabled(!m_history);
|
||||
@ -1031,6 +1036,13 @@ void EditEntryWidget::updateEntryData(Entry* entry) const
|
||||
|
||||
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()) {
|
||||
entry->setForegroundColor(m_advancedUi->fgColorButton->property("color").toString());
|
||||
} else {
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>532</width>
|
||||
<height>374</height>
|
||||
<height>469</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
@ -174,9 +174,31 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</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>
|
||||
<widget class="QWidget" name="colorsBox" native="true">
|
||||
<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>
|
||||
<widget class="QCheckBox" name="fgColorCheckBox">
|
||||
<property name="text">
|
||||
@ -293,6 +315,7 @@
|
||||
<tabstop>editAttributeButton</tabstop>
|
||||
<tabstop>protectAttributeButton</tabstop>
|
||||
<tabstop>revealAttributeButton</tabstop>
|
||||
<tabstop>knownBadCheckBox</tabstop>
|
||||
<tabstop>fgColorCheckBox</tabstop>
|
||||
<tabstop>fgColorButton</tabstop>
|
||||
<tabstop>bgColorCheckBox</tabstop>
|
||||
|
@ -20,11 +20,13 @@
|
||||
|
||||
#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 <QStandardItemModel>
|
||||
|
||||
@ -38,11 +40,14 @@ namespace
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
@ -59,10 +64,16 @@ namespace
|
||||
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
|
||||
|
||||
@ -86,8 +97,13 @@ Health::Health(QSharedPointer<Database> db)
|
||||
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)));
|
||||
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);
|
||||
}
|
||||
@ -110,8 +126,10 @@ ReportsWidgetHealthcheck::ReportsWidgetHealthcheck(QWidget* parent)
|
||||
m_ui->healthcheckTableView->setModel(m_referencesModel.data());
|
||||
m_ui->healthcheckTableView->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
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->showKnownBadCheckBox, SIGNAL(stateChanged(int)), this, SLOT(calculateHealth()));
|
||||
}
|
||||
|
||||
ReportsWidgetHealthcheck::~ReportsWidgetHealthcheck()
|
||||
@ -120,7 +138,8 @@ ReportsWidgetHealthcheck::~ReportsWidgetHealthcheck()
|
||||
|
||||
void ReportsWidgetHealthcheck::addHealthRow(QSharedPointer<PasswordHealth> health,
|
||||
const Group* group,
|
||||
const Entry* entry)
|
||||
const Entry* entry,
|
||||
bool knownBad)
|
||||
{
|
||||
QString descr, tip;
|
||||
QColor qualityColor;
|
||||
@ -151,9 +170,14 @@ void ReportsWidgetHealthcheck::addHealthRow(QSharedPointer<PasswordHealth> healt
|
||||
break;
|
||||
}
|
||||
|
||||
auto title = entry->title();
|
||||
if (knownBad) {
|
||||
title.append(tr(" (Excluded)"));
|
||||
}
|
||||
|
||||
auto row = QList<QStandardItem*>();
|
||||
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(QString::number(health->score()));
|
||||
row << new QStandardItem(health->scoreReason());
|
||||
@ -167,6 +191,9 @@ void ReportsWidgetHealthcheck::addHealthRow(QSharedPointer<PasswordHealth> healt
|
||||
|
||||
// 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)
|
||||
@ -201,21 +228,41 @@ void ReportsWidgetHealthcheck::calculateHealth()
|
||||
{
|
||||
m_referencesModel->clear();
|
||||
|
||||
// Perform the health check
|
||||
const QScopedPointer<Health> health(AsyncTask::runAndWaitForFuture([this] { return new Health(m_db); }));
|
||||
if (health->items().empty()) {
|
||||
// No findings
|
||||
m_referencesModel->clear();
|
||||
|
||||
// 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 {
|
||||
// Show our findings
|
||||
m_referencesModel->setHorizontalHeaderLabels(QStringList() << tr("") << tr("Title") << tr("Path") << tr("Score")
|
||||
<< tr("Reason"));
|
||||
for (const auto& item : health->items()) {
|
||||
addHealthRow(item->health, item->group, item->entry);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@ -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()
|
||||
{
|
||||
// nothing to do - the tab is passive
|
||||
|
@ -54,9 +54,12 @@ signals:
|
||||
public slots:
|
||||
void calculateHealth();
|
||||
void emitEntryActivated(const QModelIndex& index);
|
||||
void customMenuRequested(QPoint);
|
||||
void editFromContextmenu();
|
||||
void toggleKnownBad(bool);
|
||||
|
||||
private:
|
||||
void addHealthRow(QSharedPointer<PasswordHealth>, const Group*, const Entry*);
|
||||
void addHealthRow(QSharedPointer<PasswordHealth>, const Group*, const Entry*, bool knownBad);
|
||||
|
||||
QScopedPointer<Ui::ReportsWidgetHealthcheck> m_ui;
|
||||
|
||||
@ -65,6 +68,7 @@ private:
|
||||
QScopedPointer<QStandardItemModel> m_referencesModel;
|
||||
QSharedPointer<Database> m_db;
|
||||
QList<QPair<const Group*, const Entry*>> m_rowToEntry;
|
||||
Entry* m_contextmenuEntry = nullptr;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_REPORTSWIDGETHEALTHCHECK_H
|
||||
|
@ -6,11 +6,11 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>327</width>
|
||||
<width>505</width>
|
||||
<height>379</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
@ -24,52 +24,53 @@
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="healthcheckGroupBox">
|
||||
<property name="title">
|
||||
<string>Health Check</string>
|
||||
<widget class="QTableView" name="healthcheckTableView">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<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="QCheckBox" name="showKnownBadCheckBox">
|
||||
<property name="text">
|
||||
<string>Also show entries that have been excluded from reports</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="tipLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Hover over reason to show additional details. Double-click entries to edit.</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QTableView" name="healthcheckTableView">
|
||||
<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>true</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="tipLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Hover over reason to show additional details. Double-click entries to edit.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -20,11 +20,32 @@
|
||||
|
||||
#include "config-keepassx.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Global.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/PasswordHealth.h"
|
||||
#include "core/Resources.h"
|
||||
#include "gui/MessageBox.h"
|
||||
|
||||
#include <QMenu>
|
||||
#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)
|
||||
: QWidget(parent)
|
||||
, m_ui(new Ui::ReportsWidgetHibp())
|
||||
@ -37,6 +58,8 @@ ReportsWidgetHibp::ReportsWidgetHibp(QWidget* parent)
|
||||
m_ui->hibpTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
|
||||
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
|
||||
connect(&m_downloader, SIGNAL(hibpResult(QString, int)), SLOT(addHibpResult(QString, int)));
|
||||
connect(&m_downloader, SIGNAL(fetchFailed(QString)), SLOT(fetchFailed(QString)));
|
||||
@ -104,18 +127,43 @@ void ReportsWidgetHibp::makeHibpTable()
|
||||
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
|
||||
bool anyKnownBad = false;
|
||||
for (const auto& item : items) {
|
||||
const auto entry = item.first;
|
||||
const auto group = entry->group();
|
||||
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*>();
|
||||
row << new QStandardItem(entry->iconPixmap(), entry->title())
|
||||
row << new QStandardItem(entry->iconPixmap(), title)
|
||||
<< new QStandardItem(group->iconPixmap(), group->hierarchy().join("/"))
|
||||
<< 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);
|
||||
row[2]->setForeground(QBrush(QColor("red")));
|
||||
|
||||
// Store entry pointer per table row (used in double click handler)
|
||||
m_rowToEntry.append(entry);
|
||||
@ -129,6 +177,22 @@ void ReportsWidgetHibp::makeHibpTable()
|
||||
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->stackedWidget->setCurrentIndex(1);
|
||||
@ -176,7 +240,8 @@ void ReportsWidgetHibp::startValidation()
|
||||
{
|
||||
#ifdef WITH_XC_NETWORKING
|
||||
// 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()) {
|
||||
if (!entry->isRecycled() && !entry->password().isEmpty()) {
|
||||
m_downloader.add(entry->password());
|
||||
@ -238,6 +303,7 @@ void ReportsWidgetHibp::emitEntryActivated(const QModelIndex& index)
|
||||
// Found it, invoke entry editor
|
||||
m_editedEntry = entry;
|
||||
m_editedPassword = entry->password();
|
||||
m_editedKnownBad = isKnownBad(entry);
|
||||
emit entryActivated(const_cast<Entry*>(entry));
|
||||
}
|
||||
}
|
||||
@ -253,8 +319,13 @@ void ReportsWidgetHibp::refreshAfterEdit()
|
||||
return;
|
||||
}
|
||||
|
||||
// No need to re-validate if there was no change
|
||||
if (m_editedEntry->password() == m_editedPassword) {
|
||||
// No need to re-validate if there was no change that affects
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -270,6 +341,57 @@ void ReportsWidgetHibp::refreshAfterEdit()
|
||||
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()
|
||||
{
|
||||
// nothing to do - the tab is passive
|
||||
|
@ -58,9 +58,12 @@ public slots:
|
||||
void emitEntryActivated(const QModelIndex&);
|
||||
void addHibpResult(const QString&, int);
|
||||
void fetchFailed(const QString& error);
|
||||
void makeHibpTable();
|
||||
void customMenuRequested(QPoint);
|
||||
void editFromContextmenu();
|
||||
void toggleKnownBad(bool);
|
||||
|
||||
private:
|
||||
void makeHibpTable();
|
||||
void startValidation();
|
||||
static QString countToText(int count);
|
||||
|
||||
@ -73,6 +76,8 @@ private:
|
||||
QList<const Entry*> m_rowToEntry; // List index is table row
|
||||
QPointer<const Entry> m_editedEntry; // The entry we're currently 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
|
||||
HibpDownloader m_downloader; // This performs the actual HIBP online query
|
||||
|
@ -11,176 +11,218 @@
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<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="groupBox">
|
||||
<property name="title">
|
||||
<string>Have I Been Pwned?</string>
|
||||
<widget class="QStackedWidget" name="stackedWidget">
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stackedWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="confirmation">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<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>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>450</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CAUTION: This report requires sending information to the Have I Been Pwned online service (https://haveibeenpwned.com). If you proceed, your database passwords will be cryptographically hashed and the first five characters of those hashes will be sent securely to this service. Your database remains secure and cannot be reconstituted from this information. However, the number of passwords you send and your IP address will be exposed to this service.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="validationButton">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>275</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Perform Online Analysis</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="QWidget" name="confirmation">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<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>
|
||||
<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>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>450</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CAUTION: This report requires sending information to the Have I Been Pwned online service (https://haveibeenpwned.com). If you proceed, your database passwords will be cryptographically hashed and the first five characters of those hashes will be sent securely to this service. Your database remains secure and cannot be reconstituted from this information. However, the number of passwords you send and your IP address will be exposed to this service.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="validationButton">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>275</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Perform Online Analysis</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<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>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QWidget" name="resultsTable">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<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="QTableView" name="hibpTableView">
|
||||
<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="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="resultsTable">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<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="QTableView" name="hibpTableView">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<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>
|
||||
<widget class="QWidget" name="noNetwork">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="networkNoticeLabel">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>450</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>This build of KeePassXC does not have network functions. Networking is required to check your passwords against Have I Been Pwned databases.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showKnownBadCheckBox">
|
||||
<property name="text">
|
||||
<string>Also show entries that have been excluded from reports</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="noNetwork">
|
||||
<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>
|
||||
<widget class="QLabel" name="networkNoticeLabel">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>450</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>This build of KeePassXC does not have network functions. Networking is required to check your passwords against Have I Been Pwned databases.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include "core/AsyncTask.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Global.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "core/PasswordHealth.h"
|
||||
@ -43,6 +44,7 @@ namespace
|
||||
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
|
||||
@ -138,6 +140,11 @@ namespace
|
||||
++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]++;
|
||||
}
|
||||
@ -235,6 +242,11 @@ void ReportsWidgetStatistics::calculateStats()
|
||||
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(),
|
||||
|
@ -6,11 +6,11 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>327</width>
|
||||
<width>397</width>
|
||||
<height>379</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
@ -24,52 +24,43 @@
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="statisticsGroupBox">
|
||||
<property name="title">
|
||||
<string>Statistics</string>
|
||||
<widget class="QTableView" name="statisticsTableView">
|
||||
<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="tipLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Hover over lines with error icons for further information.</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QTableView" name="statisticsTableView">
|
||||
<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="tipLabel">
|
||||
<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>
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include "core/Entry.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "core/PasswordHealth.h"
|
||||
#include "core/Tools.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "crypto/kdf/AesKdf.h"
|
||||
@ -442,6 +443,17 @@ void TestGui::testEditEntry()
|
||||
QCOMPARE(entry->historyItems().size(), ++editCount);
|
||||
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)
|
||||
editEntryWidget->setCurrentPage(1);
|
||||
auto fgColor = QString("#FF0000");
|
||||
|
@ -117,6 +117,8 @@ map() {
|
||||
preferences-other) echo file-document-edit-outline ;;
|
||||
preferences-desktop-icons) echo emoticon-happy-outline ;;
|
||||
preferences-system-network-sharing) echo lan ;;
|
||||
reports) echo lightbulb-on-outline ;;
|
||||
reports-exclude) echo lightbulb-off-outline ;;
|
||||
security-high) echo shield-outline ;;
|
||||
sort-alphabetical-ascending) echo sort-alphabetical-ascending ;;
|
||||
sort-alphabetical-descending) echo sort-alphabetical-descending ;;
|
||||
|
Loading…
x
Reference in New Issue
Block a user