mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-02-02 09:34:58 -05:00
Add entry view column for password strength
* Closes #4216 Reduced to three-tiered rating system and fixed column implementation. Hide password strength indicator in entry view if excluded from reports. Introduce password health caching to prevent unnecessary calculations.
This commit is contained in:
parent
c9c19d043f
commit
022154462e
1
COPYING
1
COPYING
@ -166,6 +166,7 @@ Files: share/icons/application/scalable/actions/chevron-double-down.svg
|
||||
share/icons/application/scalable/actions/group-new.svg
|
||||
share/icons/application/scalable/actions/help-about.svg
|
||||
share/icons/application/scalable/actions/key-enter.svg
|
||||
share/icons/application/scalable/actions/lock-question.svg
|
||||
share/icons/application/scalable/actions/message-close.svg
|
||||
share/icons/application/scalable/actions/move-down.svg
|
||||
share/icons/application/scalable/actions/move-up.svg
|
||||
|
@ -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" width="24" height="24" viewBox="0 0 24 24"><path d="M12,1A5,5 0 0,0 7,6V8H6A2,2 0 0,0 4,10V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V10A2,2 0 0,0 18,8H17V6A5,5 0 0,0 12,1M12,2.9C13.71,2.9 15.1,4.29 15.1,6V8H8.9V6C8.9,4.29 10.29,2.9 12,2.9M12.19,10.5C13.13,10.5 13.88,10.71 14.42,11.12C14.96,11.54 15.23,12.1 15.23,12.8C15.23,13.24 15.08,13.63 14.79,14C14.5,14.36 14.12,14.64 13.66,14.85C13.4,15 13.23,15.15 13.14,15.32C13.05,15.5 13,15.72 13,16H11C11,15.5 11.1,15.16 11.29,14.92C11.5,14.68 11.84,14.4 12.36,14.08C12.62,13.94 12.83,13.76 13,13.54C13.14,13.33 13.22,13.08 13.22,12.8C13.22,12.5 13.13,12.28 12.95,12.11C12.77,11.93 12.5,11.85 12.19,11.85C11.92,11.85 11.7,11.92 11.5,12.06C11.34,12.2 11.24,12.41 11.24,12.69H9.27C9.22,12 9.5,11.4 10.05,11.04C10.59,10.68 11.3,10.5 12.19,10.5M11,17H13V19H11V17Z" /></svg>
|
After Width: | Height: | Size: 1.0 KiB |
@ -46,6 +46,7 @@
|
||||
<file>application/scalable/actions/help-about.svg</file>
|
||||
<file>application/scalable/actions/hibp.svg</file>
|
||||
<file>application/scalable/actions/key-enter.svg</file>
|
||||
<file>application/scalable/actions/lock-question.svg</file>
|
||||
<file>application/scalable/actions/keyboard-shortcuts.svg</file>
|
||||
<file>application/scalable/actions/message-close.svg</file>
|
||||
<file>application/scalable/actions/move-down.svg</file>
|
||||
|
@ -172,6 +172,6 @@ int Estimate::execute(const QStringList& arguments)
|
||||
password = in.readLine();
|
||||
}
|
||||
|
||||
estimate(password.toLatin1(), parser->isSet(Estimate::AdvancedOption));
|
||||
estimate(password.toUtf8(), parser->isSet(Estimate::AdvancedOption));
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ const QString CustomData::LastModified = QStringLiteral("_LAST_MODIFIED");
|
||||
const QString CustomData::Created = QStringLiteral("_CREATED");
|
||||
const QString CustomData::BrowserKeyPrefix = QStringLiteral("KPXC_BROWSER_");
|
||||
const QString CustomData::BrowserLegacyKeyPrefix = QStringLiteral("Public Key: ");
|
||||
const QString CustomData::ExcludeFromReports = QStringLiteral("KnownBad");
|
||||
|
||||
CustomData::CustomData(QObject* parent)
|
||||
: QObject(parent)
|
||||
|
@ -51,6 +51,7 @@ public:
|
||||
static const QString Created;
|
||||
static const QString BrowserKeyPrefix;
|
||||
static const QString BrowserLegacyKeyPrefix;
|
||||
static const QString ExcludeFromReports;
|
||||
|
||||
signals:
|
||||
void customDataModified();
|
||||
|
@ -17,14 +17,12 @@
|
||||
*/
|
||||
#include "Entry.h"
|
||||
|
||||
#include "config-keepassx.h"
|
||||
|
||||
#include "core/Clock.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/DatabaseIcons.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "core/PasswordHealth.h"
|
||||
#include "core/Tools.h"
|
||||
#include "totp/totp.h"
|
||||
|
||||
@ -245,6 +243,25 @@ QString Entry::defaultAutoTypeSequence() const
|
||||
return m_data.defaultAutoTypeSequence;
|
||||
}
|
||||
|
||||
const QSharedPointer<PasswordHealth>& Entry::passwordHealth()
|
||||
{
|
||||
if (!m_data.passwordHealth) {
|
||||
m_data.passwordHealth.reset(new PasswordHealth(resolvePlaceholder(password())));
|
||||
}
|
||||
return m_data.passwordHealth;
|
||||
}
|
||||
|
||||
bool Entry::excludeFromReports() const
|
||||
{
|
||||
return customData()->contains(CustomData::ExcludeFromReports)
|
||||
&& customData()->value(CustomData::ExcludeFromReports) == TRUE_STR;
|
||||
}
|
||||
|
||||
void Entry::setExcludeFromReports(bool state)
|
||||
{
|
||||
customData()->set(CustomData::ExcludeFromReports, state ? TRUE_STR : FALSE_STR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the effective sequence that will be injected
|
||||
* This function return an empty string if a parent group has autotype disabled or if the entry has no parent
|
||||
@ -673,6 +690,8 @@ void Entry::setUsername(const QString& username)
|
||||
|
||||
void Entry::setPassword(const QString& password)
|
||||
{
|
||||
// Reset Password Health
|
||||
m_data.passwordHealth.reset();
|
||||
m_attributes->set(EntryAttributes::PasswordKey, password, m_attributes->isProtected(EntryAttributes::PasswordKey));
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,8 @@
|
||||
|
||||
class Database;
|
||||
class Group;
|
||||
class PasswordHealth;
|
||||
|
||||
namespace Totp
|
||||
{
|
||||
struct Settings;
|
||||
@ -66,6 +68,7 @@ struct EntryData
|
||||
QString defaultAutoTypeSequence;
|
||||
TimeInfo timeInfo;
|
||||
QSharedPointer<Totp::Settings> totpSettings;
|
||||
QSharedPointer<PasswordHealth> passwordHealth;
|
||||
|
||||
bool operator==(const EntryData& other) const;
|
||||
bool operator!=(const EntryData& other) const;
|
||||
@ -94,7 +97,6 @@ public:
|
||||
int autoTypeObfuscation() const;
|
||||
QString defaultAutoTypeSequence() const;
|
||||
QString effectiveAutoTypeSequence() const;
|
||||
QString effectiveNewAutoTypeSequence() const;
|
||||
QList<QString> autoTypeSequences(const QString& pattern = {}) const;
|
||||
AutoTypeAssociations* autoTypeAssociations();
|
||||
const AutoTypeAssociations* autoTypeAssociations() const;
|
||||
@ -111,6 +113,9 @@ public:
|
||||
QSharedPointer<Totp::Settings> totpSettings() const;
|
||||
int size() const;
|
||||
QString path() const;
|
||||
const QSharedPointer<PasswordHealth>& passwordHealth();
|
||||
bool excludeFromReports() const;
|
||||
void setExcludeFromReports(bool state);
|
||||
|
||||
bool hasTotp() const;
|
||||
bool isExpired() const;
|
||||
|
@ -24,9 +24,6 @@
|
||||
#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)
|
||||
@ -49,8 +46,8 @@ PasswordHealth::PasswordHealth(double entropy)
|
||||
}
|
||||
}
|
||||
|
||||
PasswordHealth::PasswordHealth(QString pwd)
|
||||
: PasswordHealth(ZxcvbnMatch(pwd.toLatin1(), nullptr, nullptr))
|
||||
PasswordHealth::PasswordHealth(const QString& pwd)
|
||||
: PasswordHealth(ZxcvbnMatch(pwd.toUtf8(), nullptr, nullptr))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ class PasswordHealth
|
||||
{
|
||||
public:
|
||||
explicit PasswordHealth(double entropy);
|
||||
explicit PasswordHealth(QString pwd);
|
||||
explicit PasswordHealth(const QString& pwd);
|
||||
|
||||
/*
|
||||
* The password score is defined to be the greater the better
|
||||
@ -83,14 +83,6 @@ 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;
|
||||
|
@ -440,7 +440,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->excludeReportsCheckBox, 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()));
|
||||
@ -861,9 +861,7 @@ 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);
|
||||
m_advancedUi->excludeReportsCheckBox->setChecked(entry->excludeFromReports());
|
||||
setupColorButton(true, entry->foregroundColor());
|
||||
setupColorButton(false, entry->backgroundColor());
|
||||
m_iconsWidget->setEnabled(!m_history);
|
||||
@ -1126,11 +1124,8 @@ 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 (entry->excludeFromReports() != m_advancedUi->excludeReportsCheckBox->isChecked()) {
|
||||
entry->setExcludeFromReports(m_advancedUi->excludeReportsCheckBox->isChecked());
|
||||
}
|
||||
|
||||
if (m_advancedUi->fgColorCheckBox->isChecked() && m_advancedUi->fgColorButton->property("color").isValid()) {
|
||||
|
@ -187,9 +187,9 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="knownBadCheckBox">
|
||||
<widget class="QCheckBox" name="excludeReportsCheckBox">
|
||||
<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>
|
||||
<string>If checked, the entry will not appear in reports like Health Check and HIBP even if it doesn't match the quality requirements.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Exclude from database reports</string>
|
||||
@ -327,7 +327,7 @@
|
||||
<tabstop>editAttributeButton</tabstop>
|
||||
<tabstop>protectAttributeButton</tabstop>
|
||||
<tabstop>revealAttributeButton</tabstop>
|
||||
<tabstop>knownBadCheckBox</tabstop>
|
||||
<tabstop>excludeReportsCheckBox</tabstop>
|
||||
<tabstop>fgColorCheckBox</tabstop>
|
||||
<tabstop>fgColorButton</tabstop>
|
||||
<tabstop>bgColorCheckBox</tabstop>
|
||||
|
@ -18,18 +18,17 @@
|
||||
#include "EntryModel.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QFont>
|
||||
#include <QMimeData>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
|
||||
#include "core/Config.h"
|
||||
#include "core/DatabaseIcons.h"
|
||||
#include "core/Entry.h"
|
||||
#include "core/Global.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "core/PasswordHealth.h"
|
||||
#include "gui/Icons.h"
|
||||
#include "gui/styles/StateColorPalette.h"
|
||||
#ifdef Q_OS_MACOS
|
||||
#include "gui/osutils/macutils/MacUtils.h"
|
||||
#endif
|
||||
@ -128,7 +127,7 @@ int EntryModel::columnCount(const QModelIndex& parent) const
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 14;
|
||||
return 15;
|
||||
}
|
||||
|
||||
QVariant EntryModel::data(const QModelIndex& index, int role) const
|
||||
@ -249,6 +248,12 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
|
||||
return entry->resolveMultiplePlaceholders(entry->username());
|
||||
case Password:
|
||||
return entry->resolveMultiplePlaceholders(entry->password());
|
||||
case PasswordStrength: {
|
||||
if (!entry->password().isEmpty() && !entry->excludeFromReports()) {
|
||||
return entry->passwordHealth()->score();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case Expires:
|
||||
// There seems to be no better way of expressing 'infinity'
|
||||
return entry->timeInfo().expires() ? entry->timeInfo().expiryTime() : QDateTime(QDate(9999, 1, 1));
|
||||
@ -290,6 +295,28 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
|
||||
return icons()->icon("chronometer");
|
||||
}
|
||||
break;
|
||||
case PasswordStrength:
|
||||
if (!entry->password().isEmpty() && !entry->excludeFromReports()) {
|
||||
StateColorPalette statePalette;
|
||||
QColor color = statePalette.color(StateColorPalette::Error);
|
||||
|
||||
switch (entry->passwordHealth()->quality()) {
|
||||
case PasswordHealth::Quality::Bad:
|
||||
case PasswordHealth::Quality::Poor:
|
||||
color = statePalette.color(StateColorPalette::HealthCritical);
|
||||
break;
|
||||
case PasswordHealth::Quality::Weak:
|
||||
color = statePalette.color(StateColorPalette::HealthBad);
|
||||
break;
|
||||
case PasswordHealth::Quality::Good:
|
||||
case PasswordHealth::Quality::Excellent:
|
||||
color = statePalette.color(StateColorPalette::HealthExcellent);
|
||||
break;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (role == Qt::FontRole) {
|
||||
QFont font;
|
||||
@ -316,9 +343,9 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
|
||||
if (backgroundColor.isValid()) {
|
||||
return QVariant(backgroundColor);
|
||||
}
|
||||
} else if (role == Qt::TextAlignmentRole) {
|
||||
if (index.column() == Paperclip) {
|
||||
return Qt::AlignCenter;
|
||||
} else if (role == Qt::ToolTipRole) {
|
||||
if (index.column() == PasswordStrength && !entry->password().isEmpty() && !entry->excludeFromReports()) {
|
||||
return entry->passwordHealth()->scoreReason();
|
||||
}
|
||||
}
|
||||
|
||||
@ -363,6 +390,8 @@ QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int ro
|
||||
return icons()->icon("paperclip");
|
||||
case Totp:
|
||||
return icons()->icon("chronometer");
|
||||
case PasswordStrength:
|
||||
return icons()->icon("lock-question");
|
||||
}
|
||||
} else if (role == Qt::ToolTipRole) {
|
||||
switch (section) {
|
||||
@ -374,6 +403,8 @@ QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int ro
|
||||
return tr("Username");
|
||||
case Password:
|
||||
return tr("Password");
|
||||
case PasswordStrength:
|
||||
return tr("Password Strength");
|
||||
case Url:
|
||||
return tr("URL");
|
||||
case Notes:
|
||||
@ -393,7 +424,7 @@ QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int ro
|
||||
case Paperclip:
|
||||
return tr("Has attachments");
|
||||
case Totp:
|
||||
return tr("Has TOTP one-time password");
|
||||
return tr("Has TOTP");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,8 @@ public:
|
||||
Paperclip = 10,
|
||||
Attachments = 11,
|
||||
Totp = 12,
|
||||
Size = 13
|
||||
Size = 13,
|
||||
PasswordStrength = 14
|
||||
};
|
||||
|
||||
explicit EntryModel(QObject* parent = nullptr);
|
||||
|
@ -20,12 +20,42 @@
|
||||
|
||||
#include <QAccessible>
|
||||
#include <QHeaderView>
|
||||
#include <QKeyEvent>
|
||||
#include <QMenu>
|
||||
#include <QPainter>
|
||||
#include <QShortcut>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
#include "gui/SortFilterHideProxyModel.h"
|
||||
|
||||
#define ICON_ONLY_SECTION_SIZE 26
|
||||
|
||||
class PasswordStrengthItemDelegate : public QStyledItemDelegate
|
||||
{
|
||||
public:
|
||||
explicit PasswordStrengthItemDelegate(QObject* parent)
|
||||
: QStyledItemDelegate(parent){};
|
||||
|
||||
void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override
|
||||
{
|
||||
QStyledItemDelegate::initStyleOption(option, index);
|
||||
auto value = index.data(Qt::DecorationRole);
|
||||
if (value.isValid() && value.type() == QVariant::Color && option->rect.width() > 0) {
|
||||
// Rebuild the password strength icon to add a dark border
|
||||
QColor pen(Qt::black);
|
||||
if (option->widget) {
|
||||
pen = option->widget->palette().color(QPalette::Shadow);
|
||||
}
|
||||
auto size = option->decorationSize;
|
||||
QImage image(size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
|
||||
QPainter p(&image);
|
||||
p.setBrush(value.value<QColor>());
|
||||
p.setPen(pen);
|
||||
p.drawRect(0, 0, size.width() - 1, size.height() - 1);
|
||||
option->icon = QIcon(QPixmap::fromImage(image));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
EntryView::EntryView(QWidget* parent)
|
||||
: QTreeView(parent)
|
||||
, m_model(new EntryModel(this))
|
||||
@ -41,6 +71,7 @@ EntryView::EntryView(QWidget* parent)
|
||||
// Use Qt::UserRole as sort role, see EntryModel::data()
|
||||
m_sortModel->setSortRole(Qt::UserRole);
|
||||
QTreeView::setModel(m_sortModel);
|
||||
QTreeView::setItemDelegateForColumn(EntryModel::PasswordStrength, new PasswordStrengthItemDelegate(this));
|
||||
|
||||
setUniformRowHeights(true);
|
||||
setRootIsDecorated(false);
|
||||
@ -52,10 +83,10 @@ EntryView::EntryView(QWidget* parent)
|
||||
// QAbstractItemView::startDrag() uses this property as the default drag action
|
||||
setDefaultDropAction(Qt::MoveAction);
|
||||
|
||||
// clang-format off
|
||||
connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex)));
|
||||
connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(emitEntrySelectionChanged()));
|
||||
// clang-format on
|
||||
connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, [this] {
|
||||
emit entrySelectionChanged(currentEntry());
|
||||
});
|
||||
|
||||
new QShortcut(Qt::CTRL + Qt::Key_F10, this, SLOT(contextMenuShortcutPressed()), nullptr, Qt::WidgetShortcut);
|
||||
|
||||
@ -68,13 +99,11 @@ EntryView::EntryView(QWidget* parent)
|
||||
for (int visualIndex = 1; visualIndex < header()->count(); ++visualIndex) {
|
||||
int logicalIndex = header()->logicalIndex(visualIndex);
|
||||
QString caption = m_model->headerData(logicalIndex, Qt::Horizontal, Qt::DisplayRole).toString();
|
||||
if (logicalIndex == EntryModel::Paperclip) {
|
||||
caption = tr("Has attachments", "Entry attachment icon toggle");
|
||||
} else if (logicalIndex == EntryModel::Totp) {
|
||||
caption = tr("Has TOTP", "Entry TOTP icon toggle");
|
||||
if (caption.isEmpty()) {
|
||||
caption = m_model->headerData(logicalIndex, Qt::Horizontal, Qt::ToolTipRole).toString();
|
||||
}
|
||||
|
||||
QAction* action = m_headerMenu->addAction(caption);
|
||||
auto action = m_headerMenu->addAction(caption);
|
||||
action->setCheckable(true);
|
||||
action->setData(logicalIndex);
|
||||
m_columnActions->addAction(action);
|
||||
@ -82,7 +111,8 @@ EntryView::EntryView(QWidget* parent)
|
||||
connect(m_columnActions, SIGNAL(triggered(QAction*)), this, SLOT(toggleColumnVisibility(QAction*)));
|
||||
connect(header(), &QHeaderView::sortIndicatorChanged, [this](int index, Qt::SortOrder order) {
|
||||
Q_UNUSED(order)
|
||||
header()->setSortIndicatorShown(index != EntryModel::Paperclip && index != EntryModel::Totp);
|
||||
header()->setSortIndicatorShown(index != EntryModel::Paperclip && index != EntryModel::Totp
|
||||
&& index != EntryModel::PasswordStrength);
|
||||
});
|
||||
|
||||
m_headerMenu->addSeparator();
|
||||
@ -101,8 +131,6 @@ EntryView::EntryView(QWidget* parent)
|
||||
connect(header(), SIGNAL(sectionMoved(int, int, int)), SIGNAL(viewStateChanged()));
|
||||
connect(header(), SIGNAL(sectionResized(int, int, int)), SIGNAL(viewStateChanged()));
|
||||
connect(header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), SLOT(sortIndicatorChanged(int, Qt::SortOrder)));
|
||||
|
||||
// clang-format off
|
||||
}
|
||||
|
||||
void EntryView::contextMenuShortcutPressed()
|
||||
@ -130,10 +158,10 @@ void EntryView::sortIndicatorChanged(int logicalIndex, Qt::SortOrder order)
|
||||
// do not emit any signals, header()->setSortIndicator recursively calls this
|
||||
// function and the signals are emitted in the else part
|
||||
} else {
|
||||
// call emitEntrySelectionChanged even though the selection did not really change
|
||||
// emit entrySelectionChanged even though the selection did not really change
|
||||
// this triggers the evaluation of the menu activation and anyway, the position
|
||||
// of the selected entry within the widget did change
|
||||
emitEntrySelectionChanged();
|
||||
emit entrySelectionChanged(currentEntry());
|
||||
emit viewStateChanged();
|
||||
}
|
||||
}
|
||||
@ -228,11 +256,6 @@ void EntryView::emitEntryActivated(const QModelIndex& index)
|
||||
emit entryActivated(entry, static_cast<EntryModel::ModelColumn>(m_sortModel->mapToSource(index).column()));
|
||||
}
|
||||
|
||||
void EntryView::emitEntrySelectionChanged()
|
||||
{
|
||||
emit entrySelectionChanged(currentEntry());
|
||||
}
|
||||
|
||||
void EntryView::setModel(QAbstractItemModel* model)
|
||||
{
|
||||
Q_UNUSED(model);
|
||||
@ -391,18 +414,15 @@ void EntryView::fitColumnsToContents()
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark icon-only columns as fixed and resize them to their minimum section size.
|
||||
* Mark icon-only columns as fixed and resize them to icon-only section size
|
||||
*/
|
||||
void EntryView::resetFixedColumns()
|
||||
{
|
||||
if (!isColumnHidden(EntryModel::Paperclip)) {
|
||||
header()->setSectionResizeMode(EntryModel::Paperclip, QHeaderView::Fixed);
|
||||
header()->resizeSection(EntryModel::Paperclip, header()->minimumSectionSize());
|
||||
for (const auto& col : {EntryModel::Paperclip, EntryModel::Totp, EntryModel::PasswordStrength}) {
|
||||
if (!isColumnHidden(col)) {
|
||||
header()->setSectionResizeMode(col, QHeaderView::Fixed);
|
||||
header()->resizeSection(col, ICON_ONLY_SECTION_SIZE);
|
||||
}
|
||||
|
||||
if (!isColumnHidden(EntryModel::Totp)) {
|
||||
header()->setSectionResizeMode(EntryModel::Totp, QHeaderView::Fixed);
|
||||
header()->resizeSection(EntryModel::Totp, header()->minimumSectionSize());
|
||||
}
|
||||
}
|
||||
|
||||
@ -431,6 +451,7 @@ void EntryView::resetViewToDefaults()
|
||||
header()->hideSection(EntryModel::Accessed);
|
||||
header()->hideSection(EntryModel::Attachments);
|
||||
header()->hideSection(EntryModel::Size);
|
||||
header()->hideSection(EntryModel::PasswordStrength);
|
||||
|
||||
// Reset column order to logical indices
|
||||
for (int i = 0; i < header()->count(); ++i) {
|
||||
|
@ -63,7 +63,6 @@ protected:
|
||||
|
||||
private slots:
|
||||
void emitEntryActivated(const QModelIndex& index);
|
||||
void emitEntrySelectionChanged();
|
||||
void showHeaderMenu(const QPoint& position);
|
||||
void toggleColumnVisibility(QAction* action);
|
||||
void fitColumnsToWindow();
|
||||
|
@ -41,14 +41,13 @@ namespace
|
||||
QPointer<const Group> group;
|
||||
QPointer<const Entry> entry;
|
||||
QSharedPointer<PasswordHealth> health;
|
||||
bool knownBad = false;
|
||||
bool exclude = 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)
|
||||
, exclude(e->excludeFromReports())
|
||||
{
|
||||
}
|
||||
|
||||
@ -121,7 +120,7 @@ Health::Health(QSharedPointer<Database> db)
|
||||
|
||||
// Evaluate this entry
|
||||
const auto item = QSharedPointer<Item>(new Item(group, entry, m_checker.evaluate(entry)));
|
||||
if (item->knownBad) {
|
||||
if (item->exclude) {
|
||||
m_anyKnownBad = true;
|
||||
}
|
||||
|
||||
@ -140,7 +139,6 @@ Health::Health(QSharedPointer<Database> db)
|
||||
ReportsWidgetHealthcheck::ReportsWidgetHealthcheck(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_ui(new Ui::ReportsWidgetHealthcheck())
|
||||
, m_errorIcon(icons()->icon("dialog-error"))
|
||||
, m_referencesModel(new QStandardItemModel(this))
|
||||
, m_modelProxy(new ReportSortProxyModel(this))
|
||||
{
|
||||
@ -258,18 +256,18 @@ void ReportsWidgetHealthcheck::calculateHealth()
|
||||
const QScopedPointer<Health> health(AsyncTask::runAndWaitForFuture([this] { return new Health(m_db); }));
|
||||
|
||||
// Display entries that are marked as "known bad"?
|
||||
const auto showKnownBad = m_ui->showKnownBadCheckBox->isChecked();
|
||||
const auto showExcluded = m_ui->showKnownBadCheckBox->isChecked();
|
||||
|
||||
// Display the entries
|
||||
m_rowToEntry.clear();
|
||||
for (const auto& item : health->items()) {
|
||||
if (item->knownBad && !showKnownBad) {
|
||||
if (item->exclude && !showExcluded) {
|
||||
// Exclude this entry from the report
|
||||
continue;
|
||||
}
|
||||
|
||||
// Show the entry in the report
|
||||
addHealthRow(item->health, item->group, item->entry, item->knownBad);
|
||||
addHealthRow(item->health, item->group, item->entry, item->exclude);
|
||||
}
|
||||
|
||||
// Set the table header
|
||||
@ -330,12 +328,16 @@ void ReportsWidgetHealthcheck::customMenuRequested(QPoint pos)
|
||||
connect(edit, SIGNAL(triggered()), SLOT(editFromContextmenu()));
|
||||
|
||||
// Create the "exclude from reports" menu item
|
||||
const auto knownbad = new QAction(icons()->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)));
|
||||
const auto exclude = new QAction(icons()->icon("reports-exclude"), tr("Exclude from reports"), this);
|
||||
exclude->setCheckable(true);
|
||||
exclude->setChecked(m_contextmenuEntry->excludeFromReports());
|
||||
menu->addAction(exclude);
|
||||
connect(exclude, &QAction::toggled, exclude, [this](bool state) {
|
||||
if (m_contextmenuEntry) {
|
||||
m_contextmenuEntry->setExcludeFromReports(state);
|
||||
calculateHealth();
|
||||
}
|
||||
});
|
||||
|
||||
// Show the context menu
|
||||
menu->popup(m_ui->healthcheckTableView->viewport()->mapToGlobal(pos));
|
||||
@ -348,17 +350,6 @@ void ReportsWidgetHealthcheck::editFromContextmenu()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -41,7 +41,7 @@ class ReportsWidgetHealthcheck : public QWidget
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ReportsWidgetHealthcheck(QWidget* parent = nullptr);
|
||||
~ReportsWidgetHealthcheck();
|
||||
~ReportsWidgetHealthcheck() override;
|
||||
|
||||
void loadSettings(QSharedPointer<Database> db);
|
||||
void saveSettings();
|
||||
@ -57,7 +57,6 @@ public slots:
|
||||
void emitEntryActivated(const QModelIndex& index);
|
||||
void customMenuRequested(QPoint);
|
||||
void editFromContextmenu();
|
||||
void toggleKnownBad(bool);
|
||||
|
||||
private:
|
||||
void addHealthRow(QSharedPointer<PasswordHealth>, const Group*, const Entry*, bool knownBad);
|
||||
@ -65,7 +64,6 @@ private:
|
||||
QScopedPointer<Ui::ReportsWidgetHealthcheck> m_ui;
|
||||
|
||||
bool m_healthCalculated = false;
|
||||
QIcon m_errorIcon;
|
||||
QScopedPointer<QStandardItemModel> m_referencesModel;
|
||||
QScopedPointer<QSortFilterProxyModel> m_modelProxy;
|
||||
QSharedPointer<Database> m_db;
|
||||
|
@ -32,20 +32,6 @@
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
class ReportSortProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
public:
|
||||
@ -153,25 +139,23 @@ void ReportsWidgetHibp::makeHibpTable()
|
||||
});
|
||||
|
||||
// Display entries that are marked as "known bad"?
|
||||
const auto showKnownBad = m_ui->showKnownBadCheckBox->isChecked();
|
||||
const auto showExcluded = m_ui->showKnownBadCheckBox->isChecked();
|
||||
|
||||
// The colors for table cells
|
||||
const auto red = QBrush("red");
|
||||
|
||||
// Build the table
|
||||
bool anyKnownBad = false;
|
||||
bool anyExcluded = 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) {
|
||||
// Hide entry if excluded unless explicitly requested
|
||||
if (entry->excludeFromReports()) {
|
||||
anyExcluded = true;
|
||||
if (!showExcluded) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -183,7 +167,7 @@ void ReportsWidgetHibp::makeHibpTable()
|
||||
<< new QStandardItem(group->iconPixmap(), group->hierarchy().join("/"))
|
||||
<< new QStandardItem(countToText(count));
|
||||
|
||||
if (knownBad) {
|
||||
if (entry->excludeFromReports()) {
|
||||
row[1]->setToolTip(tr("This entry is being excluded from reports"));
|
||||
}
|
||||
|
||||
@ -213,7 +197,7 @@ void ReportsWidgetHibp::makeHibpTable()
|
||||
|
||||
// Show the "show known bad entries" checkbox if there's any known
|
||||
// bad entry in the database.
|
||||
if (anyKnownBad) {
|
||||
if (anyExcluded) {
|
||||
m_ui->showKnownBadCheckBox->show();
|
||||
} else {
|
||||
m_ui->showKnownBadCheckBox->hide();
|
||||
@ -331,7 +315,7 @@ void ReportsWidgetHibp::emitEntryActivated(const QModelIndex& index)
|
||||
// Found it, invoke entry editor
|
||||
m_editedEntry = entry;
|
||||
m_editedPassword = entry->password();
|
||||
m_editedKnownBad = isKnownBad(entry);
|
||||
m_editedExcluded = entry->excludeFromReports();
|
||||
emit entryActivated(const_cast<Entry*>(entry));
|
||||
}
|
||||
}
|
||||
@ -350,7 +334,7 @@ void ReportsWidgetHibp::refreshAfterEdit()
|
||||
// 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) {
|
||||
if (m_editedEntry->password() == m_editedPassword && m_editedEntry->excludeFromReports() == m_editedExcluded) {
|
||||
// Don't go through HIBP but still rebuild the table, the user might
|
||||
// have edited the entry title.
|
||||
makeHibpTable();
|
||||
@ -392,12 +376,16 @@ void ReportsWidgetHibp::customMenuRequested(QPoint pos)
|
||||
connect(edit, SIGNAL(triggered()), SLOT(editFromContextmenu()));
|
||||
|
||||
// Create the "exclude from reports" menu item
|
||||
const auto knownbad = new QAction(icons()->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)));
|
||||
const auto exclude = new QAction(icons()->icon("reports-exclude"), tr("Exclude from reports"), this);
|
||||
exclude->setCheckable(true);
|
||||
exclude->setChecked(m_contextmenuEntry->excludeFromReports());
|
||||
menu->addAction(exclude);
|
||||
connect(exclude, &QAction::toggled, exclude, [this](bool state) {
|
||||
if (m_contextmenuEntry) {
|
||||
m_contextmenuEntry->setExcludeFromReports(state);
|
||||
makeHibpTable();
|
||||
}
|
||||
});
|
||||
|
||||
// Show the context menu
|
||||
menu->popup(m_ui->hibpTableView->viewport()->mapToGlobal(pos));
|
||||
@ -410,17 +398,6 @@ void ReportsWidgetHibp::editFromContextmenu()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -62,7 +62,6 @@ public slots:
|
||||
void makeHibpTable();
|
||||
void customMenuRequested(QPoint);
|
||||
void editFromContextmenu();
|
||||
void toggleKnownBad(bool);
|
||||
|
||||
private:
|
||||
void startValidation();
|
||||
@ -78,7 +77,7 @@ 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
|
||||
bool m_editedExcluded; // 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
|
||||
|
@ -26,8 +26,6 @@
|
||||
#include "core/PasswordHealth.h"
|
||||
#include "gui/Icons.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QHash>
|
||||
#include <QStandardItemModel>
|
||||
|
||||
namespace
|
||||
@ -37,15 +35,15 @@ namespace
|
||||
public:
|
||||
// The statistics we collect:
|
||||
QDateTime modified; // File modification time
|
||||
int nGroups = 0; // Number of groups in the database
|
||||
int nEntries = 0; // Number of entries (across all groups)
|
||||
int nExpired = 0; // Number of expired entries
|
||||
int nPwdsWeak = 0; // Number of weak or poor passwords
|
||||
int nPwdsShort = 0; // Number of passwords 8 characters or less in size
|
||||
int nPwdsUnique = 0; // Number of unique passwords
|
||||
int nPwdsReused = 0; // Number of non-unique passwords
|
||||
int nKnownBad = 0; // Number of known bad entries
|
||||
int pwdTotalLen = 0; // Total length of all passwords
|
||||
int groupCount = 0; // Number of groups in the database
|
||||
int entryCount = 0; // Number of entries (across all groups)
|
||||
int expiredEntries = 0; // Number of expired entries
|
||||
int excludedEntries = 0; // Number of known bad entries
|
||||
int weakPasswords = 0; // Number of weak or poor passwords
|
||||
int shortPasswords = 0; // Number of passwords 8 characters or less in size
|
||||
int uniquePasswords = 0; // Number of unique passwords
|
||||
int reusedPasswords = 0; // Number of non-unique passwords
|
||||
int totalPasswordLength = 0; // Total length of all passwords
|
||||
|
||||
// Ctor does all the work
|
||||
explicit Stats(QSharedPointer<Database> db)
|
||||
@ -58,8 +56,8 @@ namespace
|
||||
// Get average password length
|
||||
int averagePwdLength() const
|
||||
{
|
||||
const auto nPwds = nPwdsUnique + nPwdsReused;
|
||||
return nPwds == 0 ? 0 : std::round(pwdTotalLen / double(nPwds));
|
||||
const auto passwords = uniquePasswords + reusedPasswords;
|
||||
return passwords == 0 ? 0 : std::round(totalPasswordLength / double(passwords));
|
||||
}
|
||||
|
||||
// Get max number of password reuse (=how many entries
|
||||
@ -77,12 +75,12 @@ namespace
|
||||
// following returns true.
|
||||
bool isAnyExpired() const
|
||||
{
|
||||
return nExpired > 0;
|
||||
return expiredEntries > 0;
|
||||
}
|
||||
|
||||
bool areTooManyPwdsReused() const
|
||||
{
|
||||
return nPwdsReused > nPwdsUnique / 10;
|
||||
return reusedPasswords > uniquePasswords / 10;
|
||||
}
|
||||
|
||||
bool arePwdsReusedTooOften() const
|
||||
@ -109,7 +107,7 @@ namespace
|
||||
continue;
|
||||
}
|
||||
|
||||
++nGroups;
|
||||
++groupCount;
|
||||
|
||||
for (const auto* entry : group->entries()) {
|
||||
// Don't count anything in the recycle bin
|
||||
@ -117,36 +115,35 @@ namespace
|
||||
continue;
|
||||
}
|
||||
|
||||
++nEntries;
|
||||
++entryCount;
|
||||
|
||||
if (entry->isExpired()) {
|
||||
++nExpired;
|
||||
++expiredEntries;
|
||||
}
|
||||
|
||||
// Get password statistics
|
||||
const auto pwd = entry->password();
|
||||
if (!pwd.isEmpty()) {
|
||||
if (!m_passwords.contains(pwd)) {
|
||||
++nPwdsUnique;
|
||||
++uniquePasswords;
|
||||
} else {
|
||||
++nPwdsReused;
|
||||
++reusedPasswords;
|
||||
}
|
||||
|
||||
if (pwd.size() < 8) {
|
||||
++nPwdsShort;
|
||||
++shortPasswords;
|
||||
}
|
||||
|
||||
// Speed up Zxcvbn process by excluding very long passwords and most passphrases
|
||||
if (pwd.size() < 25 && checker.evaluate(entry)->quality() <= PasswordHealth::Quality::Weak) {
|
||||
++nPwdsWeak;
|
||||
++weakPasswords;
|
||||
}
|
||||
|
||||
if (entry->customData()->contains(PasswordHealth::OPTION_KNOWN_BAD)
|
||||
&& entry->customData()->value(PasswordHealth::OPTION_KNOWN_BAD) == TRUE_STR) {
|
||||
++nKnownBad;
|
||||
if (entry->excludeFromReports()) {
|
||||
++excludedEntries;
|
||||
}
|
||||
|
||||
pwdTotalLen += pwd.size();
|
||||
totalPasswordLength += pwd.size();
|
||||
m_passwords[pwd]++;
|
||||
}
|
||||
}
|
||||
@ -220,15 +217,15 @@ void ReportsWidgetStatistics::calculateStats()
|
||||
m_db->isModified() ? tr("yes") : tr("no"),
|
||||
m_db->isModified(),
|
||||
tr("The database was modified, but the changes have not yet been saved to disk."));
|
||||
addStatsRow(tr("Number of groups"), QString::number(stats->nGroups));
|
||||
addStatsRow(tr("Number of entries"), QString::number(stats->nEntries));
|
||||
addStatsRow(tr("Number of groups"), QString::number(stats->groupCount));
|
||||
addStatsRow(tr("Number of entries"), QString::number(stats->entryCount));
|
||||
addStatsRow(tr("Number of expired entries"),
|
||||
QString::number(stats->nExpired),
|
||||
QString::number(stats->expiredEntries),
|
||||
stats->isAnyExpired(),
|
||||
tr("The database contains entries that have expired."));
|
||||
addStatsRow(tr("Unique passwords"), QString::number(stats->nPwdsUnique));
|
||||
addStatsRow(tr("Unique passwords"), QString::number(stats->uniquePasswords));
|
||||
addStatsRow(tr("Non-unique passwords"),
|
||||
QString::number(stats->nPwdsReused),
|
||||
QString::number(stats->reusedPasswords),
|
||||
stats->areTooManyPwdsReused(),
|
||||
tr("More than 10% of passwords are reused. Use unique passwords when possible."));
|
||||
addStatsRow(tr("Maximum password reuse"),
|
||||
@ -236,16 +233,16 @@ void ReportsWidgetStatistics::calculateStats()
|
||||
stats->arePwdsReusedTooOften(),
|
||||
tr("Some passwords are used more than three times. Use unique passwords when possible."));
|
||||
addStatsRow(tr("Number of short passwords"),
|
||||
QString::number(stats->nPwdsShort),
|
||||
stats->nPwdsShort > 0,
|
||||
QString::number(stats->shortPasswords),
|
||||
stats->shortPasswords > 0,
|
||||
tr("Recommended minimum password length is at least 8 characters."));
|
||||
addStatsRow(tr("Number of weak passwords"),
|
||||
QString::number(stats->nPwdsWeak),
|
||||
stats->nPwdsWeak > 0,
|
||||
QString::number(stats->weakPasswords),
|
||||
stats->weakPasswords > 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,
|
||||
QString::number(stats->excludedEntries),
|
||||
stats->excludedEntries > 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"),
|
||||
|
@ -321,7 +321,7 @@ void TestEntryModel::testProxyModel()
|
||||
*/
|
||||
QSignalSpy spyColumnRemove(modelProxy, SIGNAL(columnsAboutToBeRemoved(QModelIndex, int, int)));
|
||||
modelProxy->hideColumn(0, true);
|
||||
QCOMPARE(modelProxy->columnCount(), 13);
|
||||
QCOMPARE(modelProxy->columnCount(), 14);
|
||||
QVERIFY(!spyColumnRemove.isEmpty());
|
||||
|
||||
int oldSpyColumnRemoveSize = spyColumnRemove.size();
|
||||
@ -343,7 +343,7 @@ void TestEntryModel::testProxyModel()
|
||||
*/
|
||||
QSignalSpy spyColumnInsert(modelProxy, SIGNAL(columnsAboutToBeInserted(QModelIndex, int, int)));
|
||||
modelProxy->hideColumn(0, false);
|
||||
QCOMPARE(modelProxy->columnCount(), 14);
|
||||
QCOMPARE(modelProxy->columnCount(), 15);
|
||||
QVERIFY(!spyColumnInsert.isEmpty());
|
||||
|
||||
int oldSpyColumnInsertSize = spyColumnInsert.size();
|
||||
|
@ -461,14 +461,13 @@ void TestGui::testEditEntry()
|
||||
|
||||
// Test the "known bad" checkbox
|
||||
editEntryWidget->setCurrentPage(1);
|
||||
auto knownBadCheckBox = editEntryWidget->findChild<QCheckBox*>("knownBadCheckBox");
|
||||
QVERIFY(knownBadCheckBox);
|
||||
QCOMPARE(knownBadCheckBox->isChecked(), false);
|
||||
knownBadCheckBox->setChecked(true);
|
||||
auto excludeReportsCheckBox = editEntryWidget->findChild<QCheckBox*>("excludeReportsCheckBox");
|
||||
QVERIFY(excludeReportsCheckBox);
|
||||
QCOMPARE(excludeReportsCheckBox->isChecked(), false);
|
||||
excludeReportsCheckBox->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);
|
||||
QVERIFY(entry->excludeFromReports());
|
||||
|
||||
// Test entry colors (simulate choosing a color)
|
||||
editEntryWidget->setCurrentPage(1);
|
||||
|
Loading…
x
Reference in New Issue
Block a user