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 (except disabled ones)"));
92 setId(QStringLiteral("all-certificates"));
93 setMatchContexts(Filtering);
94 setDisabled(NotSet);
95 }
96};
97
98class MyCertificatesKeyFilter : public DefaultKeyFilter
99{
100public:
101 MyCertificatesKeyFilter()
103 {
104 setHasSecret(Set);
105 setSpecificity(UINT_MAX - 2); // overly high for ordering
106 setDisabled(NotSet);
107
108 setName(i18nc("My own Certificates", "My Own"));
109 setDescription(i18n("My own certificates (except disabled ones)"));
110 setId(QStringLiteral("my-certificates"));
111 setMatchContexts(AnyMatchContext);
112 setBold(true);
113 }
114};
115
116class FullCertificatesKeyFilter : public DefaultKeyFilter
117{
118public:
119 FullCertificatesKeyFilter()
121 {
122 setRevoked(NotSet);
123 setValidity(IsAtLeast);
124 setValidityReferenceLevel(UserID::Full);
125 setSpecificity(UINT_MAX - 4);
126 setDisabled(NotSet);
127
128 setName(i18nc("Certified Certificates", "Certified"));
129 setDescription(i18n("Certificates for which the primary user ID is certified (except disabled ones)"));
130 setId(QStringLiteral("trusted-certificates"));
131 setMatchContexts(Filtering);
132 }
133};
134
135class OtherCertificatesKeyFilter : public DefaultKeyFilter
136{
137public:
138 OtherCertificatesKeyFilter()
140 {
141 setHasSecret(NotSet);
142 setValidity(IsAtMost);
143 setValidityReferenceLevel(UserID::Marginal);
144 setSpecificity(UINT_MAX - 6); // overly high for ordering
145 setDisabled(NotSet);
146
147 setName(i18nc("Not Certified Certificates", "Not Certified"));
148 setDescription(i18n("Certificates for which the primary user ID is not certified (except disabled ones)"));
149 setId(QStringLiteral("other-certificates"));
150 setMatchContexts(Filtering);
151 }
152};
153
154/* This filter selects uncertified OpenPGP keys, i.e. "good" OpenPGP keys with
155 * unrevoked user IDs that are not fully valid. */
156class UncertifiedOpenPGPKeysFilter : public DefaultKeyFilter
157{
158public:
159 UncertifiedOpenPGPKeysFilter()
161 {
162 setSpecificity(UINT_MAX - 7); // overly high for ordering
163 setName(i18nc("Certificates to certify by the user", "To Certify"));
164 setDescription(i18n("Certificates that are not fully certified and that you may want to certify yourself (except disabled ones)"));
165 setId(QStringLiteral("not-certified-certificates"));
166
167 setMatchContexts(Filtering);
168 setIsOpenPGP(Set);
169 setIsBad(NotSet);
170 setDisabled(NotSet);
171 }
172 bool matches(const Key &key, MatchContexts contexts) const override
173 {
174 return DefaultKeyFilter::matches(key, contexts) && !Kleo::allUserIDsHaveFullValidity(key);
175 }
176 bool matches(const UserID &userID, MatchContexts contexts) const override
177 {
178 return DefaultKeyFilter::matches(userID.parent(), contexts) && userID.validity() < UserID::Full;
179 }
180};
181
182/* This filter selects only invalid keys (i.e. those where not all
183 * UIDs are at least fully valid). */
184class KeyNotValidFilter : public DefaultKeyFilter
185{
186public:
187 KeyNotValidFilter()
189 {
190 setSpecificity(UINT_MAX - 5); // overly high for ordering
191
192 setName(i18nc("Not Fully Certified Certificates", "Not Fully Certified"));
193 setDescription(i18n("Certificates for which not all user IDs are certified (except disabled ones)"));
194 setId(QStringLiteral("not-validated-certificates"));
195 setMatchContexts(Filtering);
196 setDisabled(NotSet);
197 }
198 bool matches(const Key &key, MatchContexts contexts) const override
199 {
200 return DefaultKeyFilter::matches(key, contexts) && !Kleo::allUserIDsHaveFullValidity(key);
201 }
202 bool matches(const UserID &userID, MatchContexts contexts) const override
203 {
204 return DefaultKeyFilter::matches(userID.parent(), contexts) && userID.validity() < UserID::Full;
205 }
206};
207
208}
209
210class KeyFullyCertifiedFilter : public DefaultKeyFilter
211{
212public:
213 KeyFullyCertifiedFilter()
215 {
216 setSpecificity(UINT_MAX - 3);
217 setName(i18nc("Fully Certified Certificates", "Fully Certified"));
218 setDescription(i18n("Certificates for which all user IDs are certified (except disabled ones)"));
219 setId(QStringLiteral("full-certificates"));
220 setMatchContexts(Filtering);
221 setDisabled(NotSet);
222 }
223 bool matches(const Key &key, MatchContexts contexts) const override
224 {
225 return DefaultKeyFilter::matches(key, contexts) && Kleo::allUserIDsHaveFullValidity(key);
226 }
227 bool matches(const UserID &userID, MatchContexts contexts) const override
228 {
229 return DefaultKeyFilter::matches(userID.parent(), contexts) && userID.validity() >= UserID::Full;
230 }
231};
232
233static std::vector<std::shared_ptr<KeyFilter>> defaultFilters()
234{
235 return {
236 std::shared_ptr<KeyFilter>(new MyCertificatesKeyFilter),
237 std::shared_ptr<KeyFilter>(new FullCertificatesKeyFilter),
238 std::shared_ptr<KeyFilter>(new OtherCertificatesKeyFilter),
239 std::shared_ptr<KeyFilter>(new AllCertificatesKeyFilter),
240 std::shared_ptr<KeyFilter>(new UncertifiedOpenPGPKeysFilter),
241 std::shared_ptr<KeyFilter>(new KeyFullyCertifiedFilter),
242 std::shared_ptr<KeyFilter>(new KeyNotValidFilter),
243 };
244}
245
246class KeyFilterManager::Private
247{
248public:
249 Private()
250 : filters()
251 , model(this)
252 {
253 }
254 void clear()
255 {
256 filters.clear();
257 }
258
259 std::vector<std::shared_ptr<KeyFilter>> filters;
260 Model model;
261 GpgME::Protocol protocol = GpgME::UnknownProtocol;
262};
263
264KeyFilterManager *KeyFilterManager::mSelf = nullptr;
265
266KeyFilterManager::KeyFilterManager(QObject *parent)
267 : QObject(parent)
268 , d(new Private)
269{
270 mSelf = this;
271 // ### DF: doesn't a KStaticDeleter work more reliably?
274 }
275 reload();
276}
277
278KeyFilterManager::~KeyFilterManager()
279{
280 mSelf = nullptr;
281 if (d) {
282 d->model.beginResetModel();
283 d->clear();
284 d->model.endResetModel();
285 }
286}
287
288KeyFilterManager *KeyFilterManager::instance()
289{
290 if (!mSelf) {
291 mSelf = new KeyFilterManager();
292 }
293 return mSelf;
294}
295
296void KeyFilterManager::alwaysFilterByProtocol(GpgME::Protocol protocol)
297{
298 if (protocol != d->protocol) {
299 d->protocol = protocol;
300 reload();
301 Q_EMIT alwaysFilterByProtocolChanged(protocol);
302 }
303}
304
305const std::shared_ptr<KeyFilter> &KeyFilterManager::filterMatching(const Key &key, KeyFilter::MatchContexts contexts) const
306{
307 const auto it = std::find_if(d->filters.cbegin(), d->filters.cend(), [&key, contexts](const std::shared_ptr<KeyFilter> &filter) {
308 return filter->matches(key, contexts);
309 });
310 if (it != d->filters.cend()) {
311 return *it;
312 }
313 static const std::shared_ptr<KeyFilter> null;
314 return null;
315}
316
317std::vector<std::shared_ptr<KeyFilter>> KeyFilterManager::filtersMatching(const Key &key, KeyFilter::MatchContexts contexts) const
318{
319 std::vector<std::shared_ptr<KeyFilter>> result;
320 result.reserve(d->filters.size());
321 std::remove_copy_if(d->filters.begin(), d->filters.end(), std::back_inserter(result), [&key, contexts](const std::shared_ptr<KeyFilter> &filter) {
322 return !filter->matches(key, contexts);
323 });
324 return result;
325}
326
327namespace
328{
329static const auto byDecreasingSpecificity = [](const std::shared_ptr<KeyFilter> &lhs, const std::shared_ptr<KeyFilter> &rhs) {
330 return lhs->specificity() > rhs->specificity();
331};
332}
333
334void KeyFilterManager::reload()
335{
336 d->model.beginResetModel();
337 d->clear();
338
339 d->filters = defaultFilters();
340 KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc"));
341
342 const QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Key Filter #\\d+$")));
343 const bool ignoreDeVs = !DeVSCompliance::isCompliant();
344 for (QStringList::const_iterator it = groups.begin(); it != groups.end(); ++it) {
345 const KConfigGroup cfg(config, *it);
346 if (cfg.hasKey("is-de-vs") && ignoreDeVs) {
347 /* Don't show de-vs filters in other compliance modes */
348 continue;
349 }
350 d->filters.push_back(std::shared_ptr<KeyFilter>(new KConfigBasedKeyFilter(cfg)));
351 }
352 std::stable_sort(d->filters.begin(), d->filters.end(), byDecreasingSpecificity);
353
354 adjustFilters(d->filters, d->protocol);
355 d->model.endResetModel();
356 qCDebug(LIBKLEO_LOG) << "KeyFilterManager::" << __func__ << "final filter count is" << d->filters.size();
357}
358
359QAbstractItemModel *KeyFilterManager::model() const
360{
361 return &d->model;
362}
363
364const std::shared_ptr<KeyFilter> &KeyFilterManager::keyFilterByID(const QString &id) const
365{
366 const auto it = std::find_if(d->filters.begin(), d->filters.end(), [id](const std::shared_ptr<KeyFilter> &filter) {
367 return filter->id() == id;
368 });
369 if (it != d->filters.end()) {
370 return *it;
371 }
372 static const std::shared_ptr<KeyFilter> null;
373 return null;
374}
375
376const std::shared_ptr<KeyFilter> &KeyFilterManager::fromModelIndex(const QModelIndex &idx) const
377{
378 if (!idx.isValid() || idx.model() != &d->model || idx.row() < 0 || static_cast<unsigned>(idx.row()) >= d->filters.size()) {
379 static const std::shared_ptr<KeyFilter> null;
380 return null;
381 }
382 return d->filters[idx.row()];
383}
384
385QModelIndex KeyFilterManager::toModelIndex(const std::shared_ptr<KeyFilter> &kf) const
386{
387 if (!kf) {
388 return {};
389 }
390 const auto pair = std::equal_range(d->filters.cbegin(), d->filters.cend(), kf, byDecreasingSpecificity);
391 const auto it = std::find(pair.first, pair.second, kf);
392 if (it != pair.second) {
393 return d->model.index(it - d->filters.begin());
394 } else {
395 return QModelIndex();
396 }
397}
398
399int Model::rowCount(const QModelIndex &) const
400{
401 return m_keyFilterManagerPrivate->filters.size();
402}
403
404QVariant Model::data(const QModelIndex &idx, int role) const
405{
406 if (!idx.isValid() || idx.model() != this || idx.row() < 0 || static_cast<unsigned>(idx.row()) > m_keyFilterManagerPrivate->filters.size()) {
407 return QVariant();
408 }
409
410 const auto filter = m_keyFilterManagerPrivate->filters[idx.row()];
411 switch (role) {
413 return filter->icon();
414
415 case Qt::DisplayRole:
416 case Qt::EditRole:
417 return filter->name();
418 case Qt::ToolTipRole:
419 return filter->description();
420
421 case KeyFilterManager::FilterIdRole:
422 return filter->id();
423
424 case KeyFilterManager::FilterMatchContextsRole:
425 return QVariant::fromValue(filter->availableMatchContexts());
426
427 case KeyFilterManager::FilterRole:
428 return QVariant::fromValue(filter);
429
430 default:
431 return QVariant();
432 }
433}
434
435static KeyFilter::FontDescription
436get_fontdescription(const std::vector<std::shared_ptr<KeyFilter>> &filters, const Key &key, const KeyFilter::FontDescription &initial)
437{
438 return kdtools::accumulate_if(
439 filters.begin(),
440 filters.end(),
441 [&key](const std::shared_ptr<KeyFilter> &filter) {
442 return filter->matches(key, KeyFilter::Appearance);
443 },
444 initial,
445 [](const KeyFilter::FontDescription &lhs, const std::shared_ptr<KeyFilter> &rhs) {
446 return lhs.resolve(rhs->fontDescription());
447 });
448}
449
450QFont KeyFilterManager::font(const Key &key, const QFont &baseFont) const
451{
452 const KeyFilter::FontDescription fd = get_fontdescription(d->filters, key, KeyFilter::FontDescription());
453
454 return fd.font(baseFont);
455}
456
457static QColor get_color(const std::vector<std::shared_ptr<KeyFilter>> &filters, const Key &key, QColor (KeyFilter::*fun)() const)
458{
459 const auto it = std::find_if(filters.cbegin(), filters.cend(), [&fun, &key](const std::shared_ptr<KeyFilter> &filter) {
460 return filter->matches(key, KeyFilter::Appearance) && (filter.get()->*fun)().isValid();
461 });
462 if (it == filters.cend()) {
463 return {};
464 } else {
465 return (it->get()->*fun)();
466 }
467}
468
469static QColor get_color(const std::vector<std::shared_ptr<KeyFilter>> &filters, const UserID &userID, QColor (KeyFilter::*fun)() const)
470{
471 const auto it = std::find_if(filters.cbegin(), filters.cend(), [&fun, &userID](const std::shared_ptr<KeyFilter> &filter) {
472 return filter->matches(userID, KeyFilter::Appearance) && (filter.get()->*fun)().isValid();
473 });
474 if (it == filters.cend()) {
475 return {};
476 } else {
477 return (it->get()->*fun)();
478 }
479}
480
481static QString get_string(const std::vector<std::shared_ptr<KeyFilter>> &filters, const Key &key, QString (KeyFilter::*fun)() const)
482{
483 const auto it = std::find_if(filters.cbegin(), filters.cend(), [&fun, &key](const std::shared_ptr<KeyFilter> &filter) {
484 return filter->matches(key, KeyFilter::Appearance) && !(filter.get()->*fun)().isEmpty();
485 });
486 if (it == filters.cend()) {
487 return QString();
488 } else {
489 return (*it)->icon();
490 }
491}
492
493QColor KeyFilterManager::bgColor(const Key &key) const
494{
495 return get_color(d->filters, key, &KeyFilter::bgColor);
496}
497
498QColor KeyFilterManager::fgColor(const Key &key) const
499{
500 return get_color(d->filters, key, &KeyFilter::fgColor);
501}
502
503QColor KeyFilterManager::bgColor(const UserID &userID) const
504{
505 return get_color(d->filters, userID, &KeyFilter::bgColor);
506}
507
508QColor KeyFilterManager::fgColor(const UserID &userID) const
509{
510 return get_color(d->filters, userID, &KeyFilter::fgColor);
511}
512
513QIcon KeyFilterManager::icon(const Key &key) const
514{
515 const QString icon = get_string(d->filters, key, &KeyFilter::icon);
516 return icon.isEmpty() ? QIcon() : QIcon::fromTheme(icon);
517}
518
519Protocol KeyFilterManager::protocol() const
520{
521 return d->protocol;
522}
523
524class KeyFilterModel::Private
525{
526 friend class KeyFilterModel;
527 std::vector<std::shared_ptr<KeyFilter>> customFilters;
528};
529
530KeyFilterModel::KeyFilterModel(QObject *parent)
531 : QSortFilterProxyModel(parent)
532 , d(new Private)
533{
534 setSourceModel(KeyFilterManager::instance()->model());
535 connect(KeyFilterManager::instance(), &KeyFilterManager::alwaysFilterByProtocolChanged, this, [this](auto protocol) {
537 adjustFilters(d->customFilters, protocol);
539 });
540}
541
542void KeyFilterModel::prependCustomFilter(const std::shared_ptr<KeyFilter> &filter)
543{
545 d->customFilters.insert(d->customFilters.begin(), filter);
546 adjustFilters(d->customFilters, KeyFilterManager::instance()->protocol());
548}
549
550bool KeyFilterModel::isCustomFilter(int row) const
551{
552 return (row >= 0) && (row < int(d->customFilters.size()));
553}
554
555int KeyFilterModel::rowCount(const QModelIndex &parent) const
556{
557 return d->customFilters.size() + QSortFilterProxyModel::rowCount(parent);
558}
559
560int KeyFilterModel::columnCount(const QModelIndex &parent) const
561{
562 Q_UNUSED(parent)
563 // pretend that there is only one column to workaround a bug in
564 // QAccessibleTable which provides the accessibility interface for the
565 // pop-up of the combo box
566 return 1;
567}
568
569QModelIndex KeyFilterModel::mapToSource(const QModelIndex &index) const
570{
571 if (!index.isValid()) {
572 return {};
573 }
574 if (!isCustomFilter(index.row())) {
575 const int sourceRow = index.row() - d->customFilters.size();
576 return QSortFilterProxyModel::mapToSource(createIndex(sourceRow, index.column(), index.internalPointer()));
577 }
578 return {};
579}
580
581QModelIndex KeyFilterModel::mapFromSource(const QModelIndex &source_index) const
582{
583 const QModelIndex idx = QSortFilterProxyModel::mapFromSource(source_index);
584 return createIndex(d->customFilters.size() + idx.row(), idx.column(), idx.internalPointer());
585}
586
587QModelIndex KeyFilterModel::index(int row, int column, const QModelIndex &parent) const
588{
589 if (row < 0 || row >= rowCount()) {
590 return {};
591 }
592 if (row < int(d->customFilters.size())) {
593 return createIndex(row, column, nullptr);
594 } else {
595 const QModelIndex mi = QSortFilterProxyModel::index(row - d->customFilters.size(), column, parent);
596 return createIndex(row, column, mi.internalPointer());
597 }
598}
599
600Qt::ItemFlags KeyFilterModel::flags(const QModelIndex &index) const
601{
602 Q_UNUSED(index)
604}
605
607{
608 // Flat list
609 return {};
610}
611
612QVariant KeyFilterModel::data(const QModelIndex &index, int role) const
613{
614 if (!index.isValid()) {
615 return QVariant();
616 }
617
618 if (isCustomFilter(index.row())) {
619 const auto filter = d->customFilters[index.row()];
620 switch (role) {
622 return filter->icon();
623
624 case Qt::DisplayRole:
625 case Qt::EditRole:
626 return filter->name();
627 case Qt::ToolTipRole:
628 return filter->description();
629
630 case KeyFilterManager::FilterIdRole:
631 return filter->id();
632
633 case KeyFilterManager::FilterMatchContextsRole:
634 return QVariant::fromValue(filter->availableMatchContexts());
635
636 case KeyFilterManager::FilterRole:
637 return QVariant::fromValue(filter);
638
639 default:
640 return QVariant();
641 }
642 }
643
644 return QSortFilterProxyModel::data(index, role);
645}
646
647#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 Oct 11 2024 12:11:57 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.