diff --git a/src/kleo/keyfiltermanager.cpp b/src/kleo/keyfiltermanager.cpp index 9be56d99..d9f42bbd 100644 --- a/src/kleo/keyfiltermanager.cpp +++ b/src/kleo/keyfiltermanager.cpp @@ -1,481 +1,489 @@ /* keyfiltermanager.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include <config-libkleo.h> #include "keyfiltermanager.h" #include "defaultkeyfilter.h" #include "kconfigbasedkeyfilter.h" #include "stl_util.h" #include <libkleo/algorithm.h> #include <libkleo/compliance.h> #include <libkleo/gnupg.h> #include <libkleo/keyhelpers.h> #include <libkleo_debug.h> #include <KConfig> #include <KConfigGroup> #include <KLocalizedString> #include <KSharedConfig> #include <QAbstractListModel> #include <QCoreApplication> #include <QIcon> #include <QModelIndex> #include <QRegularExpression> #include <QStringList> #include <algorithm> #include <climits> #include <functional> using namespace Kleo; using namespace GpgME; namespace { class Model : public QAbstractListModel { KeyFilterManager::Private *m_keyFilterManagerPrivate; public: explicit Model(KeyFilterManager::Private *p) : QAbstractListModel(nullptr) , m_keyFilterManagerPrivate(p) { } int rowCount(const QModelIndex &) const override; QVariant data(const QModelIndex &idx, int role) const override; /* upgrade to public */ using QAbstractListModel::beginResetModel; /* upgrade to public */ using QAbstractListModel::endResetModel; }; class AllCertificatesKeyFilter : public DefaultKeyFilter { public: AllCertificatesKeyFilter() : DefaultKeyFilter() { setSpecificity(UINT_MAX); // overly high for ordering setName(i18n("All Certificates")); setId(QStringLiteral("all-certificates")); setMatchContexts(Filtering); } }; class MyCertificatesKeyFilter : public DefaultKeyFilter { public: MyCertificatesKeyFilter() : DefaultKeyFilter() { setHasSecret(Set); setSpecificity(UINT_MAX - 1); // overly high for ordering setName(i18n("My Certificates")); setId(QStringLiteral("my-certificates")); setMatchContexts(AnyMatchContext); setBold(true); } }; class TrustedCertificatesKeyFilter : public DefaultKeyFilter { public: TrustedCertificatesKeyFilter() : DefaultKeyFilter() { setRevoked(NotSet); setValidity(IsAtLeast); setValidityReferenceLevel(UserID::Marginal); setSpecificity(UINT_MAX - 2); // overly high for ordering setName(i18n("Trusted Certificates")); setId(QStringLiteral("trusted-certificates")); setMatchContexts(Filtering); } }; class FullCertificatesKeyFilter : public DefaultKeyFilter { public: FullCertificatesKeyFilter() : DefaultKeyFilter() { setRevoked(NotSet); setValidity(IsAtLeast); setValidityReferenceLevel(UserID::Full); setSpecificity(UINT_MAX - 3); setName(i18n("Fully Trusted Certificates")); setId(QStringLiteral("full-certificates")); setMatchContexts(Filtering); } }; class OtherCertificatesKeyFilter : public DefaultKeyFilter { public: OtherCertificatesKeyFilter() : DefaultKeyFilter() { setHasSecret(NotSet); setValidity(IsAtMost); setValidityReferenceLevel(UserID::Never); setSpecificity(UINT_MAX - 4); // overly high for ordering setName(i18n("Other Certificates")); setId(QStringLiteral("other-certificates")); setMatchContexts(Filtering); } }; /* This filter selects uncertified OpenPGP keys, i.e. "good" OpenPGP keys with * unrevoked user IDs that are not fully valid. */ class UncertifiedOpenPGPKeysFilter : public DefaultKeyFilter { public: UncertifiedOpenPGPKeysFilter() : DefaultKeyFilter() { setSpecificity(UINT_MAX - 6); // overly high for ordering setName(i18n("Not Certified Certificates")); setId(QStringLiteral("not-certified-certificates")); setMatchContexts(Filtering); setIsOpenPGP(Set); setIsBad(NotSet); } bool matches(const Key &key, MatchContexts contexts) const override { return DefaultKeyFilter::matches(key, contexts) && !Kleo::allUserIDsHaveFullValidity(key); } + bool matches(const UserID &userID, MatchContexts contexts) const override + { + return DefaultKeyFilter::matches(userID.parent(), contexts) && userID.validity() < UserID::Full; + } }; /* This filter selects only invalid keys (i.e. those where not all * UIDs are at least fully valid). */ class KeyNotValidFilter : public DefaultKeyFilter { public: KeyNotValidFilter() : DefaultKeyFilter() { setSpecificity(UINT_MAX - 7); // overly high for ordering setName(i18n("Not Validated Certificates")); setId(QStringLiteral("not-validated-certificates")); setMatchContexts(Filtering); } bool matches(const Key &key, MatchContexts contexts) const override { return DefaultKeyFilter::matches(key, contexts) && !Kleo::allUserIDsHaveFullValidity(key); } + bool matches(const UserID &userID, MatchContexts contexts) const override + { + return DefaultKeyFilter::matches(userID.parent(), contexts) && userID.validity() < UserID::Full; + } }; } static std::vector<std::shared_ptr<KeyFilter>> defaultFilters() { std::vector<std::shared_ptr<KeyFilter>> result; result.reserve(6); result.push_back(std::shared_ptr<KeyFilter>(new MyCertificatesKeyFilter)); result.push_back(std::shared_ptr<KeyFilter>(new TrustedCertificatesKeyFilter)); result.push_back(std::shared_ptr<KeyFilter>(new FullCertificatesKeyFilter)); result.push_back(std::shared_ptr<KeyFilter>(new OtherCertificatesKeyFilter)); result.push_back(std::shared_ptr<KeyFilter>(new AllCertificatesKeyFilter)); result.push_back(std::shared_ptr<KeyFilter>(new UncertifiedOpenPGPKeysFilter)); result.push_back(std::shared_ptr<KeyFilter>(new KeyNotValidFilter)); return result; } class KeyFilterManager::Private { public: Private() : filters() , model(this) { } void clear() { model.beginResetModel(); filters.clear(); model.endResetModel(); } std::vector<std::shared_ptr<KeyFilter>> filters; Model model; GpgME::Protocol protocol = GpgME::UnknownProtocol; }; KeyFilterManager *KeyFilterManager::mSelf = nullptr; KeyFilterManager::KeyFilterManager(QObject *parent) : QObject(parent) , d(new Private) { mSelf = this; // ### DF: doesn't a KStaticDeleter work more reliably? if (QCoreApplication *app = QCoreApplication::instance()) { connect(app, &QCoreApplication::aboutToQuit, this, &QObject::deleteLater); } reload(); } KeyFilterManager::~KeyFilterManager() { mSelf = nullptr; if (d) { d->clear(); } } KeyFilterManager *KeyFilterManager::instance() { if (!mSelf) { mSelf = new KeyFilterManager(); } return mSelf; } void KeyFilterManager::alwaysFilterByProtocol(GpgME::Protocol protocol) { if (protocol != d->protocol) { d->protocol = protocol; reload(); } } const std::shared_ptr<KeyFilter> &KeyFilterManager::filterMatching(const Key &key, KeyFilter::MatchContexts contexts) const { const auto it = std::find_if(d->filters.cbegin(), d->filters.cend(), [&key, contexts](const std::shared_ptr<KeyFilter> &filter) { return filter->matches(key, contexts); }); if (it != d->filters.cend()) { return *it; } static const std::shared_ptr<KeyFilter> null; return null; } std::vector<std::shared_ptr<KeyFilter>> KeyFilterManager::filtersMatching(const Key &key, KeyFilter::MatchContexts contexts) const { std::vector<std::shared_ptr<KeyFilter>> result; result.reserve(d->filters.size()); std::remove_copy_if(d->filters.begin(), d->filters.end(), std::back_inserter(result), [&key, contexts](const std::shared_ptr<KeyFilter> &filter) { return !filter->matches(key, contexts); }); return result; } namespace { static const auto byDecreasingSpecificity = [](const std::shared_ptr<KeyFilter> &lhs, const std::shared_ptr<KeyFilter> &rhs) { return lhs->specificity() > rhs->specificity(); }; } void KeyFilterManager::reload() { d->clear(); d->filters = defaultFilters(); KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc")); const QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Key Filter #\\d+$"))); const bool ignoreDeVs = !DeVSCompliance::isCompliant(); for (QStringList::const_iterator it = groups.begin(); it != groups.end(); ++it) { const KConfigGroup cfg(config, *it); if (cfg.hasKey("is-de-vs") && ignoreDeVs) { /* Don't show de-vs filters in other compliance modes */ continue; } d->filters.push_back(std::shared_ptr<KeyFilter>(new KConfigBasedKeyFilter(cfg))); } std::stable_sort(d->filters.begin(), d->filters.end(), byDecreasingSpecificity); if (d->protocol != GpgME::UnknownProtocol) { // remove filters with conflicting isOpenPGP rule const auto conflictingValue = (d->protocol == GpgME::OpenPGP) ? DefaultKeyFilter::NotSet : DefaultKeyFilter::Set; Kleo::erase_if(d->filters, [conflictingValue](const auto &f) { const auto filter = std::dynamic_pointer_cast<DefaultKeyFilter>(f); Q_ASSERT(filter); return filter->isOpenPGP() == conflictingValue; }); // add isOpenPGP rule to all filters const auto isOpenPGPValue = (d->protocol == GpgME::OpenPGP) ? DefaultKeyFilter::Set : DefaultKeyFilter::NotSet; std::for_each(std::begin(d->filters), std::end(d->filters), [isOpenPGPValue](auto &f) { const auto filter = std::dynamic_pointer_cast<DefaultKeyFilter>(f); Q_ASSERT(filter); return filter->setIsOpenPGP(isOpenPGPValue); }); } qCDebug(LIBKLEO_LOG) << "KeyFilterManager::" << __func__ << "final filter count is" << d->filters.size(); } QAbstractItemModel *KeyFilterManager::model() const { return &d->model; } const std::shared_ptr<KeyFilter> &KeyFilterManager::keyFilterByID(const QString &id) const { const auto it = std::find_if(d->filters.begin(), d->filters.end(), [id](const std::shared_ptr<KeyFilter> &filter) { return filter->id() == id; }); if (it != d->filters.end()) { return *it; } static const std::shared_ptr<KeyFilter> null; return null; } const std::shared_ptr<KeyFilter> &KeyFilterManager::fromModelIndex(const QModelIndex &idx) const { if (!idx.isValid() || idx.model() != &d->model || idx.row() < 0 || static_cast<unsigned>(idx.row()) >= d->filters.size()) { static const std::shared_ptr<KeyFilter> null; return null; } return d->filters[idx.row()]; } QModelIndex KeyFilterManager::toModelIndex(const std::shared_ptr<KeyFilter> &kf) const { if (!kf) { return {}; } const auto pair = std::equal_range(d->filters.cbegin(), d->filters.cend(), kf, byDecreasingSpecificity); const auto it = std::find(pair.first, pair.second, kf); if (it != pair.second) { return d->model.index(it - d->filters.begin()); } else { return QModelIndex(); } } int Model::rowCount(const QModelIndex &) const { return m_keyFilterManagerPrivate->filters.size(); } QVariant Model::data(const QModelIndex &idx, int role) const { if (!idx.isValid() || idx.model() != this || idx.row() < 0 || static_cast<unsigned>(idx.row()) > m_keyFilterManagerPrivate->filters.size()) { return QVariant(); } const auto filter = m_keyFilterManagerPrivate->filters[idx.row()]; switch (role) { case Qt::DecorationRole: return filter->icon(); case Qt::DisplayRole: case Qt::EditRole: case Qt::ToolTipRole: /* Most useless tooltip ever. */ return filter->name(); case KeyFilterManager::FilterIdRole: return filter->id(); case KeyFilterManager::FilterMatchContextsRole: return QVariant::fromValue(filter->availableMatchContexts()); default: return QVariant(); } } static KeyFilter::FontDescription get_fontdescription(const std::vector<std::shared_ptr<KeyFilter>> &filters, const Key &key, const KeyFilter::FontDescription &initial) { return kdtools::accumulate_if( filters.begin(), filters.end(), [&key](const std::shared_ptr<KeyFilter> &filter) { return filter->matches(key, KeyFilter::Appearance); }, initial, [](const KeyFilter::FontDescription &lhs, const std::shared_ptr<KeyFilter> &rhs) { return lhs.resolve(rhs->fontDescription()); }); } QFont KeyFilterManager::font(const Key &key, const QFont &baseFont) const { const KeyFilter::FontDescription fd = get_fontdescription(d->filters, key, KeyFilter::FontDescription()); return fd.font(baseFont); } static QColor get_color(const std::vector<std::shared_ptr<KeyFilter>> &filters, const Key &key, QColor (KeyFilter::*fun)() const) { const auto it = std::find_if(filters.cbegin(), filters.cend(), [&fun, &key](const std::shared_ptr<KeyFilter> &filter) { return filter->matches(key, KeyFilter::Appearance) && (filter.get()->*fun)().isValid(); }); if (it == filters.cend()) { return {}; } else { return (it->get()->*fun)(); } } static QColor get_color(const std::vector<std::shared_ptr<KeyFilter>> &filters, const UserID &userID, QColor (KeyFilter::*fun)() const) { const auto it = std::find_if(filters.cbegin(), filters.cend(), [&fun, &userID](const std::shared_ptr<KeyFilter> &filter) { return filter->matches(userID, KeyFilter::Appearance) && (filter.get()->*fun)().isValid(); }); if (it == filters.cend()) { return {}; } else { return (it->get()->*fun)(); } } static QString get_string(const std::vector<std::shared_ptr<KeyFilter>> &filters, const Key &key, QString (KeyFilter::*fun)() const) { const auto it = std::find_if(filters.cbegin(), filters.cend(), [&fun, &key](const std::shared_ptr<KeyFilter> &filter) { return filter->matches(key, KeyFilter::Appearance) && !(filter.get()->*fun)().isEmpty(); }); if (it == filters.cend()) { return QString(); } else { return (*it)->icon(); } } QColor KeyFilterManager::bgColor(const Key &key) const { return get_color(d->filters, key, &KeyFilter::bgColor); } QColor KeyFilterManager::fgColor(const Key &key) const { return get_color(d->filters, key, &KeyFilter::fgColor); } QColor KeyFilterManager::bgColor(const UserID &userID) const { return get_color(d->filters, userID, &KeyFilter::bgColor); } QColor KeyFilterManager::fgColor(const UserID &userID) const { return get_color(d->filters, userID, &KeyFilter::fgColor); } QIcon KeyFilterManager::icon(const Key &key) const { const QString icon = get_string(d->filters, key, &KeyFilter::icon); return icon.isEmpty() ? QIcon() : QIcon::fromTheme(icon); } diff --git a/src/models/useridproxymodel.cpp b/src/models/useridproxymodel.cpp index 3b7efd05..31bf9deb 100644 --- a/src/models/useridproxymodel.cpp +++ b/src/models/useridproxymodel.cpp @@ -1,189 +1,201 @@ /* SPDX-FileCopyrightText: 2024 g10 Code GmbH SPDX-FileContributor: Tobias Fella <tobias.fella@gnupg.com> SPDX-License-Identifier: GPL-2.0-or-later */ #include "useridproxymodel.h" #include "keylist.h" #include "keylistmodel.h" #include "kleo/keyfiltermanager.h" #include "utils/formatting.h" #include "utils/systeminfo.h" #include <global.h> #include <QColor> using namespace Kleo; UserIDProxyModel::UserIDProxyModel(QObject *parent) : AbstractKeyListSortFilterProxyModel(parent) { } static QVariant returnIfValid(const QColor &t) { if (t.isValid()) { return t; } else { return QVariant(); } } QModelIndex UserIDProxyModel::mapFromSource(const QModelIndex &sourceIndex) const { if (!sourceIndex.isValid()) { return {}; } int row = 0; for (int i = 0; i < sourceIndex.row(); i++) { row += userIDsOfSourceRow(i); } return index(row, sourceIndex.column(), {}); } QModelIndex UserIDProxyModel::mapToSource(const QModelIndex &proxyIndex) const { if (!proxyIndex.isValid()) { return {}; } return sourceModel()->index(sourceRowForProxyIndex(proxyIndex), proxyIndex.column(), {}); } int UserIDProxyModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } int sum = 0; for (int i = 0; i < sourceModel()->rowCount(); i++) { sum += userIDsOfSourceRow(i); } return sum; } QModelIndex UserIDProxyModel::index(int row, int column, const QModelIndex &parent) const { if (parent.isValid()) { return {}; } return createIndex(row, column, nullptr); } QModelIndex UserIDProxyModel::parent(const QModelIndex &) const { return {}; } int UserIDProxyModel::columnCount(const QModelIndex &index) const { return sourceModel()->columnCount(mapToSource(index)); } QVariant UserIDProxyModel::data(const QModelIndex &index, int role) const { const auto row = sourceRowForProxyIndex(index); const auto offset = sourceOffsetForProxyIndex(index); const auto model = dynamic_cast<AbstractKeyListModel *>(sourceModel()); const auto key = model->key(model->index(row, 0)); if (key.isNull()) { return AbstractKeyListSortFilterProxyModel::data(index, role); } const auto userId = key.userID(offset); if ((role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::AccessibleTextRole)) { if (index.column() == KeyList::Columns::PrettyName) { auto name = QString::fromUtf8(userId.name()); if (name.isEmpty()) { return AbstractKeyListSortFilterProxyModel::data(index, role); } return name; } if (index.column() == KeyList::Columns::PrettyEMail) { return QString::fromUtf8(userId.email()); } if (index.column() == KeyList::Columns::Validity) { return Formatting::complianceStringShort(userId); } if (index.column() == KeyList::Columns::Summary) { return Formatting::summaryLine(userId); } if (index.column() == KeyList::Columns::Origin) { return Formatting::origin(userId.origin()); } if (index.column() == KeyList::Columns::LastUpdate) { if (role == Qt::AccessibleTextRole) { return Formatting::accessibleDate(userId.lastUpdate()); } else { return Formatting::dateString(userId.lastUpdate()); } } } if (role == Qt::BackgroundRole) { if (!SystemInfo::isHighContrastModeActive()) { return returnIfValid(KeyFilterManager::instance()->bgColor(userId)); } } else if (role == Qt::ForegroundRole) { if (!SystemInfo::isHighContrastModeActive()) { return returnIfValid(KeyFilterManager::instance()->fgColor(userId)); } } return AbstractKeyListSortFilterProxyModel::data(index, role); } int UserIDProxyModel::sourceRowForProxyIndex(const QModelIndex &index) const { int row = index.row(); int i; for (i = 0; row >= userIDsOfSourceRow(i); i++) { row -= userIDsOfSourceRow(i); } return i; } int UserIDProxyModel::sourceOffsetForProxyIndex(const QModelIndex &index) const { int row = index.row(); int i; for (i = 0; row >= userIDsOfSourceRow(i); i++) { row -= userIDsOfSourceRow(i); } auto model = dynamic_cast<AbstractKeyListModel *>(sourceModel()); auto key = model->key(model->index(sourceRowForProxyIndex(index), 0)); int tmp = row; for (int j = 0; j <= tmp; j++) { // account for filtered out S/MIME user IDs if (key.protocol() == GpgME::Protocol::CMS && !key.userID(j).email()) { row++; } } return row; } int UserIDProxyModel::userIDsOfSourceRow(int sourceRow) const { auto model = dynamic_cast<AbstractKeyListModel *>(sourceModel()); auto key = model->key(model->index(sourceRow, 0)); if (key.protocol() == GpgME::OpenPGP) { return key.numUserIDs(); } // Try to filter out some useless SMIME user ids int count = 0; const auto &uids = key.userIDs(); for (auto it = uids.begin(); it != uids.end(); ++it) { const auto &uid = *it; if (uid.email()) { count++; } } return count; } UserIDProxyModel *UserIDProxyModel::clone() const { auto model = new UserIDProxyModel(QObject::parent()); model->setSourceModel(sourceModel()); return model; } + +QModelIndex UserIDProxyModel::index(const KeyGroup &group) const +{ + Q_UNUSED(group); + return {}; +} + +QModelIndex UserIDProxyModel::index(const GpgME::Key &key) const +{ + Q_UNUSED(key); + return {}; +} diff --git a/src/models/useridproxymodel.h b/src/models/useridproxymodel.h index 213c68af..506d2798 100644 --- a/src/models/useridproxymodel.h +++ b/src/models/useridproxymodel.h @@ -1,34 +1,36 @@ /* SPDX-FileCopyrightText: 2024 g10 Code GmbH SPDX-FileContributor: Tobias Fella <tobias.fella@gnupg.com> SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" #include <Libkleo/KeyListSortFilterProxyModel> namespace Kleo { class KLEO_EXPORT UserIDProxyModel : public Kleo::AbstractKeyListSortFilterProxyModel { Q_OBJECT public: explicit UserIDProxyModel(QObject *parent = nullptr); QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override; QModelIndex mapToSource(const QModelIndex &proxyIndex) const override; int rowCount(const QModelIndex &parent = {}) const override; QModelIndex index(int row, int column, const QModelIndex &parent) const override; virtual QModelIndex parent(const QModelIndex &) const override; int columnCount(const QModelIndex &) const override; QVariant data(const QModelIndex &index, int role) const override; int sourceRowForProxyIndex(const QModelIndex &index) const; int sourceOffsetForProxyIndex(const QModelIndex &index) const; int userIDsOfSourceRow(int sourceRow) const; UserIDProxyModel *clone() const override; + QModelIndex index(const KeyGroup &) const override; + QModelIndex index(const GpgME::Key &key) const override; }; }