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

KDE's Doxygen guidelines are available online.