Libkleo

keyfiltermanager.cpp
1/*
2 keyfiltermanager.cpp
3
4 This file is part of libkleopatra, the KDE keymanagement library
5 SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9
10#include <config-libkleo.h>
11
12#include "keyfiltermanager.h"
13
14#include "defaultkeyfilter.h"
15#include "kconfigbasedkeyfilter.h"
16#include "stl_util.h"
17
18#include <libkleo/algorithm.h>
19#include <libkleo/compliance.h>
20#include <libkleo/gnupg.h>
21#include <libkleo/keyhelpers.h>
22
23#include <libkleo_debug.h>
24
25#include <KConfig>
26#include <KConfigGroup>
27#include <KLocalizedString>
28#include <KSharedConfig>
29
30#include <QAbstractListModel>
31#include <QCoreApplication>
32#include <QIcon>
33#include <QModelIndex>
34#include <QRegularExpression>
35#include <QStringList>
36
37#include <algorithm>
38#include <climits>
39#include <functional>
40
41using namespace Kleo;
42using namespace GpgME;
43
44namespace
45{
46void adjustFilters(std::vector<std::shared_ptr<KeyFilter>> &filters, Protocol protocol)
47{
48 if (protocol != GpgME::UnknownProtocol) {
49 // remove filters with conflicting isOpenPGP rule
50 const auto conflictingValue = (protocol == GpgME::OpenPGP) ? DefaultKeyFilter::NotSet : DefaultKeyFilter::Set;
51 Kleo::erase_if(filters, [conflictingValue](const auto &f) {
52 const auto filter = std::dynamic_pointer_cast<DefaultKeyFilter>(f);
53 Q_ASSERT(filter);
54 return filter->isOpenPGP() == conflictingValue;
55 });
56 // add isOpenPGP rule to all filters
57 const auto isOpenPGPValue = (protocol == GpgME::OpenPGP) ? DefaultKeyFilter::Set : DefaultKeyFilter::NotSet;
58 std::for_each(std::begin(filters), std::end(filters), [isOpenPGPValue](auto &f) {
59 const auto filter = std::dynamic_pointer_cast<DefaultKeyFilter>(f);
60 Q_ASSERT(filter);
61 return filter->setIsOpenPGP(isOpenPGPValue);
62 });
63 }
64}
65
66class Model : public QAbstractListModel
67{
68 KeyFilterManager::Private *m_keyFilterManagerPrivate;
69
70public:
71 explicit Model(KeyFilterManager::Private *p)
72 : QAbstractListModel(nullptr)
73 , m_keyFilterManagerPrivate(p)
74 {
75 }
76
77 int rowCount(const QModelIndex &) const override;
78 QVariant data(const QModelIndex &idx, int role) const override;
79 /* upgrade to public */ using QAbstractListModel::beginResetModel;
80 /* upgrade to public */ using QAbstractListModel::endResetModel;
81};
82
83class AllCertificatesKeyFilter : public DefaultKeyFilter
84{
85public:
86 AllCertificatesKeyFilter()
88 {
89 setSpecificity(UINT_MAX); // overly high for ordering
90 setName(i18nc("All Certificates", "All"));
91 setDescription(i18n("All certificates"));
92 setId(QStringLiteral("all-certificates"));
93 setMatchContexts(Filtering);
94 }
95};
96
97class MyCertificatesKeyFilter : public DefaultKeyFilter
98{
99public:
100 MyCertificatesKeyFilter()
102 {
103 setHasSecret(Set);
104 setSpecificity(UINT_MAX - 2); // overly high for ordering
105
106 setName(i18nc("My own Certificates", "My Own"));
107 setDescription(i18n("My own certificates"));
108 setId(QStringLiteral("my-certificates"));
109 setMatchContexts(AnyMatchContext);
110 setBold(true);
111 }
112};
113
114class FullCertificatesKeyFilter : public DefaultKeyFilter
115{
116public:
117 FullCertificatesKeyFilter()
119 {
120 setRevoked(NotSet);
121 setValidity(IsAtLeast);
122 setValidityReferenceLevel(UserID::Full);
123 setSpecificity(UINT_MAX - 4);
124
125 setName(i18nc("Certified Certificates", "Certified"));
126 setDescription(i18n("Certificates for which the primary user ID is certified"));
127 setId(QStringLiteral("trusted-certificates"));
128 setMatchContexts(Filtering);
129 }
130};
131
132class OtherCertificatesKeyFilter : public DefaultKeyFilter
133{
134public:
135 OtherCertificatesKeyFilter()
137 {
138 setHasSecret(NotSet);
139 setValidity(IsAtMost);
140 setValidityReferenceLevel(UserID::Marginal);
141 setSpecificity(UINT_MAX - 6); // overly high for ordering
142
143 setName(i18nc("Not Certified Certificates", "Not Certified"));
144 setDescription(i18n("Certificates for which the primary user ID is not certified"));
145 setId(QStringLiteral("other-certificates"));
146 setMatchContexts(Filtering);
147 }
148};
149
150/* This filter selects uncertified OpenPGP keys, i.e. "good" OpenPGP keys with
151 * unrevoked user IDs that are not fully valid. */
152class UncertifiedOpenPGPKeysFilter : public DefaultKeyFilter
153{
154public:
155 UncertifiedOpenPGPKeysFilter()
157 {
158 setSpecificity(UINT_MAX - 7); // overly high for ordering
159 setName(i18nc("Certificates to certify by the user", "To Certify"));
160 setDescription(i18n("Certificates that are not fully certified and that you may want to certify yourself"));
161 setId(QStringLiteral("not-certified-certificates"));
162
163 setMatchContexts(Filtering);
164 setIsOpenPGP(Set);
165 setIsBad(NotSet);
166 }
167 bool matches(const Key &key, MatchContexts contexts) const override
168 {
169 return DefaultKeyFilter::matches(key, contexts) && !Kleo::allUserIDsHaveFullValidity(key);
170 }
171 bool matches(const UserID &userID, MatchContexts contexts) const override
172 {
173 return DefaultKeyFilter::matches(userID.parent(), contexts) && userID.validity() < UserID::Full;
174 }
175};
176
177/* This filter selects only invalid keys (i.e. those where not all
178 * UIDs are at least fully valid). */
179class KeyNotValidFilter : public DefaultKeyFilter
180{
181public:
182 KeyNotValidFilter()
184 {
185 setSpecificity(UINT_MAX - 5); // overly high for ordering
186
187 setName(i18nc("Not Fully Certified Certificates", "Not Fully Certified"));
188 setDescription(i18n("Certificates for which not all user IDs are certified"));
189 setId(QStringLiteral("not-validated-certificates"));
190 setMatchContexts(Filtering);
191 }
192 bool matches(const Key &key, MatchContexts contexts) const override
193 {
194 return DefaultKeyFilter::matches(key, contexts) && !Kleo::allUserIDsHaveFullValidity(key);
195 }
196 bool matches(const UserID &userID, MatchContexts contexts) const override
197 {
198 return DefaultKeyFilter::matches(userID.parent(), contexts) && userID.validity() < UserID::Full;
199 }
200};
201
202}
203
204class KeyFullyCertifiedFilter : public DefaultKeyFilter
205{
206public:
207 KeyFullyCertifiedFilter()
209 {
210 setSpecificity(UINT_MAX - 3);
211 setName(i18nc("Fully Certified Certificates", "Fully Certified"));
212 setDescription(i18n("Certificates for which all user IDs are certified"));
213 setId(QStringLiteral("full-certificates"));
214 setMatchContexts(Filtering);
215 }
216 bool matches(const Key &key, MatchContexts contexts) const override
217 {
218 return DefaultKeyFilter::matches(key, contexts) && Kleo::allUserIDsHaveFullValidity(key);
219 }
220 bool matches(const UserID &userID, MatchContexts contexts) const override
221 {
222 return DefaultKeyFilter::matches(userID.parent(), contexts) && userID.validity() >= UserID::Full;
223 }
224};
225
226static std::vector<std::shared_ptr<KeyFilter>> defaultFilters()
227{
228 return {
229 std::shared_ptr<KeyFilter>(new MyCertificatesKeyFilter),
230 std::shared_ptr<KeyFilter>(new FullCertificatesKeyFilter),
231 std::shared_ptr<KeyFilter>(new OtherCertificatesKeyFilter),
232 std::shared_ptr<KeyFilter>(new AllCertificatesKeyFilter),
233 std::shared_ptr<KeyFilter>(new UncertifiedOpenPGPKeysFilter),
234 std::shared_ptr<KeyFilter>(new KeyFullyCertifiedFilter),
235 std::shared_ptr<KeyFilter>(new KeyNotValidFilter),
236 };
237}
238
239class KeyFilterManager::Private
240{
241public:
242 Private()
243 : filters()
244 , model(this)
245 {
246 }
247 void clear()
248 {
249 model.beginResetModel();
250 filters.clear();
251 model.endResetModel();
252 }
253
254 std::vector<std::shared_ptr<KeyFilter>> filters;
255 Model model;
256 GpgME::Protocol protocol = GpgME::UnknownProtocol;
257};
258
259KeyFilterManager *KeyFilterManager::mSelf = nullptr;
260
261KeyFilterManager::KeyFilterManager(QObject *parent)
262 : QObject(parent)
263 , d(new Private)
264{
265 mSelf = this;
266 // ### DF: doesn't a KStaticDeleter work more reliably?
269 }
270 reload();
271}
272
273KeyFilterManager::~KeyFilterManager()
274{
275 mSelf = nullptr;
276 if (d) {
277 d->clear();
278 }
279}
280
281KeyFilterManager *KeyFilterManager::instance()
282{
283 if (!mSelf) {
284 mSelf = new KeyFilterManager();
285 }
286 return mSelf;
287}
288
289void KeyFilterManager::alwaysFilterByProtocol(GpgME::Protocol protocol)
290{
291 if (protocol != d->protocol) {
292 d->protocol = protocol;
293 reload();
294 Q_EMIT alwaysFilterByProtocolChanged(protocol);
295 }
296}
297
298const std::shared_ptr<KeyFilter> &KeyFilterManager::filterMatching(const Key &key, KeyFilter::MatchContexts contexts) const
299{
300 const auto it = std::find_if(d->filters.cbegin(), d->filters.cend(), [&key, contexts](const std::shared_ptr<KeyFilter> &filter) {
301 return filter->matches(key, contexts);
302 });
303 if (it != d->filters.cend()) {
304 return *it;
305 }
306 static const std::shared_ptr<KeyFilter> null;
307 return null;
308}
309
310std::vector<std::shared_ptr<KeyFilter>> KeyFilterManager::filtersMatching(const Key &key, KeyFilter::MatchContexts contexts) const
311{
312 std::vector<std::shared_ptr<KeyFilter>> result;
313 result.reserve(d->filters.size());
314 std::remove_copy_if(d->filters.begin(), d->filters.end(), std::back_inserter(result), [&key, contexts](const std::shared_ptr<KeyFilter> &filter) {
315 return !filter->matches(key, contexts);
316 });
317 return result;
318}
319
320namespace
321{
322static const auto byDecreasingSpecificity = [](const std::shared_ptr<KeyFilter> &lhs, const std::shared_ptr<KeyFilter> &rhs) {
323 return lhs->specificity() > rhs->specificity();
324};
325}
326
327void KeyFilterManager::reload()
328{
329 d->clear();
330
331 d->filters = defaultFilters();
332 KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc"));
333
334 const QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Key Filter #\\d+$")));
335 const bool ignoreDeVs = !DeVSCompliance::isCompliant();
336 for (QStringList::const_iterator it = groups.begin(); it != groups.end(); ++it) {
337 const KConfigGroup cfg(config, *it);
338 if (cfg.hasKey("is-de-vs") && ignoreDeVs) {
339 /* Don't show de-vs filters in other compliance modes */
340 continue;
341 }
342 d->filters.push_back(std::shared_ptr<KeyFilter>(new KConfigBasedKeyFilter(cfg)));
343 }
344 std::stable_sort(d->filters.begin(), d->filters.end(), byDecreasingSpecificity);
345
346 adjustFilters(d->filters, d->protocol);
347 qCDebug(LIBKLEO_LOG) << "KeyFilterManager::" << __func__ << "final filter count is" << d->filters.size();
348}
349
350QAbstractItemModel *KeyFilterManager::model() const
351{
352 return &d->model;
353}
354
355const std::shared_ptr<KeyFilter> &KeyFilterManager::keyFilterByID(const QString &id) const
356{
357 const auto it = std::find_if(d->filters.begin(), d->filters.end(), [id](const std::shared_ptr<KeyFilter> &filter) {
358 return filter->id() == id;
359 });
360 if (it != d->filters.end()) {
361 return *it;
362 }
363 static const std::shared_ptr<KeyFilter> null;
364 return null;
365}
366
367const std::shared_ptr<KeyFilter> &KeyFilterManager::fromModelIndex(const QModelIndex &idx) const
368{
369 if (!idx.isValid() || idx.model() != &d->model || idx.row() < 0 || static_cast<unsigned>(idx.row()) >= d->filters.size()) {
370 static const std::shared_ptr<KeyFilter> null;
371 return null;
372 }
373 return d->filters[idx.row()];
374}
375
376QModelIndex KeyFilterManager::toModelIndex(const std::shared_ptr<KeyFilter> &kf) const
377{
378 if (!kf) {
379 return {};
380 }
381 const auto pair = std::equal_range(d->filters.cbegin(), d->filters.cend(), kf, byDecreasingSpecificity);
382 const auto it = std::find(pair.first, pair.second, kf);
383 if (it != pair.second) {
384 return d->model.index(it - d->filters.begin());
385 } else {
386 return QModelIndex();
387 }
388}
389
390int Model::rowCount(const QModelIndex &) const
391{
392 return m_keyFilterManagerPrivate->filters.size();
393}
394
395QVariant Model::data(const QModelIndex &idx, int role) const
396{
397 if (!idx.isValid() || idx.model() != this || idx.row() < 0 || static_cast<unsigned>(idx.row()) > m_keyFilterManagerPrivate->filters.size()) {
398 return QVariant();
399 }
400
401 const auto filter = m_keyFilterManagerPrivate->filters[idx.row()];
402 switch (role) {
404 return filter->icon();
405
406 case Qt::DisplayRole:
407 case Qt::EditRole:
408 return filter->name();
409 case Qt::ToolTipRole:
410 return filter->description();
411
412 case KeyFilterManager::FilterIdRole:
413 return filter->id();
414
415 case KeyFilterManager::FilterMatchContextsRole:
416 return QVariant::fromValue(filter->availableMatchContexts());
417
418 case KeyFilterManager::FilterRole:
419 return QVariant::fromValue(filter);
420
421 default:
422 return QVariant();
423 }
424}
425
426static KeyFilter::FontDescription
427get_fontdescription(const std::vector<std::shared_ptr<KeyFilter>> &filters, const Key &key, const KeyFilter::FontDescription &initial)
428{
429 return kdtools::accumulate_if(
430 filters.begin(),
431 filters.end(),
432 [&key](const std::shared_ptr<KeyFilter> &filter) {
433 return filter->matches(key, KeyFilter::Appearance);
434 },
435 initial,
436 [](const KeyFilter::FontDescription &lhs, const std::shared_ptr<KeyFilter> &rhs) {
437 return lhs.resolve(rhs->fontDescription());
438 });
439}
440
441QFont KeyFilterManager::font(const Key &key, const QFont &baseFont) const
442{
443 const KeyFilter::FontDescription fd = get_fontdescription(d->filters, key, KeyFilter::FontDescription());
444
445 return fd.font(baseFont);
446}
447
448static QColor get_color(const std::vector<std::shared_ptr<KeyFilter>> &filters, const Key &key, QColor (KeyFilter::*fun)() const)
449{
450 const auto it = std::find_if(filters.cbegin(), filters.cend(), [&fun, &key](const std::shared_ptr<KeyFilter> &filter) {
451 return filter->matches(key, KeyFilter::Appearance) && (filter.get()->*fun)().isValid();
452 });
453 if (it == filters.cend()) {
454 return {};
455 } else {
456 return (it->get()->*fun)();
457 }
458}
459
460static QColor get_color(const std::vector<std::shared_ptr<KeyFilter>> &filters, const UserID &userID, QColor (KeyFilter::*fun)() const)
461{
462 const auto it = std::find_if(filters.cbegin(), filters.cend(), [&fun, &userID](const std::shared_ptr<KeyFilter> &filter) {
463 return filter->matches(userID, KeyFilter::Appearance) && (filter.get()->*fun)().isValid();
464 });
465 if (it == filters.cend()) {
466 return {};
467 } else {
468 return (it->get()->*fun)();
469 }
470}
471
472static QString get_string(const std::vector<std::shared_ptr<KeyFilter>> &filters, const Key &key, QString (KeyFilter::*fun)() const)
473{
474 const auto it = std::find_if(filters.cbegin(), filters.cend(), [&fun, &key](const std::shared_ptr<KeyFilter> &filter) {
475 return filter->matches(key, KeyFilter::Appearance) && !(filter.get()->*fun)().isEmpty();
476 });
477 if (it == filters.cend()) {
478 return QString();
479 } else {
480 return (*it)->icon();
481 }
482}
483
484QColor KeyFilterManager::bgColor(const Key &key) const
485{
486 return get_color(d->filters, key, &KeyFilter::bgColor);
487}
488
489QColor KeyFilterManager::fgColor(const Key &key) const
490{
491 return get_color(d->filters, key, &KeyFilter::fgColor);
492}
493
494QColor KeyFilterManager::bgColor(const UserID &userID) const
495{
496 return get_color(d->filters, userID, &KeyFilter::bgColor);
497}
498
499QColor KeyFilterManager::fgColor(const UserID &userID) const
500{
501 return get_color(d->filters, userID, &KeyFilter::fgColor);
502}
503
504QIcon KeyFilterManager::icon(const Key &key) const
505{
506 const QString icon = get_string(d->filters, key, &KeyFilter::icon);
507 return icon.isEmpty() ? QIcon() : QIcon::fromTheme(icon);
508}
509
510Protocol KeyFilterManager::protocol() const
511{
512 return d->protocol;
513}
514
515class KeyFilterModel::Private
516{
517 friend class KeyFilterModel;
518 std::vector<std::shared_ptr<KeyFilter>> customFilters;
519};
520
521KeyFilterModel::KeyFilterModel(QObject *parent)
522 : QSortFilterProxyModel(parent)
523 , d(new Private)
524{
525 setSourceModel(KeyFilterManager::instance()->model());
526 connect(KeyFilterManager::instance(), &KeyFilterManager::alwaysFilterByProtocolChanged, this, [this](auto protocol) {
528 adjustFilters(d->customFilters, protocol);
530 });
531}
532
533void KeyFilterModel::prependCustomFilter(const std::shared_ptr<KeyFilter> &filter)
534{
536 d->customFilters.insert(d->customFilters.begin(), filter);
537 adjustFilters(d->customFilters, KeyFilterManager::instance()->protocol());
539}
540
541bool KeyFilterModel::isCustomFilter(int row) const
542{
543 return (row >= 0) && (row < int(d->customFilters.size()));
544}
545
546int KeyFilterModel::rowCount(const QModelIndex &parent) const
547{
548 return d->customFilters.size() + QSortFilterProxyModel::rowCount(parent);
549}
550
551int KeyFilterModel::columnCount(const QModelIndex &parent) const
552{
553 Q_UNUSED(parent)
554 // pretend that there is only one column to workaround a bug in
555 // QAccessibleTable which provides the accessibility interface for the
556 // pop-up of the combo box
557 return 1;
558}
559
560QModelIndex KeyFilterModel::mapToSource(const QModelIndex &index) const
561{
562 if (!index.isValid()) {
563 return {};
564 }
565 if (!isCustomFilter(index.row())) {
566 const int sourceRow = index.row() - d->customFilters.size();
567 return QSortFilterProxyModel::mapToSource(createIndex(sourceRow, index.column(), index.internalPointer()));
568 }
569 return {};
570}
571
572QModelIndex KeyFilterModel::mapFromSource(const QModelIndex &source_index) const
573{
574 const QModelIndex idx = QSortFilterProxyModel::mapFromSource(source_index);
575 return createIndex(d->customFilters.size() + idx.row(), idx.column(), idx.internalPointer());
576}
577
578QModelIndex KeyFilterModel::index(int row, int column, const QModelIndex &parent) const
579{
580 if (row < 0 || row >= rowCount()) {
581 return {};
582 }
583 if (row < int(d->customFilters.size())) {
584 return createIndex(row, column, nullptr);
585 } else {
586 const QModelIndex mi = QSortFilterProxyModel::index(row - d->customFilters.size(), column, parent);
587 return createIndex(row, column, mi.internalPointer());
588 }
589}
590
591Qt::ItemFlags KeyFilterModel::flags(const QModelIndex &index) const
592{
593 Q_UNUSED(index)
595}
596
598{
599 // Flat list
600 return {};
601}
602
603QVariant KeyFilterModel::data(const QModelIndex &index, int role) const
604{
605 if (!index.isValid()) {
606 return QVariant();
607 }
608
609 if (isCustomFilter(index.row())) {
610 const auto filter = d->customFilters[index.row()];
611 switch (role) {
613 return filter->icon();
614
615 case Qt::DisplayRole:
616 case Qt::EditRole:
617 return filter->name();
618 case Qt::ToolTipRole:
619 return filter->description();
620
621 case KeyFilterManager::FilterIdRole:
622 return filter->id();
623
624 case KeyFilterManager::FilterMatchContextsRole:
625 return QVariant::fromValue(filter->availableMatchContexts());
626
627 case KeyFilterManager::FilterRole:
628 return QVariant::fromValue(filter);
629
630 default:
631 return QVariant();
632 }
633 }
634
635 return QSortFilterProxyModel::data(index, role);
636}
637
638#include "moc_keyfiltermanager.cpp"
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
Default implementation of key filter class.
An abstract base class key filters.
Definition keyfilter.h:37
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
const QList< QKeySequence > & reload()
QModelIndex createIndex(int row, int column, const void *ptr) const const
QCoreApplication * instance()
iterator begin()
iterator end()
int column() const const
void * internalPointer() const const
bool isValid() const const
const QAbstractItemModel * model() const const
int row() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
QObject * parent() const const
virtual QVariant data(const QModelIndex &index, int role) const const override
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const const override
virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const const override
virtual int rowCount(const QModelIndex &parent) const const override
virtual void setSourceModel(QAbstractItemModel *sourceModel) override
bool isEmpty() const const
QStringList filter(QStringView str, Qt::CaseSensitivity cs) const const
DecorationRole
typedef ItemFlags
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QVariant fromValue(T &&value)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Sep 13 2024 11:51:13 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.