diff --git a/autotests/abstractkeylistmodeltest.cpp b/autotests/abstractkeylistmodeltest.cpp index 44a9fcce9..ff26d9b15 100644 --- a/autotests/abstractkeylistmodeltest.cpp +++ b/autotests/abstractkeylistmodeltest.cpp @@ -1,284 +1,284 @@ /* autotests/abstractkeylistmodeltest.cpp This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "abstractkeylistmodeltest.h" -#include -#include +#include +#include + +#include +#include #include #include -#include -#include - using namespace Kleo; using namespace GpgME; Q_DECLARE_METATYPE(KeyGroup) namespace { Key createTestKey(const char *uid) { static int count = 0; count++; gpgme_key_t key; gpgme_key_from_uid(&key, uid); const QByteArray fingerprint = QByteArray::number(count, 16).rightJustified(40, '0'); key->fpr = strdup(fingerprint.constData()); return Key(key, false); } KeyGroup createGroup(const QString &name, const std::vector &keys = std::vector(), KeyGroup::Source source = KeyGroup::ApplicationConfig, const QString &configName = QString()) { const KeyGroup::Id groupId = ((source == KeyGroup::ApplicationConfig) // ? (configName.isEmpty() ? name : configName) : name); KeyGroup g(groupId, name, keys, source); return g; } } void AbstractKeyListModelTest::testCreation() { QScopedPointer model(createModel()); QCOMPARE(model->rowCount(), 0); } void AbstractKeyListModelTest::testSetKeys() { QScopedPointer model(createModel()); const std::vector keys = { createTestKey("test1@example.net"), }; model->setKeys(keys); QCOMPARE(model->rowCount(), 1); QVERIFY(model->index(keys[0]).isValid()); const std::vector otherKeys = { createTestKey("test2@example.net"), createTestKey("test3@example.net"), }; model->setKeys(otherKeys); QCOMPARE(model->rowCount(), 2); QVERIFY(model->index(otherKeys[0]).isValid()); QVERIFY(model->index(otherKeys[1]).isValid()); QVERIFY(!model->index(keys[0]).isValid()); } void AbstractKeyListModelTest::testSetGroups() { QScopedPointer model(createModel()); const std::vector groups = { createGroup("test1"), }; model->setGroups(groups); QCOMPARE(model->rowCount(), 1); QVERIFY(model->index(groups[0]).isValid()); const std::vector otherGroups = { createGroup("test2"), createGroup("test3"), }; model->setGroups(otherGroups); QCOMPARE(model->rowCount(), 2); QVERIFY(model->index(otherGroups[0]).isValid()); QVERIFY(model->index(otherGroups[1]).isValid()); QVERIFY(!model->index(groups[0]).isValid()); } void AbstractKeyListModelTest::testKeys() { QScopedPointer model(createModel()); const Key key = createTestKey("test@example.net"); const KeyGroup group = createGroup(QStringLiteral("test"), {key}); model->setKeys({key}); model->setGroups({group}); QCOMPARE(model->rowCount(), 2); const QModelIndex keyIndex = model->index(key); QVERIFY(keyIndex.isValid()); const QModelIndex groupIndex = model->index(group); QVERIFY(groupIndex.isValid()); { const auto keys = model->keys({}); QCOMPARE(keys.size(), 0); } { const auto keys = model->keys({keyIndex}); QCOMPARE(keys.size(), 1); QCOMPARE(keys[0].userID(0).addrSpec(), UserID::addrSpecFromString("test@example.net")); } { // duplicate keys are removed from result const auto keys = model->keys({keyIndex, keyIndex}); QCOMPARE(keys.size(), 1); QCOMPARE(keys[0].userID(0).addrSpec(), UserID::addrSpecFromString("test@example.net")); } { // null keys are removed from result const auto keys = model->keys({groupIndex}); QCOMPARE(keys.size(), 0); } } void AbstractKeyListModelTest::testIndex() { QScopedPointer model(createModel()); const Key key = createTestKey("test@example.net"); const std::vector groups = { createGroup("test", {key}, KeyGroup::UnknownSource), createGroup("test", {key}, KeyGroup::GnuPGConfig), createGroup("test", {key}, KeyGroup::ApplicationConfig, "test"), createGroup("test", {key}, KeyGroup::ApplicationConfig, "otherConfigName"), }; model->setKeys({key}); model->setGroups(groups); const QModelIndex keyIndex = model->index(0, 0); QVERIFY(keyIndex.isValid()); QVERIFY(!model->key(keyIndex).isNull()); const QModelIndex groupIndex = model->index(1, 0); QVERIFY(groupIndex.isValid()); QVERIFY(!model->group(groupIndex).isNull()); } void AbstractKeyListModelTest::testIndexForGroup() { QScopedPointer model(createModel()); const Key key = createTestKey("test@example.net"); const std::vector groups = { createGroup("test", {key}, KeyGroup::UnknownSource), createGroup("test", {key}, KeyGroup::GnuPGConfig), createGroup("test", {key}, KeyGroup::ApplicationConfig, "test"), createGroup("test", {key}, KeyGroup::ApplicationConfig, "otherConfigName"), }; model->setKeys({key}); model->setGroups(groups); QSet rows; for (const KeyGroup &group : groups) { const QModelIndex groupIndex = model->index(group); QVERIFY(groupIndex.isValid()); rows.insert(groupIndex.row()); } QCOMPARE(rows.size(), 4); } void AbstractKeyListModelTest::testAddGroup() { QScopedPointer model(createModel()); { const QModelIndex resultIndex = model->addGroup(KeyGroup()); QVERIFY(!resultIndex.isValid()); QCOMPARE(model->rowCount(), 0); } { const KeyGroup group = createGroup(QStringLiteral("test")); const QModelIndex resultIndex = model->addGroup(group); QVERIFY(resultIndex.isValid()); QCOMPARE(resultIndex.row(), 0); QCOMPARE(resultIndex.column(), 0); QVERIFY(!resultIndex.parent().isValid()); QCOMPARE(model->rowCount(), 1); const KeyGroup groupInModel = model->group(model->index(0, 0)); QVERIFY(!groupInModel.isNull()); QCOMPARE(groupInModel.id(), group.id()); QCOMPARE(groupInModel.source(), group.source()); QCOMPARE(groupInModel.name(), group.name()); QCOMPARE(groupInModel.keys().size(), group.keys().size()); } } void AbstractKeyListModelTest::testSetData() { QScopedPointer model(createModel()); const Key key = createTestKey("test@example.net"); const KeyGroup group = createGroup(QStringLiteral("test")); model->setKeys({key}); model->setGroups({group}); const KeyGroup updatedGroup = createGroup(QStringLiteral("updated"), {key}); QVERIFY(!model->setData(QModelIndex(), QVariant::fromValue(updatedGroup))); QVERIFY(!model->setData(model->index(key), QVariant::fromValue(updatedGroup))); const QModelIndex groupIndex = model->index(group); QVERIFY(model->setData(groupIndex, QVariant::fromValue(updatedGroup))); const KeyGroup groupInModel = model->group(groupIndex); QVERIFY(!groupInModel.isNull()); QCOMPARE(groupInModel.name(), updatedGroup.name()); QCOMPARE(groupInModel.keys().size(), updatedGroup.keys().size()); } void AbstractKeyListModelTest::testRemoveGroup() { QScopedPointer model(createModel()); const KeyGroup group = createGroup(QStringLiteral("test")); model->setGroups({group}); { const bool result = model->removeGroup(KeyGroup()); QVERIFY(!result); QCOMPARE(model->rowCount(), 1); } { const KeyGroup otherGroup = createGroup(QStringLiteral("test2")); const bool result = model->removeGroup(otherGroup); QVERIFY(!result); QCOMPARE(model->rowCount(), 1); } { const bool result = model->removeGroup(group); QVERIFY(result); QCOMPARE(model->rowCount(), 0); } } void AbstractKeyListModelTest::testClear() { QScopedPointer model(createModel()); model->setGroups({ createGroup("test"), }); model->clear(AbstractKeyListModel::Keys); QCOMPARE(model->rowCount(), 1); model->clear(AbstractKeyListModel::Groups); QCOMPARE(model->rowCount(), 0); } diff --git a/autotests/flatkeylistmodeltest.cpp b/autotests/flatkeylistmodeltest.cpp index 29b30614a..eed0bd8dc 100644 --- a/autotests/flatkeylistmodeltest.cpp +++ b/autotests/flatkeylistmodeltest.cpp @@ -1,31 +1,31 @@ /* autotests/flatkeylistmodeltest.cpp This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "abstractkeylistmodeltest.h" -#include +#include #include using namespace Kleo; class FlatKeyListModelTest : public AbstractKeyListModelTest { Q_OBJECT private: AbstractKeyListModel *createModel() override { return AbstractKeyListModel::createFlatKeyListModel(this); } }; QTEST_MAIN(FlatKeyListModelTest) #include "flatkeylistmodeltest.moc" diff --git a/autotests/hierarchicalkeylistmodeltest.cpp b/autotests/hierarchicalkeylistmodeltest.cpp index f59317e98..824b95e01 100644 --- a/autotests/hierarchicalkeylistmodeltest.cpp +++ b/autotests/hierarchicalkeylistmodeltest.cpp @@ -1,31 +1,31 @@ /* autotests/hierarchicalkeylistmodeltest.cpp This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "abstractkeylistmodeltest.h" -#include +#include #include using namespace Kleo; class HierarchicalKeyListModelTest : public AbstractKeyListModelTest { Q_OBJECT private: AbstractKeyListModel *createModel() override { return AbstractKeyListModel::createHierarchicalKeyListModel(this); } }; QTEST_MAIN(HierarchicalKeyListModelTest) #include "hierarchicalkeylistmodeltest.moc" diff --git a/src/kleo/checksumdefinition.cpp b/src/kleo/checksumdefinition.cpp index 50660d3a0..6251a1fcb 100644 --- a/src/kleo/checksumdefinition.cpp +++ b/src/kleo/checksumdefinition.cpp @@ -1,433 +1,432 @@ /* -*- mode: c++; c-basic-offset:4 -*- checksumdefinition.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "checksumdefinition.h" #include "kleoexception.h" #include #include #include #include +#include #include #include #include #include #include #include #include #include - -#include #include #ifdef stdin #undef stdin // pah.. #endif using namespace Kleo; static QMutex installPathMutex; Q_GLOBAL_STATIC(QString, _installPath) QString ChecksumDefinition::installPath() { const QMutexLocker locker(&installPathMutex); QString *const ip = _installPath(); if (ip->isEmpty()) { if (QCoreApplication::instance()) { *ip = QCoreApplication::applicationDirPath(); } else { qCWarning(LIBKLEO_LOG) << "checksumdefinition.cpp: installPath() called before QCoreApplication was constructed"; } } return *ip; } void ChecksumDefinition::setInstallPath(const QString &ip) { const QMutexLocker locker(&installPathMutex); *_installPath() = ip; } // Checksum Definition #N groups static const QLatin1String ID_ENTRY("id"); static const QLatin1String NAME_ENTRY("Name"); static const QLatin1String CREATE_COMMAND_ENTRY("create-command"); static const QLatin1String VERIFY_COMMAND_ENTRY("verify-command"); static const QLatin1String FILE_PATTERNS_ENTRY("file-patterns"); static const QLatin1String OUTPUT_FILE_ENTRY("output-file"); static const QLatin1String FILE_PLACEHOLDER("%f"); static const QLatin1String INSTALLPATH_PLACEHOLDER("%I"); static const QLatin1String NULL_SEPARATED_STDIN_INDICATOR("0|"); static const QLatin1Char NEWLINE_SEPARATED_STDIN_INDICATOR('|'); // ChecksumOperations group static const QLatin1String CHECKSUM_DEFINITION_ID_ENTRY("checksum-definition-id"); namespace { class ChecksumDefinitionError : public Kleo::Exception { const QString m_id; public: ChecksumDefinitionError(const QString &id, const QString &message) : Kleo::Exception(GPG_ERR_INV_PARAMETER, i18n("Error in checksum definition %1: %2", id, message), MessageOnly) , m_id(id) { } ~ChecksumDefinitionError() throw() override { } const QString &checksumDefinitionId() const { return m_id; } }; } static QString try_extensions(const QString &path) { static const char exts[][4] = { "", "exe", "bat", "bin", "cmd", }; static const size_t numExts = sizeof exts / sizeof *exts; for (unsigned int i = 0; i < numExts; ++i) { const QFileInfo fi(path + QLatin1Char('.') + QLatin1String(exts[i])); if (fi.exists()) { return fi.filePath(); } } return QString(); } static void parse_command(QString cmdline, const QString &id, const QString &whichCommand, QString *command, QStringList *prefix, QStringList *suffix, ChecksumDefinition::ArgumentPassingMethod *method) { Q_ASSERT(prefix); Q_ASSERT(suffix); Q_ASSERT(method); KShell::Errors errors; QStringList l; if (cmdline.startsWith(NULL_SEPARATED_STDIN_INDICATOR)) { *method = ChecksumDefinition::NullSeparatedInputFile; cmdline.remove(0, 2); } else if (cmdline.startsWith(NEWLINE_SEPARATED_STDIN_INDICATOR)) { *method = ChecksumDefinition::NewlineSeparatedInputFile; cmdline.remove(0, 1); } else { *method = ChecksumDefinition::CommandLine; } if (*method != ChecksumDefinition::CommandLine && cmdline.contains(FILE_PLACEHOLDER)) { throw ChecksumDefinitionError(id, i18n("Cannot use both %f and | in '%1'", whichCommand)); } cmdline .replace(FILE_PLACEHOLDER, QLatin1String("__files_go_here__")) // .replace(INSTALLPATH_PLACEHOLDER, QStringLiteral("__path_goes_here__")); l = KShell::splitArgs(cmdline, KShell::AbortOnMeta | KShell::TildeExpand, &errors); l = l.replaceInStrings(QStringLiteral("__files_go_here__"), FILE_PLACEHOLDER); static const QRegularExpression regExpression(QLatin1String(".*__path_goes_here__.*")); if (l.indexOf(regExpression) >= 0) { l = l.replaceInStrings(QStringLiteral("__path_goes_here__"), ChecksumDefinition::installPath()); } if (errors == KShell::BadQuoting) { throw ChecksumDefinitionError(id, i18n("Quoting error in '%1' entry", whichCommand)); } if (errors == KShell::FoundMeta) { throw ChecksumDefinitionError(id, i18n("'%1' too complex (would need shell)", whichCommand)); } qCDebug(LIBKLEO_LOG) << "ChecksumDefinition[" << id << ']' << l; if (l.empty()) { throw ChecksumDefinitionError(id, i18n("'%1' entry is empty/missing", whichCommand)); } const QFileInfo fi1(l.front()); if (fi1.isAbsolute()) { *command = try_extensions(l.front()); } else { *command = QStandardPaths::findExecutable(fi1.fileName()); } if (command->isEmpty()) { throw ChecksumDefinitionError(id, i18n("'%1' empty or not found", whichCommand)); } const int idx1 = l.indexOf(FILE_PLACEHOLDER); if (idx1 < 0) { // none -> append *prefix = l.mid(1); } else { *prefix = l.mid(1, idx1 - 1); *suffix = l.mid(idx1 + 1); } switch (*method) { case ChecksumDefinition::CommandLine: qCDebug(LIBKLEO_LOG) << "ChecksumDefinition[" << id << ']' << *command << *prefix << FILE_PLACEHOLDER << *suffix; break; case ChecksumDefinition::NewlineSeparatedInputFile: qCDebug(LIBKLEO_LOG) << "ChecksumDefinition[" << id << ']' << "find | " << *command << *prefix; break; case ChecksumDefinition::NullSeparatedInputFile: qCDebug(LIBKLEO_LOG) << "ChecksumDefinition[" << id << ']' << "find -print0 | " << *command << *prefix; break; case ChecksumDefinition::NumArgumentPassingMethods: Q_ASSERT(!"Should not happen"); break; } } namespace { class KConfigBasedChecksumDefinition : public ChecksumDefinition { public: explicit KConfigBasedChecksumDefinition(const KConfigGroup &group) : ChecksumDefinition(group.readEntryUntranslated(ID_ENTRY), group.readEntry(NAME_ENTRY), group.readEntry(OUTPUT_FILE_ENTRY), group.readEntry(FILE_PATTERNS_ENTRY, QStringList())) { if (id().isEmpty()) { throw ChecksumDefinitionError(group.name(), i18n("'id' entry is empty/missing")); } if (outputFileName().isEmpty()) { throw ChecksumDefinitionError(id(), i18n("'output-file' entry is empty/missing")); } if (patterns().empty()) { throw ChecksumDefinitionError(id(), i18n("'file-patterns' entry is empty/missing")); } // create-command ArgumentPassingMethod method; parse_command(group.readEntry(CREATE_COMMAND_ENTRY), id(), CREATE_COMMAND_ENTRY, &m_createCommand, &m_createPrefixArguments, &m_createPostfixArguments, &method); setCreateCommandArgumentPassingMethod(method); // verify-command parse_command(group.readEntry(VERIFY_COMMAND_ENTRY), id(), VERIFY_COMMAND_ENTRY, &m_verifyCommand, &m_verifyPrefixArguments, &m_verifyPostfixArguments, &method); setVerifyCommandArgumentPassingMethod(method); } private: QString doGetCreateCommand() const override { return m_createCommand; } QStringList doGetCreateArguments(const QStringList &files) const override { return m_createPrefixArguments + files + m_createPostfixArguments; } QString doGetVerifyCommand() const override { return m_verifyCommand; } QStringList doGetVerifyArguments(const QStringList &files) const override { return m_verifyPrefixArguments + files + m_verifyPostfixArguments; } private: QString m_createCommand, m_verifyCommand; QStringList m_createPrefixArguments, m_createPostfixArguments; QStringList m_verifyPrefixArguments, m_verifyPostfixArguments; }; } ChecksumDefinition::ChecksumDefinition(const QString &id, const QString &label, const QString &outputFileName, const QStringList &patterns) : m_id(id) , m_label(label.isEmpty() ? id : label) , m_outputFileName(outputFileName) , m_patterns(patterns) , m_createMethod(CommandLine) , m_verifyMethod(CommandLine) { } ChecksumDefinition::~ChecksumDefinition() { } QString ChecksumDefinition::createCommand() const { return doGetCreateCommand(); } QString ChecksumDefinition::verifyCommand() const { return doGetVerifyCommand(); } #if 0 QStringList ChecksumDefinition::createCommandArguments(const QStringList &files) const { return doGetCreateArguments(files); } QStringList ChecksumDefinition::verifyCommandArguments(const QStringList &files) const { return doGetVerifyArguments(files); } #endif static QByteArray make_input(const QStringList &files, char sep) { QByteArray result; for (const QString &file : files) { result += QFile::encodeName(file); result += sep; } return result; } static bool start_command(QProcess *p, const char *functionName, const QString &cmd, const QStringList &args, const QStringList &files, ChecksumDefinition::ArgumentPassingMethod method) { if (!p) { qCWarning(LIBKLEO_LOG) << functionName << ": process == NULL"; return false; } switch (method) { case ChecksumDefinition::NumArgumentPassingMethods: Q_ASSERT(!"Should not happen"); case ChecksumDefinition::CommandLine: qCDebug(LIBKLEO_LOG) << "Starting: " << cmd << " " << args.join(QLatin1Char(' ')); p->start(cmd, args, QIODevice::ReadOnly); return true; case ChecksumDefinition::NewlineSeparatedInputFile: case ChecksumDefinition::NullSeparatedInputFile: qCDebug(LIBKLEO_LOG) << "Starting: " << cmd << " " << args.join(QLatin1Char(' ')); p->start(cmd, args, QIODevice::ReadWrite); if (!p->waitForStarted()) { return false; } const char sep = method == ChecksumDefinition::NewlineSeparatedInputFile ? '\n' : '\0'; const QByteArray stdin = make_input(files, sep); if (p->write(stdin) != stdin.size()) { return false; } p->closeWriteChannel(); return true; } return false; // make compiler happy } bool ChecksumDefinition::startCreateCommand(QProcess *p, const QStringList &files) const { return start_command(p, Q_FUNC_INFO, doGetCreateCommand(), m_createMethod == CommandLine ? doGetCreateArguments(files) : doGetCreateArguments(QStringList()), files, m_createMethod); } bool ChecksumDefinition::startVerifyCommand(QProcess *p, const QStringList &files) const { return start_command(p, Q_FUNC_INFO, doGetVerifyCommand(), m_verifyMethod == CommandLine ? doGetVerifyArguments(files) : doGetVerifyArguments(QStringList()), files, m_verifyMethod); } // static std::vector> ChecksumDefinition::getChecksumDefinitions() { QStringList errors; return getChecksumDefinitions(errors); } // static std::vector> ChecksumDefinition::getChecksumDefinitions(QStringList &errors) { std::vector> result; KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc")); const QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Checksum Definition #"))); result.reserve(groups.size()); for (const QString &group : groups) { try { const std::shared_ptr ad(new KConfigBasedChecksumDefinition(KConfigGroup(config, group))); result.push_back(ad); } catch (const std::exception &e) { qDebug() << e.what(); errors.push_back(QString::fromLocal8Bit(e.what())); } catch (...) { errors.push_back(i18n("Caught unknown exception in group %1", group)); } } return result; } // static std::shared_ptr ChecksumDefinition::getDefaultChecksumDefinition(const std::vector> &checksumDefinitions) { const KConfigGroup group(KSharedConfig::openConfig(), "ChecksumOperations"); const QString checksumDefinitionId = group.readEntry(CHECKSUM_DEFINITION_ID_ENTRY, QStringLiteral("sha256sum")); if (!checksumDefinitionId.isEmpty()) { for (const std::shared_ptr &cd : checksumDefinitions) { if (cd && cd->id() == checksumDefinitionId) { return cd; } } } if (!checksumDefinitions.empty()) { return checksumDefinitions.front(); } else { return std::shared_ptr(); } } // static void ChecksumDefinition::setDefaultChecksumDefinition(const std::shared_ptr &checksumDefinition) { if (!checksumDefinition) { return; } KConfigGroup group(KSharedConfig::openConfig(), "ChecksumOperations"); group.writeEntry(CHECKSUM_DEFINITION_ID_ENTRY, checksumDefinition->id()); group.sync(); } diff --git a/src/kleo/debug.cpp b/src/kleo/debug.cpp index 2f8cc6bf6..ec442c34a 100644 --- a/src/kleo/debug.cpp +++ b/src/kleo/debug.cpp @@ -1,50 +1,50 @@ /* kleo/debug.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "debug.h" #include "keygroup.h" -#include +#include #include using namespace Kleo; QDebug operator<<(QDebug debug, const GpgME::Key &key) { const bool oldSetting = debug.autoInsertSpaces(); debug.nospace() << "GpgME::Key("; if (key.isNull()) { debug << "null"; } else if (key.primaryFingerprint()) { debug << Formatting::summaryLine(key) << ", fpr: " << key.primaryFingerprint(); } else { debug << Formatting::summaryLine(key) << ", id: " << key.keyID(); } debug << ')'; debug.setAutoInsertSpaces(oldSetting); return debug.maybeSpace(); } QDebug operator<<(QDebug debug, const Kleo::KeyGroup &group) { const bool oldSetting = debug.autoInsertSpaces(); if (group.isNull()) { debug << "Null"; } else { debug.nospace() << group.name() << " (id: " << group.id() << ", source: " << group.source() << ", keys: " << group.keys().size() << ", isImmutable: " << group.isImmutable() << ")"; } debug.setAutoInsertSpaces(oldSetting); return debug.maybeSpace(); } diff --git a/src/kleo/defaultkeyfilter.cpp b/src/kleo/defaultkeyfilter.cpp index 83e58f13b..7ad0b55a1 100644 --- a/src/kleo/defaultkeyfilter.cpp +++ b/src/kleo/defaultkeyfilter.cpp @@ -1,531 +1,531 @@ /* defaultkeyfilter.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "defaultkeyfilter.h" -#include +#include #include #include using namespace GpgME; using namespace Kleo; static bool is_card_key(const Key &key) { const std::vector sks = key.subkeys(); return std::find_if(sks.begin(), sks.end(), std::mem_fn(&Subkey::isCardKey)) != sks.end(); } class DefaultKeyFilter::Private { public: Private() : mMatchContexts(AnyMatchContext) , mRevoked(DoesNotMatter) , mExpired(DoesNotMatter) , mInvalid(DoesNotMatter) , mDisabled(DoesNotMatter) , mRoot(DoesNotMatter) , mCanEncrypt(DoesNotMatter) , mCanSign(DoesNotMatter) , mCanCertify(DoesNotMatter) , mCanAuthenticate(DoesNotMatter) , mQualified(DoesNotMatter) , mCardKey(DoesNotMatter) , mHasSecret(DoesNotMatter) , mIsOpenPGP(DoesNotMatter) , mWasValidated(DoesNotMatter) , mIsDeVs(DoesNotMatter) , mBad(DoesNotMatter) , mOwnerTrust(LevelDoesNotMatter) , mOwnerTrustReferenceLevel(Key::Unknown) , mValidity(LevelDoesNotMatter) , mValidityReferenceLevel(UserID::Unknown) { } QColor mFgColor, mBgColor; QString mName; QString mIcon; QString mId; MatchContexts mMatchContexts; unsigned int mSpecificity = 0; bool mItalic = false; bool mBold = false; bool mStrikeOut = false; bool mUseFullFont = false; QFont mFont; TriState mRevoked; TriState mExpired; TriState mInvalid; TriState mDisabled; TriState mRoot; TriState mCanEncrypt; TriState mCanSign; TriState mCanCertify; TriState mCanAuthenticate; TriState mQualified; TriState mCardKey; TriState mHasSecret; TriState mIsOpenPGP; TriState mWasValidated; TriState mIsDeVs; TriState mBad; LevelState mOwnerTrust; GpgME::Key::OwnerTrust mOwnerTrustReferenceLevel; LevelState mValidity; GpgME::UserID::Validity mValidityReferenceLevel; }; DefaultKeyFilter::DefaultKeyFilter() : KeyFilter() , d_ptr(new Private()) { } DefaultKeyFilter::~DefaultKeyFilter() { } bool DefaultKeyFilter::matches(const Key &key, MatchContexts contexts) const { if (!(d_ptr->mMatchContexts & contexts)) { return false; } #ifdef MATCH #undef MATCH #endif #define MATCH(member, method) \ do { \ if (member != DoesNotMatter && key.method() != bool(member == Set)) { \ return false; \ } \ } while (false) #define IS_MATCH(what) MATCH(d_ptr->m##what, is##what) #define CAN_MATCH(what) MATCH(d_ptr->mCan##what, can##what) IS_MATCH(Revoked); IS_MATCH(Expired); IS_MATCH(Invalid); IS_MATCH(Disabled); IS_MATCH(Root); CAN_MATCH(Encrypt); CAN_MATCH(Sign); CAN_MATCH(Certify); CAN_MATCH(Authenticate); IS_MATCH(Qualified); if (d_ptr->mCardKey != DoesNotMatter) { if ((d_ptr->mCardKey == Set && !is_card_key(key)) || (d_ptr->mCardKey == NotSet && is_card_key(key))) { return false; } } MATCH(d_ptr->mHasSecret, hasSecret); #undef MATCH if (d_ptr->mIsOpenPGP != DoesNotMatter && bool(key.protocol() == GpgME::OpenPGP) != bool(d_ptr->mIsOpenPGP == Set)) { return false; } if (d_ptr->mWasValidated != DoesNotMatter && bool(key.keyListMode() & GpgME::Validate) != bool(d_ptr->mWasValidated == Set)) { return false; } if (d_ptr->mIsDeVs != DoesNotMatter && bool(Formatting::uidsHaveFullValidity(key) && Formatting::isKeyDeVs(key)) != bool(d_ptr->mIsDeVs == Set)) { return false; } if (d_ptr->mBad != DoesNotMatter && /* This is similar to GPGME::Key::isBad which was introduced in GPGME 1.13.0 */ bool(key.isNull() || key.isRevoked() || key.isExpired() || key.isDisabled() || key.isInvalid()) != bool(d_ptr->mBad == Set)) { return false; } switch (d_ptr->mOwnerTrust) { default: case LevelDoesNotMatter: break; case Is: if (key.ownerTrust() != d_ptr->mOwnerTrustReferenceLevel) { return false; } break; case IsNot: if (key.ownerTrust() == d_ptr->mOwnerTrustReferenceLevel) { return false; } break; case IsAtLeast: if (static_cast(key.ownerTrust()) < static_cast(d_ptr->mOwnerTrustReferenceLevel)) { return false; } break; case IsAtMost: if (static_cast(key.ownerTrust()) > static_cast(d_ptr->mOwnerTrustReferenceLevel)) { return false; } break; } const UserID uid = key.userID(0); switch (d_ptr->mValidity) { default: case LevelDoesNotMatter: break; case Is: if (uid.validity() != d_ptr->mValidityReferenceLevel) { return false; } break; case IsNot: if (uid.validity() == d_ptr->mValidityReferenceLevel) { return false; } break; case IsAtLeast: if (static_cast(uid.validity()) < static_cast(d_ptr->mValidityReferenceLevel)) { return false; } break; case IsAtMost: if (static_cast(uid.validity()) > static_cast(d_ptr->mValidityReferenceLevel)) { return false; } break; } return true; } KeyFilter::FontDescription DefaultKeyFilter::fontDescription() const { if (d_ptr->mUseFullFont) { return FontDescription::create(font(), bold(), italic(), strikeOut()); } else { return FontDescription::create(bold(), italic(), strikeOut()); } } void DefaultKeyFilter::setFgColor(const QColor &value) const { d_ptr->mFgColor = value; } void DefaultKeyFilter::setBgColor(const QColor &value) const { d_ptr->mBgColor = value; } void DefaultKeyFilter::setName(const QString &value) const { d_ptr->mName = value; } void DefaultKeyFilter::setIcon(const QString &value) const { d_ptr->mIcon = value; } void DefaultKeyFilter::setId(const QString &value) const { d_ptr->mId = value; } void DefaultKeyFilter::setMatchContexts(MatchContexts value) const { d_ptr->mMatchContexts = value; } void DefaultKeyFilter::setSpecificity(unsigned int value) const { d_ptr->mSpecificity = value; } void DefaultKeyFilter::setItalic(bool value) const { d_ptr->mItalic = value; } void DefaultKeyFilter::setBold(bool value) const { d_ptr->mBold = value; } void DefaultKeyFilter::setStrikeOut(bool value) const { d_ptr->mStrikeOut = value; } void DefaultKeyFilter::setUseFullFont(bool value) const { d_ptr->mUseFullFont = value; } void DefaultKeyFilter::setFont(const QFont &value) const { d_ptr->mFont = value; } void DefaultKeyFilter::setRevoked(DefaultKeyFilter::TriState value) const { d_ptr->mRevoked = value; } void DefaultKeyFilter::setExpired(DefaultKeyFilter::TriState value) const { d_ptr->mExpired = value; } void DefaultKeyFilter::setInvalid(DefaultKeyFilter::TriState value) const { d_ptr->mInvalid = value; } void DefaultKeyFilter::setDisabled(DefaultKeyFilter::TriState value) const { d_ptr->mDisabled = value; } void DefaultKeyFilter::setRoot(DefaultKeyFilter::TriState value) const { d_ptr->mRoot = value; } void DefaultKeyFilter::setCanEncrypt(DefaultKeyFilter::TriState value) const { d_ptr->mCanEncrypt = value; } void DefaultKeyFilter::setCanSign(DefaultKeyFilter::TriState value) const { d_ptr->mCanSign = value; } void DefaultKeyFilter::setCanCertify(DefaultKeyFilter::TriState value) const { d_ptr->mCanCertify = value; } void DefaultKeyFilter::setCanAuthenticate(DefaultKeyFilter::TriState value) const { d_ptr->mCanAuthenticate = value; } void DefaultKeyFilter::setQualified(DefaultKeyFilter::TriState value) const { d_ptr->mQualified = value; } void DefaultKeyFilter::setCardKey(DefaultKeyFilter::TriState value) const { d_ptr->mCardKey = value; } void DefaultKeyFilter::setHasSecret(DefaultKeyFilter::TriState value) const { d_ptr->mHasSecret = value; } void DefaultKeyFilter::setIsOpenPGP(DefaultKeyFilter::TriState value) const { d_ptr->mIsOpenPGP = value; } void DefaultKeyFilter::setWasValidated(DefaultKeyFilter::TriState value) const { d_ptr->mWasValidated = value; } void DefaultKeyFilter::setOwnerTrust(DefaultKeyFilter::LevelState value) const { d_ptr->mOwnerTrust = value; } void DefaultKeyFilter::setOwnerTrustReferenceLevel(GpgME::Key::OwnerTrust value) const { d_ptr->mOwnerTrustReferenceLevel = value; } void DefaultKeyFilter::setValidity(DefaultKeyFilter::LevelState value) const { d_ptr->mValidity = value; } void DefaultKeyFilter::setValidityReferenceLevel(GpgME::UserID::Validity value) const { d_ptr->mValidityReferenceLevel = value; } void DefaultKeyFilter::setIsDeVs(DefaultKeyFilter::TriState value) const { d_ptr->mIsDeVs = value; } void DefaultKeyFilter::setIsBad(DefaultKeyFilter::TriState value) const { d_ptr->mBad = value; } QColor DefaultKeyFilter::fgColor() const { return d_ptr->mFgColor; } QColor DefaultKeyFilter::bgColor() const { return d_ptr->mBgColor; } QString DefaultKeyFilter::name() const { return d_ptr->mName; } QString DefaultKeyFilter::icon() const { return d_ptr->mIcon; } QString DefaultKeyFilter::id() const { return d_ptr->mId; } QFont DefaultKeyFilter::font() const { return d_ptr->mFont; } KeyFilter::MatchContexts DefaultKeyFilter::availableMatchContexts() const { return d_ptr->mMatchContexts; } unsigned int DefaultKeyFilter::specificity() const { return d_ptr->mSpecificity; } bool DefaultKeyFilter::italic() const { return d_ptr->mItalic; } bool DefaultKeyFilter::bold() const { return d_ptr->mBold; } bool DefaultKeyFilter::strikeOut() const { return d_ptr->mStrikeOut; } bool DefaultKeyFilter::useFullFont() const { return d_ptr->mUseFullFont; } DefaultKeyFilter::TriState DefaultKeyFilter::revoked() const { return d_ptr->mRevoked; } DefaultKeyFilter::TriState DefaultKeyFilter::expired() const { return d_ptr->mExpired; } DefaultKeyFilter::TriState DefaultKeyFilter::invalid() const { return d_ptr->mInvalid; } DefaultKeyFilter::TriState DefaultKeyFilter::disabled() const { return d_ptr->mDisabled; } DefaultKeyFilter::TriState DefaultKeyFilter::root() const { return d_ptr->mRoot; } DefaultKeyFilter::TriState DefaultKeyFilter::canEncrypt() const { return d_ptr->mCanEncrypt; } DefaultKeyFilter::TriState DefaultKeyFilter::canSign() const { return d_ptr->mCanSign; } DefaultKeyFilter::TriState DefaultKeyFilter::canCertify() const { return d_ptr->mCanCertify; } DefaultKeyFilter::TriState DefaultKeyFilter::canAuthenticate() const { return d_ptr->mCanAuthenticate; } DefaultKeyFilter::TriState DefaultKeyFilter::qualified() const { return d_ptr->mQualified; } DefaultKeyFilter::TriState DefaultKeyFilter::cardKey() const { return d_ptr->mCardKey; } DefaultKeyFilter::TriState DefaultKeyFilter::hasSecret() const { return d_ptr->mHasSecret; } DefaultKeyFilter::TriState DefaultKeyFilter::isOpenPGP() const { return d_ptr->mIsOpenPGP; } DefaultKeyFilter::TriState DefaultKeyFilter::wasValidated() const { return d_ptr->mWasValidated; } DefaultKeyFilter::LevelState DefaultKeyFilter::ownerTrust() const { return d_ptr->mOwnerTrust; } GpgME::Key::OwnerTrust DefaultKeyFilter::ownerTrustReferenceLevel() const { return d_ptr->mOwnerTrustReferenceLevel; } DefaultKeyFilter::LevelState DefaultKeyFilter::validity() const { return d_ptr->mValidity; } GpgME::UserID::Validity DefaultKeyFilter::validityReferenceLevel() const { return d_ptr->mValidityReferenceLevel; } DefaultKeyFilter::TriState DefaultKeyFilter::isDeVS() const { return d_ptr->mIsDeVs; } DefaultKeyFilter::TriState DefaultKeyFilter::isBad() const { return d_ptr->mBad; } diff --git a/src/kleo/defaultkeyfilter.h b/src/kleo/defaultkeyfilter.h index f2613359d..230ae5839 100644 --- a/src/kleo/defaultkeyfilter.h +++ b/src/kleo/defaultkeyfilter.h @@ -1,139 +1,138 @@ /* defaultkeyfilter.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "keyfilter.h" - #include "kleo_export.h" #include #include #include #include #include namespace Kleo { /** Default implementation of key filter class. */ class KLEO_EXPORT DefaultKeyFilter : public KeyFilter { public: DefaultKeyFilter(); ~DefaultKeyFilter() override; /** Used for bool checks */ enum TriState { // clang-format off DoesNotMatter = 0, Set = 1, NotSet = 2, // clang-format on }; /** Used for level checks */ enum LevelState { // clang-format off LevelDoesNotMatter = 0, Is = 1, IsNot = 2, IsAtLeast = 3, IsAtMost = 4, // clang-format on }; bool matches(const GpgME::Key &key, MatchContexts ctx) const override; unsigned int specificity() const override; void setSpecificity(unsigned int value) const; QString id() const override; void setId(const QString &value) const; KeyFilter::MatchContexts availableMatchContexts() const override; void setMatchContexts(KeyFilter::MatchContexts value) const; QColor fgColor() const override; void setFgColor(const QColor &value) const; QColor bgColor() const override; void setBgColor(const QColor &value) const; FontDescription fontDescription() const override; QString name() const override; void setName(const QString &value) const; QString icon() const override; void setIcon(const QString &value) const; QFont font() const; void setFont(const QFont &value) const; TriState revoked() const; TriState expired() const; TriState invalid() const; TriState disabled() const; TriState root() const; TriState canEncrypt() const; TriState canSign() const; TriState canCertify() const; TriState canAuthenticate() const; TriState qualified() const; TriState cardKey() const; TriState hasSecret() const; TriState isOpenPGP() const; TriState wasValidated() const; TriState isDeVS() const; TriState isBad() const; LevelState ownerTrust() const; GpgME::Key::OwnerTrust ownerTrustReferenceLevel() const; LevelState validity() const; GpgME::UserID::Validity validityReferenceLevel() const; bool italic() const; bool bold() const; bool strikeOut() const; bool useFullFont() const; void setRevoked(const TriState) const; void setExpired(const TriState) const; void setInvalid(const TriState) const; void setDisabled(const TriState) const; void setRoot(const TriState) const; void setCanEncrypt(const TriState) const; void setCanSign(const TriState) const; void setCanCertify(const TriState) const; void setCanAuthenticate(const TriState) const; void setQualified(const TriState) const; void setCardKey(const TriState) const; void setHasSecret(const TriState) const; void setIsOpenPGP(const TriState) const; void setWasValidated(const TriState) const; void setIsDeVs(const TriState) const; void setIsBad(const TriState) const; void setOwnerTrust(const LevelState) const; void setOwnerTrustReferenceLevel(const GpgME::Key::OwnerTrust) const; void setValidity(const LevelState) const; void setValidityReferenceLevel(const GpgME::UserID::Validity) const; void setItalic(bool value) const; void setBold(bool value) const; void setStrikeOut(bool value) const; void setUseFullFont(bool value) const; private: class Private; const QScopedPointer d_ptr; }; } // namespace Kleo diff --git a/src/kleo/defaultkeygenerationjob.cpp b/src/kleo/defaultkeygenerationjob.cpp index 8365ef75e..17e4b1ad3 100644 --- a/src/kleo/defaultkeygenerationjob.cpp +++ b/src/kleo/defaultkeygenerationjob.cpp @@ -1,123 +1,123 @@ /* This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "defaultkeygenerationjob.h" -#include -#include +#include +#include #include #include using namespace Kleo; namespace Kleo { class DefaultKeyGenerationJob::DefaultKeyGenerationJobPrivate { public: DefaultKeyGenerationJobPrivate() { } ~DefaultKeyGenerationJobPrivate() { if (job) { job->deleteLater(); } } QString passphrase; QPointer job; }; } DefaultKeyGenerationJob::DefaultKeyGenerationJob(QObject *parent) : Job(parent) , d(new DefaultKeyGenerationJob::DefaultKeyGenerationJobPrivate()) { } DefaultKeyGenerationJob::~DefaultKeyGenerationJob() = default; QString DefaultKeyGenerationJob::auditLogAsHtml() const { return d->job ? d->job->auditLogAsHtml() : QString(); } GpgME::Error DefaultKeyGenerationJob::auditLogError() const { return d->job ? d->job->auditLogError() : GpgME::Error(); } void DefaultKeyGenerationJob::slotCancel() { if (d->job) { d->job->slotCancel(); } } void DefaultKeyGenerationJob::setPassphrase(const QString &passphrase) { // null QString = ask for passphrase // empty QString = empty passphrase // non-empty QString = passphrase d->passphrase = passphrase.isNull() ? QLatin1String("") : passphrase; } namespace { QString passphraseParameter(const QString &passphrase) { if (passphrase.isNull()) { return QStringLiteral("%ask-passphrase"); } else if (passphrase.isEmpty()) { return QStringLiteral("%no-protection"); } else { return QStringLiteral("passphrase: %1").arg(passphrase); }; } } GpgME::Error DefaultKeyGenerationJob::start(const QString &email, const QString &name) { const QString passphrase = passphraseParameter(d->passphrase); const QString args = QStringLiteral( "\n" "key-type: RSA\n" "key-length: 2048\n" "key-usage: sign\n" "subkey-type: RSA\n" "subkey-length: 2048\n" "subkey-usage: encrypt\n" "%1\n" "name-email: %2\n" "name-real: %3\n" "") .arg(passphrase, email, name); d->job = QGpgME::openpgp()->keyGenerationJob(); d->job->installEventFilter(this); connect(d->job.data(), &QGpgME::KeyGenerationJob::result, this, &DefaultKeyGenerationJob::result); connect(d->job.data(), &QGpgME::KeyGenerationJob::done, this, &DefaultKeyGenerationJob::done); connect(d->job.data(), &QGpgME::KeyGenerationJob::done, this, &QObject::deleteLater); return d->job->start(args); } bool DefaultKeyGenerationJob::eventFilter(QObject *watched, QEvent *event) { // Intercept the KeyGenerationJob's deferred delete event. We want the job // to live at least as long as we do so we can delegate calls to it. We will // delete the job manually afterwards. if (watched == d->job && event->type() == QEvent::DeferredDelete) { return true; } return Job::eventFilter(watched, event); } diff --git a/src/kleo/defaultkeygenerationjob.h b/src/kleo/defaultkeygenerationjob.h index 9bb0708ed..46a08b56c 100644 --- a/src/kleo/defaultkeygenerationjob.h +++ b/src/kleo/defaultkeygenerationjob.h @@ -1,62 +1,63 @@ /* This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include - #include "kleo_export.h" + +#include + #include namespace GpgME { class KeyGenerationResult; } namespace Kleo { /** * Generates a PGP RSA/2048 bit key pair for given name and email address. * * @since 5.4 */ class KLEO_EXPORT DefaultKeyGenerationJob : public QGpgME::Job { Q_OBJECT public: explicit DefaultKeyGenerationJob(QObject *parent = nullptr); ~DefaultKeyGenerationJob() override; /** * Set key passphrase * * Use this method to specify custom passphrase, including an empty * one. If no passphrase (not even empty) is specified, gpg me will * automatically prompt for passphrase using Pinentry dialog. */ void setPassphrase(const QString &passphrase); GpgME::Error start(const QString &email, const QString &name); QString auditLogAsHtml() const override; GpgME::Error auditLogError() const override; public Q_SLOTS: void slotCancel() override; Q_SIGNALS: void result(const GpgME::KeyGenerationResult &result, const QByteArray &pubkeyData, const QString &auditLogAsHtml, const GpgME::Error &auditLogError); protected: bool eventFilter(QObject *watched, QEvent *event) override; private: class DefaultKeyGenerationJobPrivate; std::unique_ptr const d; }; } diff --git a/src/kleo/docaction.h b/src/kleo/docaction.h index ce616eb38..aea4725e8 100644 --- a/src/kleo/docaction.h +++ b/src/kleo/docaction.h @@ -1,56 +1,56 @@ /* kleo/docaction.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Andre Heinecke SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" -#include - #include +#include + class QIcon; class QString; namespace Kleo { /** An action for custom documentation which is opened by file. This can be used for PDF documents like the GnuPG manual. The action is disabled and invisible if the corresponding file cannout be found at creation. Otherwise triggered calls QDesktopServicesOpenURL on the file. */ class KLEO_EXPORT DocAction : public QAction { Q_OBJECT public: /* Create a DocAction with icon, text and file name of the document * * @a filename The name of the documentation file. * @a pathHint A path relative to QCoreApplication::applicationDirPath() to look for the file. * * */ explicit DocAction(const QIcon &icon, const QString &text, const QString &filename, const QString &pathHint = QString(), QObject *parent = nullptr); ~DocAction() override; DocAction(const QString &, QObject *parent) = delete; DocAction(QObject *parent) = delete; private: class Private; std::unique_ptr d; }; } // namespace Kleo diff --git a/src/kleo/enum.cpp b/src/kleo/enum.cpp index 3d13e04f9..e7dee37f4 100644 --- a/src/kleo/enum.cpp +++ b/src/kleo/enum.cpp @@ -1,310 +1,310 @@ /* kleo/enum.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 #include "enum.h" -#include +#include #include -#include - #include #include +#include +#include + #include #include -#include -#include +#include static const struct { Kleo::CryptoMessageFormat format; const KLazyLocalizedString displayName; const char *configName; } cryptoMessageFormats[] = { // clang-format off {Kleo::InlineOpenPGPFormat, kli18n("Inline OpenPGP (deprecated)"), "inline openpgp"}, {Kleo::OpenPGPMIMEFormat, kli18n("OpenPGP/MIME"), "openpgp/mime" }, {Kleo::SMIMEFormat, kli18n("S/MIME"), "s/mime" }, {Kleo::SMIMEOpaqueFormat, kli18n("S/MIME Opaque"), "s/mime opaque" }, {Kleo::AnySMIME, kli18n("Any S/MIME"), "any s/mime" }, {Kleo::AnyOpenPGP, kli18n("Any OpenPGP"), "any openpgp" }, // clang-format on }; static const unsigned int numCryptoMessageFormats = sizeof cryptoMessageFormats / sizeof *cryptoMessageFormats; const char *Kleo::cryptoMessageFormatToString(Kleo::CryptoMessageFormat f) { if (f == AutoFormat) { return "auto"; } for (unsigned int i = 0; i < numCryptoMessageFormats; ++i) { if (f == cryptoMessageFormats[i].format) { return cryptoMessageFormats[i].configName; } } return nullptr; } QStringList Kleo::cryptoMessageFormatsToStringList(unsigned int f) { QStringList result; for (unsigned int i = 0; i < numCryptoMessageFormats; ++i) { if (f & cryptoMessageFormats[i].format) { result.push_back(QLatin1String(cryptoMessageFormats[i].configName)); } } return result; } QString Kleo::cryptoMessageFormatToLabel(Kleo::CryptoMessageFormat f) { if (f == AutoFormat) { return i18n("Any"); } for (unsigned int i = 0; i < numCryptoMessageFormats; ++i) { if (f == cryptoMessageFormats[i].format) { return KLocalizedString(cryptoMessageFormats[i].displayName).toString(); } } return QString(); } Kleo::CryptoMessageFormat Kleo::stringToCryptoMessageFormat(const QString &s) { const QString t = s.toLower(); for (unsigned int i = 0; i < numCryptoMessageFormats; ++i) { if (t == QLatin1String(cryptoMessageFormats[i].configName)) { return cryptoMessageFormats[i].format; } } return AutoFormat; } unsigned int Kleo::stringListToCryptoMessageFormats(const QStringList &sl) { unsigned int result = 0; for (QStringList::const_iterator it = sl.begin(); it != sl.end(); ++it) { result |= stringToCryptoMessageFormat(*it); } return result; } // For the config values used below, see also kaddressbook/editors/cryptowidget.cpp const char *Kleo::encryptionPreferenceToString(EncryptionPreference pref) { switch (pref) { case UnknownPreference: return nullptr; case NeverEncrypt: return "never"; case AlwaysEncrypt: return "always"; case AlwaysEncryptIfPossible: return "alwaysIfPossible"; case AlwaysAskForEncryption: return "askAlways"; case AskWheneverPossible: return "askWhenPossible"; } return nullptr; // keep the compiler happy } Kleo::EncryptionPreference Kleo::stringToEncryptionPreference(const QString &str) { if (str == QLatin1String("never")) { return NeverEncrypt; } if (str == QLatin1String("always")) { return AlwaysEncrypt; } if (str == QLatin1String("alwaysIfPossible")) { return AlwaysEncryptIfPossible; } if (str == QLatin1String("askAlways")) { return AlwaysAskForEncryption; } if (str == QLatin1String("askWhenPossible")) { return AskWheneverPossible; } return UnknownPreference; } QString Kleo::encryptionPreferenceToLabel(EncryptionPreference pref) { switch (pref) { case NeverEncrypt: return i18n("Never Encrypt"); case AlwaysEncrypt: return i18n("Always Encrypt"); case AlwaysEncryptIfPossible: return i18n("Always Encrypt If Possible"); case AlwaysAskForEncryption: return i18n("Ask"); case AskWheneverPossible: return i18n("Ask Whenever Possible"); default: return xi18nc("no specific preference", "none"); } } const char *Kleo::signingPreferenceToString(SigningPreference pref) { switch (pref) { case UnknownSigningPreference: return nullptr; case NeverSign: return "never"; case AlwaysSign: return "always"; case AlwaysSignIfPossible: return "alwaysIfPossible"; case AlwaysAskForSigning: return "askAlways"; case AskSigningWheneverPossible: return "askWhenPossible"; } return nullptr; // keep the compiler happy } Kleo::SigningPreference Kleo::stringToSigningPreference(const QString &str) { if (str == QLatin1String("never")) { return NeverSign; } if (str == QLatin1String("always")) { return AlwaysSign; } if (str == QLatin1String("alwaysIfPossible")) { return AlwaysSignIfPossible; } if (str == QLatin1String("askAlways")) { return AlwaysAskForSigning; } if (str == QLatin1String("askWhenPossible")) { return AskSigningWheneverPossible; } return UnknownSigningPreference; } QString Kleo::signingPreferenceToLabel(SigningPreference pref) { switch (pref) { case NeverSign: return i18n("Never Sign"); case AlwaysSign: return i18n("Always Sign"); case AlwaysSignIfPossible: return i18n("Always Sign If Possible"); case AlwaysAskForSigning: return i18n("Ask"); case AskSigningWheneverPossible: return i18n("Ask Whenever Possible"); default: return i18nc("no specific preference", ""); } } Kleo::TrustLevel Kleo::trustLevel(const GpgME::Key &key) { TrustLevel maxTl = Level0; for (int i = 0, c = key.numUserIDs(); i < c; ++i) { const auto tl = trustLevel(key.userID(i)); maxTl = qMax(maxTl, tl); if (maxTl == Level4) { break; } } return maxTl; } namespace { bool hasTrustedSignature(const GpgME::UserID &uid) { // lazily initialized cache static std::shared_ptr keyCache; if (!keyCache) { keyCache = Kleo::KeyCache::instance(); } if (!keyCache->initialized()) { QEventLoop el; QObject::connect(keyCache.get(), &Kleo::KeyCache::keyListingDone, &el, &QEventLoop::quit); el.exec(); } const auto signatures = uid.signatures(); std::vector sigKeyIDs; std::transform(signatures.cbegin(), signatures.cend(), std::back_inserter(sigKeyIDs), std::bind(&GpgME::UserID::Signature::signerKeyID, std::placeholders::_1)); const auto keys = keyCache->findByKeyIDOrFingerprint(sigKeyIDs); return std::any_of(keys.cbegin(), keys.cend(), [](const GpgME::Key &key) { return key.ownerTrust() == GpgME::Key::Ultimate; }); } } Kleo::TrustLevel Kleo::trustLevel(const GpgME::UserID &uid) { // Modelled after https://wiki.gnupg.org/EasyGpg2016/AutomatedEncryption, // but modified to cover all cases, unlike the pseudocode in the document. // // TODO: Check whether the key comes from a trusted source (Cert/PKA/DANE/WKD) switch (uid.validity()) { case GpgME::UserID::Unknown: case GpgME::UserID::Undefined: case GpgME::UserID::Never: // Not enough trust -> level 0 return Level0; case GpgME::UserID::Marginal: // Marginal trust without TOFU data means the key is still trusted // through the Web of Trust -> level 2 if (uid.tofuInfo().isNull()) { return Level2; } // Marginal trust with TOFU, level will depend on TOFU history switch (uid.tofuInfo().validity()) { case GpgME::TofuInfo::ValidityUnknown: case GpgME::TofuInfo::Conflict: case GpgME::TofuInfo::NoHistory: // Marginal trust, but not enough history -> level 0 return Level0; case GpgME::TofuInfo::LittleHistory: // Marginal trust, but too little history -> level 1 return Level1; case GpgME::TofuInfo::BasicHistory: case GpgME::TofuInfo::LargeHistory: // Marginal trust and enough history -> level 2 return Level2; } return Level2; // Not reached, but avoids fallthrough warnings case GpgME::UserID::Full: // Full trust, trust level depends whether the UserID is signed with // at least one key with Ultimate ownertrust. return hasTrustedSignature(uid) ? Level4 : Level3; case GpgME::UserID::Ultimate: // Ultimate trust -> leve 4 return Level4; } Q_UNREACHABLE(); } diff --git a/src/kleo/enum.h b/src/kleo/enum.h index 80fa53d4b..425e7ddd4 100644 --- a/src/kleo/enum.h +++ b/src/kleo/enum.h @@ -1,84 +1,85 @@ /* kleo/enum.h 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 */ #pragma once #include "kleo_export.h" -class QString; #include +class QString; + namespace GpgME { class Key; class UserID; } namespace Kleo { enum CryptoMessageFormat { // clang-format off InlineOpenPGPFormat = 1, OpenPGPMIMEFormat = 2, SMIMEFormat = 4, SMIMEOpaqueFormat = 8, AnyOpenPGP = InlineOpenPGPFormat | OpenPGPMIMEFormat, AnySMIME = SMIMEOpaqueFormat | SMIMEFormat, AutoFormat = AnyOpenPGP | AnySMIME // clang-format on }; KLEO_EXPORT QString cryptoMessageFormatToLabel(CryptoMessageFormat f); KLEO_EXPORT const char *cryptoMessageFormatToString(CryptoMessageFormat f); KLEO_EXPORT QStringList cryptoMessageFormatsToStringList(unsigned int f); KLEO_EXPORT CryptoMessageFormat stringToCryptoMessageFormat(const QString &s); KLEO_EXPORT unsigned int stringListToCryptoMessageFormats(const QStringList &sl); enum Action { Conflict, DoIt, DontDoIt, Ask, AskOpportunistic, Impossible }; enum EncryptionPreference { // clang-format off UnknownPreference = 0, NeverEncrypt = 1, AlwaysEncrypt = 2, AlwaysEncryptIfPossible = 3, AlwaysAskForEncryption = 4, AskWheneverPossible = 5, MaxEncryptionPreference = AskWheneverPossible // clang-format on }; KLEO_EXPORT QString encryptionPreferenceToLabel(EncryptionPreference pref); KLEO_EXPORT const char *encryptionPreferenceToString(EncryptionPreference pref); KLEO_EXPORT EncryptionPreference stringToEncryptionPreference(const QString &str); enum SigningPreference { // clang-format off UnknownSigningPreference = 0, NeverSign = 1, AlwaysSign = 2, AlwaysSignIfPossible = 3, AlwaysAskForSigning = 4, AskSigningWheneverPossible = 5, MaxSigningPreference = AskSigningWheneverPossible // clang-format on }; KLEO_EXPORT QString signingPreferenceToLabel(SigningPreference pref); KLEO_EXPORT const char *signingPreferenceToString(SigningPreference pref); KLEO_EXPORT SigningPreference stringToSigningPreference(const QString &str); enum TrustLevel { Level0, Level1, Level2, Level3, Level4 }; KLEO_EXPORT TrustLevel trustLevel(const GpgME::Key &key); KLEO_EXPORT TrustLevel trustLevel(const GpgME::UserID &uid); } diff --git a/src/kleo/kconfigbasedkeyfilter.cpp b/src/kleo/kconfigbasedkeyfilter.cpp index 8d4d4aa98..7b34be69b 100644 --- a/src/kleo/kconfigbasedkeyfilter.cpp +++ b/src/kleo/kconfigbasedkeyfilter.cpp @@ -1,239 +1,240 @@ /* kconfigbasedkeyfilter.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 #include "kconfigbasedkeyfilter.h" #include #include #include #include + #include using namespace Kleo; using namespace GpgME; // // // FontDescription - intuitive font property resolving // (QFont::resolve doesn't work for us) // // struct KeyFilter::FontDescription::Private { bool bold, italic, strikeOut, fullFont; QFont font; }; KeyFilter::FontDescription::FontDescription() : d(new Private) { d->bold = d->italic = d->strikeOut = d->fullFont = false; } KeyFilter::FontDescription::FontDescription(const FontDescription &other) : d(new Private(*other.d)) { } KeyFilter::FontDescription::~FontDescription() = default; KeyFilter::FontDescription KeyFilter::FontDescription::create(bool b, bool i, bool s) { FontDescription fd; fd.d->bold = b; fd.d->italic = i; fd.d->strikeOut = s; return fd; } KeyFilter::FontDescription KeyFilter::FontDescription::create(const QFont &f, bool b, bool i, bool s) { FontDescription fd; fd.d->fullFont = true; fd.d->font = f; fd.d->bold = b; fd.d->italic = i; fd.d->strikeOut = s; return fd; } QFont KeyFilter::FontDescription::font(const QFont &base) const { QFont font; if (d->fullFont) { font = d->font; font.setPointSize(base.pointSize()); } else { font = base; } if (d->bold) { font.setBold(true); } if (d->italic) { font.setItalic(true); } if (d->strikeOut) { font.setStrikeOut(true); } return font; } KeyFilter::FontDescription KeyFilter::FontDescription::resolve(const FontDescription &other) const { FontDescription fd; fd.d->fullFont = this->d->fullFont || other.d->fullFont; if (fd.d->fullFont) { fd.d->font = this->d->fullFont ? this->d->font : other.d->font; } fd.d->bold = this->d->bold || other.d->bold; fd.d->italic = this->d->italic || other.d->italic; fd.d->strikeOut = this->d->strikeOut || other.d->strikeOut; return fd; } static const struct { const char *name; Key::OwnerTrust trust; UserID::Validity validity; } ownerTrustAndValidityMap[] = { // clang-format off {"unknown", Key::Unknown, UserID::Unknown }, {"undefined", Key::Undefined, UserID::Undefined}, {"never", Key::Never, UserID::Never }, {"marginal", Key::Marginal, UserID::Marginal }, {"full", Key::Full, UserID::Full }, {"ultimate", Key::Ultimate, UserID::Ultimate }, // clang-format on }; static Key::OwnerTrust map2OwnerTrust(const QString &s) { for (unsigned int i = 0; i < sizeof ownerTrustAndValidityMap / sizeof *ownerTrustAndValidityMap; ++i) { if (s.toLower() == QLatin1String(ownerTrustAndValidityMap[i].name)) { return ownerTrustAndValidityMap[i].trust; } } return ownerTrustAndValidityMap[0].trust; } static UserID::Validity map2Validity(const QString &s) { for (unsigned int i = 0; i < sizeof ownerTrustAndValidityMap / sizeof *ownerTrustAndValidityMap; ++i) { if (s.toLower() == QLatin1String(ownerTrustAndValidityMap[i].name)) { return ownerTrustAndValidityMap[i].validity; } } return ownerTrustAndValidityMap[0].validity; } KConfigBasedKeyFilter::KConfigBasedKeyFilter(const KConfigGroup &config) : DefaultKeyFilter() { setFgColor(config.readEntry("foreground-color", QColor())); setBgColor(config.readEntry("background-color", QColor())); setName(config.readEntry("Name", config.name())); setIcon(config.readEntry("icon")); setId(config.readEntry("id", config.name())); if (config.hasKey("font")) { setUseFullFont(true); setFont(config.readEntry("font")); } else { setUseFullFont(false); setItalic(config.readEntry("font-italic", false)); setBold(config.readEntry("font-bold", false)); } setStrikeOut(config.readEntry("font-strikeout", false)); #ifdef SET #undef SET #endif #define SET(member, key) \ if (config.hasKey(key)) { \ set##member(config.readEntry(key, false) ? Set : NotSet); \ setSpecificity(specificity() + 1); \ } SET(Revoked, "is-revoked"); SET(Expired, "is-expired"); SET(Disabled, "is-disabled"); SET(Root, "is-root-certificate"); SET(CanEncrypt, "can-encrypt"); SET(CanSign, "can-sign"); SET(CanCertify, "can-certify"); SET(CanAuthenticate, "can-authenticate"); SET(Qualified, "is-qualified"); SET(CardKey, "is-cardkey"); SET(HasSecret, "has-secret-key"); SET(IsOpenPGP, "is-openpgp-key"); SET(WasValidated, "was-validated"); SET(IsDeVs, "is-de-vs"); #undef SET static const struct { const char *prefix; LevelState state; } prefixMap[] = { {"is-", Is}, {"is-not-", IsNot}, {"is-at-least-", IsAtLeast}, {"is-at-most-", IsAtMost}, }; for (unsigned int i = 0; i < sizeof prefixMap / sizeof *prefixMap; ++i) { const QString key = QLatin1String(prefixMap[i].prefix) + QLatin1String("ownertrust"); if (config.hasKey(key)) { setOwnerTrust(prefixMap[i].state); setOwnerTrustReferenceLevel(map2OwnerTrust(config.readEntry(key, QString()))); setSpecificity(specificity() + 1); break; } } for (unsigned int i = 0; i < sizeof prefixMap / sizeof *prefixMap; ++i) { const QString key = QLatin1String(prefixMap[i].prefix) + QLatin1String("validity"); if (config.hasKey(key)) { setValidity(prefixMap[i].state); setValidityReferenceLevel(map2Validity(config.readEntry(key, QString()))); setSpecificity(specificity() + 1); break; } } static const struct { const char *key; MatchContext context; } matchMap[] = { {"any", AnyMatchContext}, {"appearance", Appearance}, {"filtering", Filtering}, }; static const QRegularExpression reg(QRegularExpression(QLatin1String("[^a-zA-Z0-9_-!]+"))); const QStringList contexts = config.readEntry("match-contexts", "any").toLower().split(reg, Qt::SkipEmptyParts); setMatchContexts(NoMatchContext); for (const QString &ctx : contexts) { bool found = false; for (unsigned int i = 0; i < sizeof matchMap / sizeof *matchMap; ++i) { if (ctx == QLatin1String(matchMap[i].key)) { setMatchContexts(availableMatchContexts() |= matchMap[i].context); found = true; break; } else if (ctx.startsWith(QLatin1Char('!')) && ctx.mid(1) == QLatin1String(matchMap[i].key)) { setMatchContexts(availableMatchContexts() &= matchMap[i].context); found = true; break; } } if (!found) { qWarning() << QStringLiteral("KConfigBasedKeyFilter: found unknown match context '%1' in group '%2'").arg(ctx, config.name()); } } if (availableMatchContexts() == NoMatchContext) { qWarning() << QStringLiteral( "KConfigBasedKeyFilter: match context in group '%1' evaluates to NoMatchContext, " "replaced by AnyMatchContext") .arg(config.name()); setMatchContexts(AnyMatchContext); } } diff --git a/src/kleo/kconfigbasedkeyfilter.h b/src/kleo/kconfigbasedkeyfilter.h index fe42d78d5..eb0b4cbc8 100644 --- a/src/kleo/kconfigbasedkeyfilter.h +++ b/src/kleo/kconfigbasedkeyfilter.h @@ -1,25 +1,26 @@ /* kconfigbasedkeyfilter.h 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 */ #pragma once #include "defaultkeyfilter.h" +#include "kleo_export.h" class KConfigGroup; namespace Kleo { class KLEO_EXPORT KConfigBasedKeyFilter : public DefaultKeyFilter { public: explicit KConfigBasedKeyFilter(const KConfigGroup &group); }; } diff --git a/src/kleo/keyfilter.h b/src/kleo/keyfilter.h index a5b72eca6..6c5be6454 100644 --- a/src/kleo/keyfilter.h +++ b/src/kleo/keyfilter.h @@ -1,104 +1,105 @@ /* keyfilter.h 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 */ #pragma once +#include "kleo_export.h" + #include #include - -#include "kleo_export.h" #include + namespace GpgME { class Key; } class QFont; class QColor; class QString; namespace Kleo { /** @short An abstract base class key filters */ class KLEO_EXPORT KeyFilter { public: virtual ~KeyFilter() { } enum MatchContext { // clang-format off NoMatchContext = 0x0, Appearance = 0x1, Filtering = 0x2, AnyMatchContext = Appearance | Filtering // clang-format on }; Q_DECLARE_FLAGS(MatchContexts, MatchContext) virtual bool matches(const GpgME::Key &key, MatchContexts ctx) const = 0; virtual unsigned int specificity() const = 0; virtual QString id() const = 0; virtual MatchContexts availableMatchContexts() const = 0; // not sure if we want these here, but for the time being, it's // the easiest way: virtual QColor fgColor() const = 0; virtual QColor bgColor() const = 0; virtual QString name() const = 0; virtual QString icon() const = 0; class FontDescription { public: FontDescription(); FontDescription(const FontDescription &other); FontDescription &operator=(const FontDescription &other) { FontDescription copy(other); swap(copy); return *this; } ~FontDescription(); static FontDescription create(bool bold, bool italic, bool strikeOut); static FontDescription create(const QFont &font, bool bold, bool italic, bool strikeOut); QFont font(const QFont &base) const; FontDescription resolve(const FontDescription &other) const; void swap(FontDescription &other) { std::swap(this->d, other.d); } struct Private; private: std::unique_ptr d; }; virtual FontDescription fontDescription() const = 0; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KeyFilter::MatchContexts) } #include Q_DECLARE_METATYPE(Kleo::KeyFilter::MatchContexts) diff --git a/src/kleo/keyfiltermanager.cpp b/src/kleo/keyfiltermanager.cpp index fe285201b..e9a455367 100644 --- a/src/kleo/keyfiltermanager.cpp +++ b/src/kleo/keyfiltermanager.cpp @@ -1,458 +1,458 @@ /* 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 #include "keyfiltermanager.h" #include "defaultkeyfilter.h" #include "kconfigbasedkeyfilter.h" #include "stl_util.h" -#include -#include -#include +#include +#include +#include #include #include #include #include #include -#include #include #include +#include #include #include #include #include #include #include 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) && !Formatting::uidsHaveFullValidity(key); } }; /* 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) && !Formatting::uidsHaveFullValidity(key); } }; } static std::vector> defaultFilters() { std::vector> result; result.reserve(6); result.push_back(std::shared_ptr(new MyCertificatesKeyFilter)); result.push_back(std::shared_ptr(new TrustedCertificatesKeyFilter)); result.push_back(std::shared_ptr(new FullCertificatesKeyFilter)); result.push_back(std::shared_ptr(new OtherCertificatesKeyFilter)); result.push_back(std::shared_ptr(new AllCertificatesKeyFilter)); result.push_back(std::shared_ptr(new UncertifiedOpenPGPKeysFilter)); result.push_back(std::shared_ptr(new KeyNotValidFilter)); return result; } class KeyFilterManager::Private { public: Private() : filters() , model(this) { } void clear() { model.beginResetModel(); filters.clear(); model.endResetModel(); } std::vector> 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 &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 &filter) { return filter->matches(key, contexts); }); if (it != d->filters.cend()) { return *it; } static const std::shared_ptr null; return null; } std::vector> KeyFilterManager::filtersMatching(const Key &key, KeyFilter::MatchContexts contexts) const { std::vector> 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 &filter) { return !filter->matches(key, contexts); }); return result; } namespace { static const auto byDecreasingSpecificity = [](const std::shared_ptr &lhs, const std::shared_ptr &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 = !Kleo::gnupgIsDeVsCompliant(); 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(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(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(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 &KeyFilterManager::keyFilterByID(const QString &id) const { const auto it = std::find_if(d->filters.begin(), d->filters.end(), [id](const std::shared_ptr &filter) { return filter->id() == id; }); if (it != d->filters.end()) { return *it; } static const std::shared_ptr null; return null; } const std::shared_ptr &KeyFilterManager::fromModelIndex(const QModelIndex &idx) const { if (!idx.isValid() || idx.model() != &d->model || idx.row() < 0 || static_cast(idx.row()) >= d->filters.size()) { static const std::shared_ptr null; return null; } return d->filters[idx.row()]; } QModelIndex KeyFilterManager::toModelIndex(const std::shared_ptr &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(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> &filters, const Key &key, const KeyFilter::FontDescription &initial) { return kdtools::accumulate_if( filters.begin(), filters.end(), [&key](const std::shared_ptr &filter) { return filter->matches(key, KeyFilter::Appearance); }, initial, [](const KeyFilter::FontDescription &lhs, const std::shared_ptr &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> &filters, const Key &key, QColor (KeyFilter::*fun)() const) { const auto it = std::find_if(filters.cbegin(), filters.cend(), [&fun, &key](const std::shared_ptr &filter) { return filter->matches(key, KeyFilter::Appearance) && (filter.get()->*fun)().isValid(); }); if (it == filters.cend()) { return {}; } else { return (it->get()->*fun)(); } } static QString get_string(const std::vector> &filters, const Key &key, QString (KeyFilter::*fun)() const) { const auto it = std::find_if(filters.cbegin(), filters.cend(), [&fun, &key](const std::shared_ptr &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); } 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/kleo/keyfiltermanager.h b/src/kleo/keyfiltermanager.h index f1f360978..533637c8b 100644 --- a/src/kleo/keyfiltermanager.h +++ b/src/kleo/keyfiltermanager.h @@ -1,80 +1,81 @@ /* keyfiltermanager.h 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 */ #pragma once #include "kleo_export.h" -#include #include +#include + #include #include #include namespace GpgME { class Key; } class QAbstractItemModel; class QModelIndex; class QFont; class QColor; class QIcon; namespace Kleo { class KLEO_EXPORT KeyFilterManager : public QObject { Q_OBJECT public: enum ModelRoles { FilterIdRole = Qt::UserRole, FilterMatchContextsRole, }; protected: explicit KeyFilterManager(QObject *parent = nullptr); ~KeyFilterManager() override; public: static KeyFilterManager *instance(); /** * Adds the rule that keys must match @p protocol to all filters. */ void alwaysFilterByProtocol(GpgME::Protocol protocol); const std::shared_ptr &filterMatching(const GpgME::Key &key, KeyFilter::MatchContexts contexts) const; std::vector> filtersMatching(const GpgME::Key &key, KeyFilter::MatchContexts contexts) const; QAbstractItemModel *model() const; const std::shared_ptr &keyFilterByID(const QString &id) const; const std::shared_ptr &fromModelIndex(const QModelIndex &mi) const; QModelIndex toModelIndex(const std::shared_ptr &kf) const; void reload(); QFont font(const GpgME::Key &key, const QFont &baseFont) const; QColor bgColor(const GpgME::Key &key) const; QColor fgColor(const GpgME::Key &key) const; QIcon icon(const GpgME::Key &key) const; class Private; private: std::unique_ptr d; static KeyFilterManager *mSelf; }; } diff --git a/src/kleo/keygroupconfig.cpp b/src/kleo/keygroupconfig.cpp index 16fe278b2..158fa8dfe 100644 --- a/src/kleo/keygroupconfig.cpp +++ b/src/kleo/keygroupconfig.cpp @@ -1,177 +1,177 @@ /* kleo/keygroupconfig.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keygroupconfig.h" #include "debug.h" #include "keygroup.h" -#include -#include -#include +#include +#include +#include #include #include #include #include #include using namespace Kleo; using namespace GpgME; static const QString groupNamePrefix = QStringLiteral("Group-"); class KeyGroupConfig::Private { public: explicit Private(const QString &filename); std::vector readGroups() const; KeyGroup writeGroup(const KeyGroup &group); bool removeGroup(const KeyGroup &group); private: KeyGroup readGroup(const KSharedConfigPtr &groupsConfig, const QString &groupId) const; private: QString filename; }; KeyGroupConfig::Private::Private(const QString &filename) : filename{filename} { if (filename.isEmpty()) { qCWarning(LIBKLEO_LOG) << __func__ << "Warning: name of configuration file is empty"; } } KeyGroup KeyGroupConfig::Private::readGroup(const KSharedConfigPtr &groupsConfig, const QString &groupId) const { const KConfigGroup configGroup = groupsConfig->group(groupNamePrefix + groupId); const QString groupName = configGroup.readEntry("Name", QString()); const auto fingerprints = toStdStrings(configGroup.readEntry("Keys", QStringList())); const std::vector groupKeys = KeyCache::instance()->findByFingerprint(fingerprints); // treat group as immutable if any of its entries is immutable const QStringList entries = configGroup.keyList(); const bool isImmutable = (configGroup.isImmutable() // || std::any_of(entries.begin(), entries.end(), [configGroup](const QString &entry) { return configGroup.isEntryImmutable(entry); })); KeyGroup g(groupId, groupName, groupKeys, KeyGroup::ApplicationConfig); g.setIsImmutable(isImmutable); qCDebug(LIBKLEO_LOG) << "Read group" << g; return g; } std::vector KeyGroupConfig::Private::readGroups() const { std::vector groups; if (filename.isEmpty()) { return groups; } const KSharedConfigPtr groupsConfig = KSharedConfig::openConfig(filename); const QStringList configGroups = groupsConfig->groupList(); for (const QString &configGroupName : configGroups) { qCDebug(LIBKLEO_LOG) << "Reading config group" << configGroupName; if (configGroupName.startsWith(groupNamePrefix)) { const QString keyGroupId = configGroupName.mid(groupNamePrefix.size()); if (keyGroupId.isEmpty()) { qCWarning(LIBKLEO_LOG) << "Config group" << configGroupName << "has empty group id"; continue; } KeyGroup group = readGroup(groupsConfig, keyGroupId); groups.push_back(group); } } return groups; } KeyGroup KeyGroupConfig::Private::writeGroup(const KeyGroup &group) { if (filename.isEmpty()) { return {}; } if (group.isNull()) { qCDebug(LIBKLEO_LOG) << __func__ << "Error: group is null"; return group; } KSharedConfigPtr groupsConfig = KSharedConfig::openConfig(filename); KConfigGroup configGroup = groupsConfig->group(groupNamePrefix + group.id()); qCDebug(LIBKLEO_LOG) << __func__ << "Writing config group" << configGroup.name(); configGroup.writeEntry("Name", group.name()); configGroup.writeEntry("Keys", Kleo::getFingerprints(group.keys())); // reread group to ensure that it reflects the saved group in case of immutable entries return readGroup(groupsConfig, group.id()); } bool KeyGroupConfig::Private::removeGroup(const KeyGroup &group) { if (filename.isEmpty()) { return false; } if (group.isNull()) { qCDebug(LIBKLEO_LOG) << __func__ << "Error: group is null"; return false; } KSharedConfigPtr groupsConfig = KSharedConfig::openConfig(filename); KConfigGroup configGroup = groupsConfig->group(groupNamePrefix + group.id()); qCDebug(LIBKLEO_LOG) << __func__ << "Removing config group" << configGroup.name(); configGroup.deleteGroup(); return true; } KeyGroupConfig::KeyGroupConfig(const QString &filename) : d{std::make_unique(filename)} { } KeyGroupConfig::~KeyGroupConfig() = default; std::vector KeyGroupConfig::readGroups() const { return d->readGroups(); } KeyGroup KeyGroupConfig::writeGroup(const KeyGroup &group) { return d->writeGroup(group); } void KeyGroupConfig::writeGroups(const std::vector &groups) { std::for_each(std::begin(groups), std::end(groups), [this](const auto &group) { d->writeGroup(group); }); } bool KeyGroupConfig::removeGroup(const KeyGroup &group) { return d->removeGroup(group); } diff --git a/src/kleo/keygroupimportexport.cpp b/src/kleo/keygroupimportexport.cpp index c2bf46d89..ececa017c 100644 --- a/src/kleo/keygroupimportexport.cpp +++ b/src/kleo/keygroupimportexport.cpp @@ -1,149 +1,149 @@ /* kleo/keygroupimportexport.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keygroupimportexport.h" #include "debug.h" #include "keygroup.h" -#include -#include -#include +#include +#include +#include #include #include #include #include using namespace Kleo; using namespace GpgME; // use a different, less generic prefix for the config group names than in // KeyGroupConfig to avoid problems with "Group-*" config groups created by // other applications; this means that the key groups stored in the normal group // configuration file cannot be read with the below functions, but that's a good // thing because the ini files created by KConfig are incompatible with QSettings static const QString keyGroupNamePrefix = QStringLiteral("KeyGroup-"); namespace { QString readString(const QSettings &settings, const QString &key) { return settings.value(key, QString{}).toString(); } QStringList readStringList(const QSettings &settings, const QString &key) { auto variant = settings.value(key); if (!variant.isValid()) { return {}; } if ((variant.userType() == QMetaType::QString) && variant.toString().isEmpty()) { // interpret empty string value as empty list instead of as list with an empty string return {}; } // opportunistically, interpret the value as string list return variant.toStringList(); } void writeString(QSettings &settings, const QString &key, const QString &string) { settings.setValue(key, string); } void writeStringList(QSettings &settings, const QString &key, const QStringList &list) { // write empty list as empty string to avoid Qt's "@Invalid()" if (list.empty()) { writeString(settings, key, {}); } else { settings.setValue(key, list); } } KeyGroup readGroup(const QSettings &groupsConfig, const QString &groupId) { const auto configGroupPath = keyGroupNamePrefix + groupId + QLatin1Char{'/'}; const auto groupName = readString(groupsConfig, configGroupPath + QLatin1String{"Name"}); const auto fingerprints = readStringList(groupsConfig, configGroupPath + QLatin1String{"Keys"}); const std::vector groupKeys = KeyCache::instance()->findByFingerprint(toStdStrings(fingerprints)); KeyGroup g(groupId, groupName, groupKeys, KeyGroup::ApplicationConfig); qCDebug(LIBKLEO_LOG) << __func__ << "Read group" << g; return g; } void writeGroup(QSettings &groupsConfig, const KeyGroup &group) { if (group.isNull()) { qCDebug(LIBKLEO_LOG) << __func__ << "Error: group is null"; return; } const auto configGroupName = keyGroupNamePrefix + group.id(); qCDebug(LIBKLEO_LOG) << __func__ << "Writing config group" << configGroupName; const auto configGroupPath = configGroupName + QLatin1Char{'/'}; writeString(groupsConfig, configGroupPath + QLatin1String{"Name"}, group.name()); writeStringList(groupsConfig, configGroupPath + QLatin1String{"Keys"}, Kleo::getFingerprints(group.keys())); } } // namespace std::vector Kleo::readKeyGroups(const QString &filename) { std::vector groups; if (filename.isEmpty()) { return groups; } if (!QFile::exists(filename)) { qCWarning(LIBKLEO_LOG) << __func__ << "File" << filename << "does not exist"; return groups; } const QSettings groupsConfig{filename, QSettings::IniFormat}; const QStringList configGroups = groupsConfig.childGroups(); for (const QString &configGroupName : configGroups) { if (configGroupName.startsWith(keyGroupNamePrefix)) { qCDebug(LIBKLEO_LOG) << __func__ << "Reading config group" << configGroupName; const QString keyGroupId = configGroupName.mid(keyGroupNamePrefix.size()); if (keyGroupId.isEmpty()) { qCWarning(LIBKLEO_LOG) << __func__ << "Config group" << configGroupName << "has empty group id"; continue; } groups.push_back(readGroup(groupsConfig, keyGroupId)); } } return groups; } Kleo::WriteKeyGroups Kleo::writeKeyGroups(const QString &filename, const std::vector &groups) { if (filename.isEmpty()) { return WriteKeyGroups::InvalidFilename; } QSettings groupsConfig{filename, QSettings::IniFormat}; for (const auto &group : groups) { writeGroup(groupsConfig, group); } // ensure that the data is written to disk before calling status() groupsConfig.sync(); qCDebug(LIBKLEO_LOG) << __func__ << "groupsConfig.status():" << groupsConfig.status(); return groupsConfig.status() == QSettings::NoError ? WriteKeyGroups::Success : WriteKeyGroups::Error; } diff --git a/src/kleo/keyresolver.cpp b/src/kleo/keyresolver.cpp index dfe0c3d35..30c534c3e 100644 --- a/src/kleo/keyresolver.cpp +++ b/src/kleo/keyresolver.cpp @@ -1,164 +1,164 @@ /* -*- c++ -*- keyresolver.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker Based on kpgp.cpp SPDX-FileCopyrightText: 2001, 2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keyresolver.h" #include "keyresolvercore.h" -#include -#include -#include +#include +#include +#include #include #include using namespace Kleo; using namespace GpgME; class KeyResolver::Private { public: Private(KeyResolver *qq, bool enc, bool sig, Protocol fmt, bool allowMixed) : q(qq) , mCore(enc, sig, fmt) , mFormat(fmt) , mEncrypt(enc) , mSign(sig) , mAllowMixed(allowMixed) , mCache(KeyCache::instance()) , mDialogWindowFlags(Qt::WindowFlags()) , mPreferredProtocol(UnknownProtocol) { mCore.setAllowMixedProtocols(allowMixed); } ~Private() = default; void showApprovalDialog(KeyResolverCore::Result result, QWidget *parent); void dialogAccepted(); KeyResolver *const q; KeyResolverCore mCore; Solution mResult; Protocol mFormat; bool mEncrypt; bool mSign; bool mAllowMixed; // The cache is needed as a member variable to avoid rebuilding // it between calls if we are the only user. std::shared_ptr mCache; std::unique_ptr mDialog; Qt::WindowFlags mDialogWindowFlags; Protocol mPreferredProtocol; }; void KeyResolver::Private::showApprovalDialog(KeyResolverCore::Result result, QWidget *parent) { const QString sender = mCore.normalizedSender(); mDialog = std::make_unique(mEncrypt, mSign, sender, std::move(result.solution), std::move(result.alternative), mAllowMixed, mFormat, parent, mDialogWindowFlags); connect(mDialog.get(), &QDialog::accepted, q, [this]() { dialogAccepted(); }); connect(mDialog.get(), &QDialog::rejected, q, [this]() { Q_EMIT q->keysResolved(false, false); }); mDialog->open(); } void KeyResolver::Private::dialogAccepted() { mResult = mDialog->result(); Q_EMIT q->keysResolved(true, false); } void KeyResolver::start(bool showApproval, QWidget *parentWidget) { qCDebug(LIBKLEO_LOG) << "Starting "; if (!d->mSign && !d->mEncrypt) { // nothing to do return Q_EMIT keysResolved(true, true); } const auto result = d->mCore.resolve(); const bool success = (result.flags & KeyResolverCore::AllResolved); if (success && !showApproval) { d->mResult = std::move(result.solution); Q_EMIT keysResolved(true, false); return; } else if (success) { qCDebug(LIBKLEO_LOG) << "No need for the user showing approval anyway."; } d->showApprovalDialog(std::move(result), parentWidget); } KeyResolver::KeyResolver(bool encrypt, bool sign, Protocol fmt, bool allowMixed) : d(new Private(this, encrypt, sign, fmt, allowMixed)) { } Kleo::KeyResolver::~KeyResolver() = default; void KeyResolver::setRecipients(const QStringList &addresses) { d->mCore.setRecipients(addresses); } void KeyResolver::setSender(const QString &address) { d->mCore.setSender(address); } void KeyResolver::setOverrideKeys(const QMap> &overrides) { d->mCore.setOverrideKeys(overrides); } void KeyResolver::setSigningKeys(const QStringList &fingerprints) { d->mCore.setSigningKeys(fingerprints); } KeyResolver::Solution KeyResolver::result() const { return d->mResult; } void KeyResolver::setDialogWindowFlags(Qt::WindowFlags flags) { d->mDialogWindowFlags = flags; } void KeyResolver::setPreferredProtocol(Protocol proto) { d->mCore.setPreferredProtocol(proto); } void KeyResolver::setMinimumValidity(int validity) { d->mCore.setMinimumValidity(validity); } diff --git a/src/kleo/keyresolver.h b/src/kleo/keyresolver.h index 2821fefde..a64682a52 100644 --- a/src/kleo/keyresolver.h +++ b/src/kleo/keyresolver.h @@ -1,183 +1,183 @@ /* -*- c++ -*- keyresolver.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include "kleo_export.h" + #include #include #include #include #include #include #include -#include "kleo_export.h" - namespace GpgME { class Key; } namespace Kleo { /** * Class to find Keys for E-Mail signing and encryption. * * The KeyResolver uses the Keycache to find keys for signing * or encryption. * * Overrides can be provided for address book integration. * * If no override key(s) are provided for an address and no * KeyGroup for this address is found, then the key * with a uid that matches the address and has the highest * validity is used. If both keys have the same validity, * then the key with the newest subkey is used. * * The KeyResolver also supports groups so the number of * encryption keys does not necessarily * need to match the amount of sender addresses. For this reason * maps are used to map addresses to lists of keys. * * The keys can be OpenPGP keys and S/MIME (CMS) keys. * As a caller you need to partition the keys by their protocol and * send one message for each protocol for the recipients and signed * by the signing keys. */ class KLEO_EXPORT KeyResolver : public QObject { Q_OBJECT public: /** * Solution represents the solution found by the KeyResolver. * @a protocol hints at the protocol of the signing and encryption keys, * i.e. if @a protocol is either @c GpgME::OpenPGP or @c GpgME::CMS, then * all keys have the corresponding protocol. Otherwise, the keys have * mixed protocols. * @a signingKeys contains the signing keys to use. It contains * zero or one OpenPGP key and zero or one S/MIME key. * @a encryptionKeys contains the encryption keys to use for the * different recipients. The keys of the map represent the normalized * email addresses of the recipients. */ struct Solution { GpgME::Protocol protocol = GpgME::UnknownProtocol; std::vector signingKeys; QMap> encryptionKeys; }; /** Creates a new key resolver object. * * @param encrypt: Should encryption keys be selected. * @param sign: Should signing keys be selected. * @param protocol: A specific key protocol (OpenPGP, S/MIME) for selection. Default: Both protocols. * @param allowMixed: Specify if multiple message formats may be resolved. **/ explicit KeyResolver(bool encrypt, bool sign, GpgME::Protocol protocol = GpgME::UnknownProtocol, bool allowMixed = true); ~KeyResolver() override; /** * Set the list of recipient addresses. * * @param addresses: A list of (not necessarily normalized) email addresses */ void setRecipients(const QStringList &addresses); /** * Set the sender's address. * * This address is added to the list of recipients (for encryption to self) * and it is used for signing key resolution, if the signing keys are not * explicitly set through setSigningKeys. * * @param sender: The sender of this message. */ void setSender(const QString &sender); /** * Set up possible override keys for recipients addresses. * The keys for the fingerprints are looked * up and used when found. * * Overrides for @c GpgME::UnknownProtocol are used regardless of the * protocol. Overrides for a specific protocol are only used for this * protocol. Overrides for @c GpgME::UnknownProtocol takes precedence over * overrides for a specific protocol. * * @param overrides: A map of \ -> (\ \) */ void setOverrideKeys(const QMap> &overrides); /** * Set explicit signing keys to use. */ void setSigningKeys(const QStringList &fingerprints); /** * Set the minimum user id validity for autoresolution. * * The default value is marginal * * @param validity int representation of a GpgME::UserID::Validity. */ void setMinimumValidity(int validity); /** * Get the result of the resolution. * * @return the resolved keys for signing and encryption. */ Solution result() const; /** * Starts the key resolving procedure. Emits keysResolved on success or * error. * * @param showApproval: If set to true a dialog listing the keys * will always be shown. * @param parentWidget: Optional, a Widget to use as parent for dialogs. */ void start(bool showApproval, QWidget *parentWidget = nullptr); /** * Set window flags for a possible dialog. */ void setDialogWindowFlags(Qt::WindowFlags flags); /** * Set the protocol that is preferred to be displayed first when * it is not clear from the keys. E.g. if both OpenPGP and S/MIME * can be resolved. */ void setPreferredProtocol(GpgME::Protocol proto); Q_SIGNALS: /** * Emitted when key resolution finished. * * @param success: The general result. If true continue sending, * if false abort. * @param sendUnencrypted: If there could be no key found for one of * the recipients the user was queried if the * mail should be sent out unencrypted. * sendUnencrypted is true if the user agreed * to this.*/ void keysResolved(bool success, bool sendUnencrypted); private: class Private; std::unique_ptr d; }; } // namespace Kleo diff --git a/src/kleo/keyresolvercore.cpp b/src/kleo/keyresolvercore.cpp index 1d081c4da..6396c4942 100644 --- a/src/kleo/keyresolvercore.cpp +++ b/src/kleo/keyresolvercore.cpp @@ -1,788 +1,788 @@ /* -*- c++ -*- kleo/keyresolvercore.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker Based on kpgp.cpp SPDX-FileCopyrightText: 2001, 2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keyresolvercore.h" #include "enum.h" #include "keygroup.h" -#include -#include -#include +#include +#include +#include #include #include using namespace Kleo; using namespace GpgME; namespace { QDebug operator<<(QDebug debug, const GpgME::Key &key) { if (key.isNull()) { debug << "Null"; } else { debug << Formatting::summaryLine(key); } return debug.maybeSpace(); } static inline bool ValidEncryptionKey(const Key &key) { if (key.isNull() || key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt()) { return false; } return true; } static inline bool ValidSigningKey(const Key &key) { if (key.isNull() || key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canSign() || !key.hasSecret()) { return false; } return true; } static int keyValidity(const Key &key, const QString &address) { // returns the validity of the UID matching the address or, if no UID matches, the maximal validity of all UIDs int overallValidity = UserID::Validity::Unknown; for (const auto &uid : key.userIDs()) { if (QString::fromStdString(uid.addrSpec()).toLower() == address.toLower()) { return uid.validity(); } overallValidity = std::max(overallValidity, static_cast(uid.validity())); } return overallValidity; } static int minimumValidity(const std::vector &keys, const QString &address) { const int minValidity = std::accumulate(keys.cbegin(), // keys.cend(), UserID::Ultimate + 1, [address](int validity, const Key &key) { return std::min(validity, keyValidity(key, address)); }); return minValidity <= UserID::Ultimate ? static_cast(minValidity) : UserID::Unknown; } bool allKeysHaveProtocol(const std::vector &keys, Protocol protocol) { return std::all_of(keys.cbegin(), keys.cend(), [protocol](const Key &key) { return key.protocol() == protocol; }); } bool anyKeyHasProtocol(const std::vector &keys, Protocol protocol) { return std::any_of(std::begin(keys), std::end(keys), [protocol](const Key &key) { return key.protocol() == protocol; }); } } // namespace class KeyResolverCore::Private { public: Private(KeyResolverCore *qq, bool enc, bool sig, Protocol fmt) : q(qq) , mFormat(fmt) , mEncrypt(enc) , mSign(sig) , mCache(KeyCache::instance()) , mPreferredProtocol(UnknownProtocol) , mMinimumValidity(UserID::Marginal) { } ~Private() = default; bool isAcceptableSigningKey(const Key &key); bool isAcceptableEncryptionKey(const Key &key, const QString &address = QString()); void setSender(const QString &address); void addRecipients(const QStringList &addresses); void setOverrideKeys(const QMap> &overrides); void resolveOverrides(); std::vector resolveRecipientWithGroup(const QString &address, Protocol protocol); void resolveEncryptionGroups(); std::vector resolveSenderWithGroup(const QString &address, Protocol protocol); void resolveSigningGroups(); void resolveSign(Protocol proto); void setSigningKeys(const QStringList &fingerprints); std::vector resolveRecipient(const QString &address, Protocol protocol); void resolveEnc(Protocol proto); void mergeEncryptionKeys(); Result resolve(); KeyResolverCore *const q; QString mSender; QStringList mRecipients; QMap> mSigKeys; QMap>> mEncKeys; QMap> mOverrides; Protocol mFormat; QStringList mFatalErrors; bool mEncrypt; bool mSign; // The cache is needed as a member variable to avoid rebuilding // it between calls if we are the only user. std::shared_ptr mCache; bool mAllowMixed = true; Protocol mPreferredProtocol; int mMinimumValidity; }; bool KeyResolverCore::Private::isAcceptableSigningKey(const Key &key) { if (!ValidSigningKey(key)) { return false; } if (Kleo::gnupgIsDeVsCompliant()) { if (!Formatting::isKeyDeVs(key)) { qCDebug(LIBKLEO_LOG) << "Rejected sig key" << key.primaryFingerprint() << "because it is not de-vs compliant."; return false; } } return true; } bool KeyResolverCore::Private::isAcceptableEncryptionKey(const Key &key, const QString &address) { if (!ValidEncryptionKey(key)) { return false; } if (Kleo::gnupgIsDeVsCompliant()) { if (!Formatting::isKeyDeVs(key)) { qCDebug(LIBKLEO_LOG) << "Rejected enc key" << key.primaryFingerprint() << "because it is not de-vs compliant."; return false; } } if (address.isEmpty()) { return true; } for (const auto &uid : key.userIDs()) { if (uid.addrSpec() == address.toStdString()) { if (uid.validity() >= mMinimumValidity) { return true; } } } return false; } void KeyResolverCore::Private::setSender(const QString &address) { const auto normalized = UserID::addrSpecFromString(address.toUtf8().constData()); if (normalized.empty()) { // should not happen bug in the caller, non localized // error for bug reporting. mFatalErrors << QStringLiteral("The sender address '%1' could not be extracted").arg(address); return; } const auto normStr = QString::fromUtf8(normalized.c_str()); mSender = normStr; addRecipients({address}); } void KeyResolverCore::Private::addRecipients(const QStringList &addresses) { if (!mEncrypt) { return; } // Internally we work with normalized addresses. Normalization // matches the gnupg one. for (const auto &addr : addresses) { // PGP Uids are defined to be UTF-8 (RFC 4880 §5.11) const auto normalized = UserID::addrSpecFromString(addr.toUtf8().constData()); if (normalized.empty()) { // should not happen bug in the caller, non localized // error for bug reporting. mFatalErrors << QStringLiteral("The mail address for '%1' could not be extracted").arg(addr); continue; } const QString normStr = QString::fromUtf8(normalized.c_str()); mRecipients << normStr; // Initially add empty lists of keys for both protocols mEncKeys[normStr] = {{CMS, {}}, {OpenPGP, {}}}; } } void KeyResolverCore::Private::setOverrideKeys(const QMap> &overrides) { for (auto protocolIt = overrides.cbegin(); protocolIt != overrides.cend(); ++protocolIt) { const Protocol &protocol = protocolIt.key(); const auto &addressFingerprintMap = protocolIt.value(); for (auto addressIt = addressFingerprintMap.cbegin(); addressIt != addressFingerprintMap.cend(); ++addressIt) { const QString &address = addressIt.key(); const QStringList &fingerprints = addressIt.value(); const QString normalizedAddress = QString::fromUtf8(UserID::addrSpecFromString(address.toUtf8().constData()).c_str()); mOverrides[normalizedAddress][protocol] = fingerprints; } } } namespace { std::vector resolveOverride(const QString &address, Protocol protocol, const QStringList &fingerprints) { std::vector keys; for (const auto &fprOrId : fingerprints) { const Key key = KeyCache::instance()->findByKeyIDOrFingerprint(fprOrId.toUtf8().constData()); if (key.isNull()) { // FIXME: Report to caller qCDebug(LIBKLEO_LOG) << "Failed to find override key for:" << address << "fpr:" << fprOrId; continue; } if (protocol != UnknownProtocol && key.protocol() != protocol) { qCDebug(LIBKLEO_LOG) << "Ignoring key" << Formatting::summaryLine(key) << "given as" << Formatting::displayName(protocol) << "override for" << address; continue; } qCDebug(LIBKLEO_LOG) << "Using key" << Formatting::summaryLine(key) << "as" << Formatting::displayName(protocol) << "override for" << address; keys.push_back(key); } return keys; } } void KeyResolverCore::Private::resolveOverrides() { if (!mEncrypt) { // No encryption we are done. return; } for (auto addressIt = mOverrides.cbegin(); addressIt != mOverrides.cend(); ++addressIt) { const QString &address = addressIt.key(); const auto &protocolFingerprintsMap = addressIt.value(); if (!mRecipients.contains(address)) { qCDebug(LIBKLEO_LOG) << "Overrides provided for an address that is " "neither sender nor recipient. Address:" << address; continue; } const QStringList commonOverride = protocolFingerprintsMap.value(UnknownProtocol); if (!commonOverride.empty()) { mEncKeys[address][UnknownProtocol] = resolveOverride(address, UnknownProtocol, commonOverride); if (protocolFingerprintsMap.contains(OpenPGP)) { qCDebug(LIBKLEO_LOG) << "Ignoring OpenPGP-specific override for" << address << "in favor of common override"; } if (protocolFingerprintsMap.contains(CMS)) { qCDebug(LIBKLEO_LOG) << "Ignoring S/MIME-specific override for" << address << "in favor of common override"; } } else { if (mFormat != CMS) { mEncKeys[address][OpenPGP] = resolveOverride(address, OpenPGP, protocolFingerprintsMap.value(OpenPGP)); } if (mFormat != OpenPGP) { mEncKeys[address][CMS] = resolveOverride(address, CMS, protocolFingerprintsMap.value(CMS)); } } } } std::vector KeyResolverCore::Private::resolveSenderWithGroup(const QString &address, Protocol protocol) { // prefer single-protocol groups over mixed-protocol groups auto group = mCache->findGroup(address, protocol, KeyCache::KeyUsage::Sign); if (group.isNull()) { group = mCache->findGroup(address, UnknownProtocol, KeyCache::KeyUsage::Sign); } if (group.isNull()) { return {}; } // take the first key matching the protocol const auto &keys = group.keys(); const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol](const auto &key) { return key.protocol() == protocol; }); if (it == std::end(keys)) { qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has no" << Formatting::displayName(protocol) << "signing key"; return {}; } const auto key = *it; if (!isAcceptableSigningKey(key)) { qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has unacceptable signing key" << key; return {}; } return {key}; } void KeyResolverCore::Private::resolveSigningGroups() { auto &protocolKeysMap = mSigKeys; if (!protocolKeysMap[UnknownProtocol].empty()) { // already resolved by common override return; } if (mFormat == OpenPGP) { if (!protocolKeysMap[OpenPGP].empty()) { // already resolved by override return; } protocolKeysMap[OpenPGP] = resolveSenderWithGroup(mSender, OpenPGP); } else if (mFormat == CMS) { if (!protocolKeysMap[CMS].empty()) { // already resolved by override return; } protocolKeysMap[CMS] = resolveSenderWithGroup(mSender, CMS); } else { protocolKeysMap[OpenPGP] = resolveSenderWithGroup(mSender, OpenPGP); protocolKeysMap[CMS] = resolveSenderWithGroup(mSender, CMS); } } void KeyResolverCore::Private::resolveSign(Protocol proto) { if (!mSigKeys[proto].empty()) { // Explicitly set return; } const auto key = mCache->findBestByMailBox(mSender.toUtf8().constData(), proto, KeyCache::KeyUsage::Sign); if (key.isNull()) { qCDebug(LIBKLEO_LOG) << "Failed to find" << Formatting::displayName(proto) << "signing key for" << mSender; return; } if (!isAcceptableSigningKey(key)) { qCDebug(LIBKLEO_LOG) << "Unacceptable signing key" << key.primaryFingerprint() << "for" << mSender; return; } mSigKeys.insert(proto, {key}); } void KeyResolverCore::Private::setSigningKeys(const QStringList &fingerprints) { if (mSign) { for (const auto &fpr : fingerprints) { const auto key = mCache->findByKeyIDOrFingerprint(fpr.toUtf8().constData()); if (key.isNull()) { qCDebug(LIBKLEO_LOG) << "Failed to find signing key with fingerprint" << fpr; continue; } mSigKeys[key.protocol()].push_back(key); } } } std::vector KeyResolverCore::Private::resolveRecipientWithGroup(const QString &address, Protocol protocol) { const auto group = mCache->findGroup(address, protocol, KeyCache::KeyUsage::Encrypt); if (group.isNull()) { return {}; } // If we have one unacceptable group key we reject the // whole group to avoid the situation where one key is // skipped or the operation fails. // // We are in Autoresolve land here. In the GUI we // will also show unacceptable group keys so that the // user can see which key is not acceptable. const auto &keys = group.keys(); const bool allKeysAreAcceptable = std::all_of(std::begin(keys), std::end(keys), [this](const auto &key) { return isAcceptableEncryptionKey(key); }); if (!allKeysAreAcceptable) { qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has at least one unacceptable key"; return {}; } for (const auto &k : keys) { qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << address << "with key" << k.primaryFingerprint(); } std::vector result; std::copy(std::begin(keys), std::end(keys), std::back_inserter(result)); return result; } void KeyResolverCore::Private::resolveEncryptionGroups() { for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) { const QString &address = it.key(); auto &protocolKeysMap = it.value(); if (!protocolKeysMap[UnknownProtocol].empty()) { // already resolved by common override continue; } if (mFormat == OpenPGP) { if (!protocolKeysMap[OpenPGP].empty()) { // already resolved by override continue; } protocolKeysMap[OpenPGP] = resolveRecipientWithGroup(address, OpenPGP); } else if (mFormat == CMS) { if (!protocolKeysMap[CMS].empty()) { // already resolved by override continue; } protocolKeysMap[CMS] = resolveRecipientWithGroup(address, CMS); } else { // prefer single-protocol groups over mixed-protocol groups const auto openPGPGroupKeys = resolveRecipientWithGroup(address, OpenPGP); const auto smimeGroupKeys = resolveRecipientWithGroup(address, CMS); if (!openPGPGroupKeys.empty() && !smimeGroupKeys.empty()) { protocolKeysMap[OpenPGP] = openPGPGroupKeys; protocolKeysMap[CMS] = smimeGroupKeys; } else if (openPGPGroupKeys.empty() && smimeGroupKeys.empty()) { // no single-protocol groups found; // if mixed protocols are allowed, then look for any group with encryption keys if (mAllowMixed) { protocolKeysMap[UnknownProtocol] = resolveRecipientWithGroup(address, UnknownProtocol); } } else { // there is a single-protocol group only for one protocol; use this group for all protocols protocolKeysMap[UnknownProtocol] = !openPGPGroupKeys.empty() ? openPGPGroupKeys : smimeGroupKeys; } } } } std::vector KeyResolverCore::Private::resolveRecipient(const QString &address, Protocol protocol) { const auto key = mCache->findBestByMailBox(address.toUtf8().constData(), protocol, KeyCache::KeyUsage::Encrypt); if (key.isNull()) { qCDebug(LIBKLEO_LOG) << "Failed to find any" << Formatting::displayName(protocol) << "key for:" << address; return {}; } if (!isAcceptableEncryptionKey(key, address)) { qCDebug(LIBKLEO_LOG) << "key for:" << address << key.primaryFingerprint() << "has not enough validity"; return {}; } qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << address << "with key" << key.primaryFingerprint(); return {key}; } // Try to find matching keys in the provided protocol for the unresolved addresses void KeyResolverCore::Private::resolveEnc(Protocol proto) { for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) { const QString &address = it.key(); auto &protocolKeysMap = it.value(); if (!protocolKeysMap[proto].empty()) { // already resolved for current protocol (by override or group) continue; } const std::vector &commonOverrideOrGroup = protocolKeysMap[UnknownProtocol]; if (!commonOverrideOrGroup.empty()) { // there is a common override or group; use it for current protocol if possible if (allKeysHaveProtocol(commonOverrideOrGroup, proto)) { protocolKeysMap[proto] = commonOverrideOrGroup; continue; } else { qCDebug(LIBKLEO_LOG) << "Common override/group for" << address << "is unusable for" << Formatting::displayName(proto); continue; } } protocolKeysMap[proto] = resolveRecipient(address, proto); } } auto getBestEncryptionKeys(const QMap>> &encryptionKeys, Protocol preferredProtocol) { QMap> result; for (auto it = encryptionKeys.begin(); it != encryptionKeys.end(); ++it) { const QString &address = it.key(); auto &protocolKeysMap = it.value(); const std::vector &overrideKeys = protocolKeysMap[UnknownProtocol]; if (!overrideKeys.empty()) { result.insert(address, overrideKeys); continue; } const std::vector &keysOpenPGP = protocolKeysMap[OpenPGP]; const std::vector &keysCMS = protocolKeysMap[CMS]; if (keysOpenPGP.empty() && keysCMS.empty()) { result.insert(address, {}); } else if (!keysOpenPGP.empty() && keysCMS.empty()) { result.insert(address, keysOpenPGP); } else if (keysOpenPGP.empty() && !keysCMS.empty()) { result.insert(address, keysCMS); } else { // check whether OpenPGP keys or S/MIME keys have higher validity const int validityPGP = minimumValidity(keysOpenPGP, address); const int validityCMS = minimumValidity(keysCMS, address); if ((validityCMS > validityPGP) || (validityCMS == validityPGP && preferredProtocol == CMS)) { result.insert(address, keysCMS); } else { result.insert(address, keysOpenPGP); } } } return result; } namespace { bool hasUnresolvedSender(const QMap> &signingKeys, Protocol protocol) { return signingKeys.value(protocol).empty(); } bool hasUnresolvedRecipients(const QMap>> &encryptionKeys, Protocol protocol) { return std::any_of(std::cbegin(encryptionKeys), std::cend(encryptionKeys), [protocol](const auto &protocolKeysMap) { return protocolKeysMap.value(protocol).empty(); }); } bool anyCommonOverrideHasKeyOfType(const QMap>> &encryptionKeys, Protocol protocol) { return std::any_of(std::cbegin(encryptionKeys), std::cend(encryptionKeys), [protocol](const auto &protocolKeysMap) { return anyKeyHasProtocol(protocolKeysMap.value(UnknownProtocol), protocol); }); } auto keysForProtocol(const QMap>> &encryptionKeys, Protocol protocol) { QMap> keys; for (auto it = std::begin(encryptionKeys), end = std::end(encryptionKeys); it != end; ++it) { const QString &address = it.key(); const auto &protocolKeysMap = it.value(); keys.insert(address, protocolKeysMap.value(protocol)); } return keys; } template auto concatenate(std::vector v1, const std::vector &v2) { v1.reserve(v1.size() + v2.size()); v1.insert(std::end(v1), std::begin(v2), std::end(v2)); return v1; } } KeyResolverCore::Result KeyResolverCore::Private::resolve() { qCDebug(LIBKLEO_LOG) << "Starting "; if (!mSign && !mEncrypt) { // nothing to do return {AllResolved, {}, {}}; } // First resolve through overrides resolveOverrides(); // check protocols needed for overrides const bool commonOverridesNeedOpenPGP = anyCommonOverrideHasKeyOfType(mEncKeys, OpenPGP); const bool commonOverridesNeedCMS = anyCommonOverrideHasKeyOfType(mEncKeys, CMS); if ((mFormat == OpenPGP && commonOverridesNeedCMS) // || (mFormat == CMS && commonOverridesNeedOpenPGP) // || (!mAllowMixed && commonOverridesNeedOpenPGP && commonOverridesNeedCMS)) { // invalid protocol requirements -> clear intermediate result and abort resolution mEncKeys.clear(); return {Error, {}, {}}; } // Next look for matching groups of keys if (mSign) { resolveSigningGroups(); } if (mEncrypt) { resolveEncryptionGroups(); } // Then look for signing / encryption keys if (mFormat == OpenPGP || mFormat == UnknownProtocol) { resolveSign(OpenPGP); resolveEnc(OpenPGP); } const bool pgpOnly = ((!mEncrypt || !hasUnresolvedRecipients(mEncKeys, OpenPGP)) // && (!mSign || !hasUnresolvedSender(mSigKeys, OpenPGP))); if (mFormat == OpenPGP) { return { SolutionFlags((pgpOnly ? AllResolved : SomeUnresolved) | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)}, {}, }; } if (mFormat == CMS || mFormat == UnknownProtocol) { resolveSign(CMS); resolveEnc(CMS); } const bool cmsOnly = ((!mEncrypt || !hasUnresolvedRecipients(mEncKeys, CMS)) // && (!mSign || !hasUnresolvedSender(mSigKeys, CMS))); if (mFormat == CMS) { return { SolutionFlags((cmsOnly ? AllResolved : SomeUnresolved) | CMSOnly), {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}, {}, }; } // check if single-protocol solution has been found if (cmsOnly && (!pgpOnly || mPreferredProtocol == CMS)) { if (!mAllowMixed) { return { SolutionFlags(AllResolved | CMSOnly), {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}, {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)}, }; } else { return { SolutionFlags(AllResolved | CMSOnly), {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}, {}, }; } } if (pgpOnly) { if (!mAllowMixed) { return { SolutionFlags(AllResolved | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)}, {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}, }; } else { return { SolutionFlags(AllResolved | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)}, {}, }; } } if (!mAllowMixed) { // return incomplete single-protocol solution if (mPreferredProtocol == CMS) { return { SolutionFlags(SomeUnresolved | CMSOnly), {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}, {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)}, }; } else { return { SolutionFlags(SomeUnresolved | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)}, {CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)}, }; } } const auto bestEncryptionKeys = getBestEncryptionKeys(mEncKeys, mPreferredProtocol); // we are in mixed mode, i.e. we need an OpenPGP signing key and an S/MIME signing key const bool senderIsResolved = (!mSign || (!hasUnresolvedSender(mSigKeys, OpenPGP) && !hasUnresolvedSender(mSigKeys, CMS))); const bool allRecipientsAreResolved = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys), [](const auto &keys) { return !keys.empty(); }); if (senderIsResolved && allRecipientsAreResolved) { return { SolutionFlags(AllResolved | MixedProtocols), {UnknownProtocol, concatenate(mSigKeys.value(OpenPGP), mSigKeys.value(CMS)), bestEncryptionKeys}, {}, }; } const bool allKeysAreOpenPGP = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys), [](const auto &keys) { return allKeysHaveProtocol(keys, OpenPGP); }); if (allKeysAreOpenPGP) { return { SolutionFlags(SomeUnresolved | OpenPGPOnly), {OpenPGP, mSigKeys.value(OpenPGP), bestEncryptionKeys}, {}, }; } const bool allKeysAreCMS = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys), [](const auto &keys) { return allKeysHaveProtocol(keys, CMS); }); if (allKeysAreCMS) { return { SolutionFlags(SomeUnresolved | CMSOnly), {CMS, mSigKeys.value(CMS), bestEncryptionKeys}, {}, }; } return { SolutionFlags(SomeUnresolved | MixedProtocols), {UnknownProtocol, concatenate(mSigKeys.value(OpenPGP), mSigKeys.value(CMS)), bestEncryptionKeys}, {}, }; } KeyResolverCore::KeyResolverCore(bool encrypt, bool sign, Protocol fmt) : d(new Private(this, encrypt, sign, fmt)) { } KeyResolverCore::~KeyResolverCore() = default; void KeyResolverCore::setSender(const QString &address) { d->setSender(address); } QString KeyResolverCore::normalizedSender() const { return d->mSender; } void KeyResolverCore::setRecipients(const QStringList &addresses) { d->addRecipients(addresses); } void KeyResolverCore::setSigningKeys(const QStringList &fingerprints) { d->setSigningKeys(fingerprints); } void KeyResolverCore::setOverrideKeys(const QMap> &overrides) { d->setOverrideKeys(overrides); } void KeyResolverCore::setAllowMixedProtocols(bool allowMixed) { d->mAllowMixed = allowMixed; } void KeyResolverCore::setPreferredProtocol(Protocol proto) { d->mPreferredProtocol = proto; } void KeyResolverCore::setMinimumValidity(int validity) { d->mMinimumValidity = validity; } KeyResolverCore::Result KeyResolverCore::resolve() { return d->resolve(); } diff --git a/src/kleo/keyresolvercore.h b/src/kleo/keyresolvercore.h index 546d0db97..9f2321f47 100644 --- a/src/kleo/keyresolvercore.h +++ b/src/kleo/keyresolvercore.h @@ -1,85 +1,85 @@ /* -*- c++ -*- kleo/keyresolvercore.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include "kleo_export.h" + #include #include #include #include #include #include -#include "kleo_export.h" - class QString; namespace GpgME { class Key; } namespace Kleo { class KLEO_EXPORT KeyResolverCore { public: enum SolutionFlags { // clang-format off SomeUnresolved = 0, AllResolved = 1, OpenPGPOnly = 2, CMSOnly = 4, MixedProtocols = OpenPGPOnly | CMSOnly, Error = 0x1000, ResolvedMask = AllResolved | Error, ProtocolsMask = OpenPGPOnly | CMSOnly | Error, // clang-format on }; struct Result { SolutionFlags flags; KeyResolver::Solution solution; KeyResolver::Solution alternative; }; explicit KeyResolverCore(bool encrypt, bool sign, GpgME::Protocol format = GpgME::UnknownProtocol); ~KeyResolverCore(); void setSender(const QString &sender); QString normalizedSender() const; void setRecipients(const QStringList &addresses); void setSigningKeys(const QStringList &fingerprints); void setOverrideKeys(const QMap> &overrides); void setAllowMixedProtocols(bool allowMixed); void setPreferredProtocol(GpgME::Protocol proto); void setMinimumValidity(int validity); Result resolve(); private: class Private; std::unique_ptr d; }; } // namespace Kleo diff --git a/src/kleo/keyserverconfig.cpp b/src/kleo/keyserverconfig.cpp index 33e2d8f16..c1a86ce45 100644 --- a/src/kleo/keyserverconfig.cpp +++ b/src/kleo/keyserverconfig.cpp @@ -1,221 +1,221 @@ /* kleo/keyserverconfig.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keyserverconfig.h" -#include +#include #include #include using namespace Kleo; class KeyserverConfig::Private { public: explicit Private(); QString host; int port = -1; // -1 == use default port KeyserverAuthentication authentication = KeyserverAuthentication::Anonymous; QString user; QString password; KeyserverConnection connection = KeyserverConnection::Default; QString baseDn; QStringList additionalFlags; }; KeyserverConfig::Private::Private() { } KeyserverConfig::KeyserverConfig() : d{std::make_unique()} { } KeyserverConfig::~KeyserverConfig() = default; KeyserverConfig::KeyserverConfig(const KeyserverConfig &other) : d{std::make_unique(*other.d)} { } KeyserverConfig &KeyserverConfig::operator=(const KeyserverConfig &other) { *d = *other.d; return *this; } KeyserverConfig::KeyserverConfig(KeyserverConfig &&other) = default; KeyserverConfig &KeyserverConfig::operator=(KeyserverConfig &&other) = default; KeyserverConfig KeyserverConfig::fromUrl(const QUrl &url) { KeyserverConfig config; config.d->host = url.host(); config.d->port = url.port(); config.d->user = url.userName(); config.d->password = url.password(); if (!config.d->user.isEmpty()) { config.d->authentication = KeyserverAuthentication::Password; } if (url.hasFragment()) { const auto flags = transformInPlace(url.fragment().split(QLatin1Char{','}, Qt::SkipEmptyParts), [](const auto &flag) { return flag.trimmed().toLower(); }); for (const auto &flag : flags) { if (flag == QLatin1String{"starttls"}) { config.d->connection = KeyserverConnection::UseSTARTTLS; } else if (flag == QLatin1String{"ldaptls"}) { config.d->connection = KeyserverConnection::TunnelThroughTLS; } else if (flag == QLatin1String{"plain"}) { config.d->connection = KeyserverConnection::Plain; } else if (flag == QLatin1String{"ntds"}) { config.d->authentication = KeyserverAuthentication::ActiveDirectory; } else { config.d->additionalFlags.push_back(flag); } } } if (url.hasQuery()) { config.d->baseDn = url.query(); } return config; } QUrl KeyserverConfig::toUrl() const { QUrl url; url.setScheme(QStringLiteral("ldap")); // set host to empty string if it's a null string; this ensures that the URL has an authority and always gets a "//" after the scheme url.setHost(d->host.isNull() ? QStringLiteral("") : d->host); if (d->port != -1) { url.setPort(d->port); } if (!d->user.isEmpty()) { url.setUserName(d->user); } if (!d->password.isEmpty()) { url.setPassword(d->password); } if (!d->baseDn.isEmpty()) { url.setQuery(d->baseDn); } QStringList flags; switch (d->connection) { case KeyserverConnection::UseSTARTTLS: flags.push_back(QStringLiteral("starttls")); break; case KeyserverConnection::TunnelThroughTLS: flags.push_back(QStringLiteral("ldaptls")); break; case KeyserverConnection::Plain: flags.push_back(QStringLiteral("plain")); break; case KeyserverConnection::Default:; // omit connection flag to use default } if (d->authentication == KeyserverAuthentication::ActiveDirectory) { flags.push_back(QStringLiteral("ntds")); } std::copy(std::cbegin(d->additionalFlags), std::cend(d->additionalFlags), std::back_inserter(flags)); if (!flags.isEmpty()) { url.setFragment(flags.join(QLatin1Char{','})); } return url; } QString KeyserverConfig::host() const { return d->host; } void KeyserverConfig::setHost(const QString &host) { d->host = host; } int KeyserverConfig::port() const { return d->port; } void KeyserverConfig::setPort(int port) { d->port = port; } KeyserverAuthentication KeyserverConfig::authentication() const { return d->authentication; } void KeyserverConfig::setAuthentication(KeyserverAuthentication authentication) { d->authentication = authentication; } QString KeyserverConfig::user() const { return d->user; } void KeyserverConfig::setUser(const QString &user) { d->user = user; } QString KeyserverConfig::password() const { return d->password; } void KeyserverConfig::setPassword(const QString &password) { d->password = password; } KeyserverConnection KeyserverConfig::connection() const { return d->connection; } void KeyserverConfig::setConnection(KeyserverConnection connection) { d->connection = connection; } QString KeyserverConfig::ldapBaseDn() const { return d->baseDn; } void KeyserverConfig::setLdapBaseDn(const QString &baseDn) { d->baseDn = baseDn; } QStringList KeyserverConfig::additionalFlags() const { return d->additionalFlags; } void KeyserverConfig::setAdditionalFlags(const QStringList &flags) { d->additionalFlags = flags; } diff --git a/src/kleo/keyserverconfig.h b/src/kleo/keyserverconfig.h index ede1f4529..bc915696d 100644 --- a/src/kleo/keyserverconfig.h +++ b/src/kleo/keyserverconfig.h @@ -1,80 +1,82 @@ /* kleo/keyserverconfig.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" + #include + #include class QString; class QUrl; namespace Kleo { enum class KeyserverAuthentication { Anonymous, ActiveDirectory, Password, }; enum class KeyserverConnection { Default, Plain, UseSTARTTLS, TunnelThroughTLS, }; class KLEO_EXPORT KeyserverConfig { public: KeyserverConfig(); ~KeyserverConfig(); KeyserverConfig(const KeyserverConfig &other); KeyserverConfig &operator=(const KeyserverConfig &other); KeyserverConfig(KeyserverConfig &&other); KeyserverConfig &operator=(KeyserverConfig &&other); static KeyserverConfig fromUrl(const QUrl &url); QUrl toUrl() const; QString host() const; void setHost(const QString &host); int port() const; void setPort(int port); KeyserverAuthentication authentication() const; void setAuthentication(KeyserverAuthentication authentication); QString user() const; void setUser(const QString &user); QString password() const; void setPassword(const QString &password); KeyserverConnection connection() const; void setConnection(KeyserverConnection connection); QString ldapBaseDn() const; void setLdapBaseDn(const QString &baseDn); QStringList additionalFlags() const; void setAdditionalFlags(const QStringList &flags); private: class Private; std::unique_ptr d; }; } diff --git a/src/kleo/kleoexception.h b/src/kleo/kleoexception.h index ebb293888..4e3e8b27b 100644 --- a/src/kleo/kleoexception.h +++ b/src/kleo/kleoexception.h @@ -1,68 +1,69 @@ /* -*- mode: c++; c-basic-offset:4 -*- exception.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" -#include +#include + #include -#include +#include namespace Kleo { class KLEO_EXPORT Exception : public GpgME::Exception { public: Exception(gpg_error_t e, const std::string &msg, Options opt = NoOptions) : GpgME::Exception(GpgME::Error(e), msg, opt) { } Exception(gpg_error_t e, const char *msg, Options opt = NoOptions) : GpgME::Exception(GpgME::Error(e), msg, opt) { } Exception(gpg_error_t e, const QString &msg, Options opt = NoOptions) : GpgME::Exception(GpgME::Error(e), msg.toLocal8Bit().constData(), opt) { } Exception(const GpgME::Error &e, const std::string &msg) : GpgME::Exception(e, msg) { } Exception(const GpgME::Error &e, const char *msg) : GpgME::Exception(e, msg) { } Exception(const GpgME::Error &e, const QString &msg) : GpgME::Exception(e, msg.toLocal8Bit().constData()) { } ~Exception() throw() override; const std::string &messageLocal8Bit() const { return GpgME::Exception::message(); } gpg_error_t error_code() const { return error().encodedError(); } QString message() const { return QString::fromLocal8Bit(GpgME::Exception::message().c_str()); } }; } diff --git a/src/models/keycache.cpp b/src/models/keycache.cpp index f4c8bdc76..b697087e1 100644 --- a/src/models/keycache.cpp +++ b/src/models/keycache.cpp @@ -1,1740 +1,1737 @@ /* -*- mode: c++; c-basic-offset:4 -*- models/keycache.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007, 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-FileCopyrightText: 2020, 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keycache.h" #include "keycache_p.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include +#include +#include +#include + +#include +#include +#include + #include #include #include #include #include #include -#include -#include -#include - #include -//#include - -#include -#include -#include - #include +#include #include #include #include -#include - using namespace std::chrono_literals; using namespace Kleo; using namespace GpgME; using namespace KMime::Types; static const unsigned int hours2ms = 1000 * 60 * 60; // // // KeyCache // // namespace { make_comparator_str(ByEMail, .first.c_str()); } class Kleo::KeyCacheAutoRefreshSuspension { KeyCacheAutoRefreshSuspension() { qCDebug(LIBKLEO_LOG) << __func__; auto cache = KeyCache::mutableInstance(); cache->enableFileSystemWatcher(false); m_refreshInterval = cache->refreshInterval(); cache->setRefreshInterval(0); cache->cancelKeyListing(); m_cache = cache; } public: ~KeyCacheAutoRefreshSuspension() { qCDebug(LIBKLEO_LOG) << __func__; if (auto cache = m_cache.lock()) { cache->enableFileSystemWatcher(true); cache->setRefreshInterval(m_refreshInterval); } } static std::shared_ptr instance() { static std::weak_ptr self; if (auto s = self.lock()) { return s; } else { s = std::shared_ptr{new KeyCacheAutoRefreshSuspension{}}; self = s; return s; } } private: std::weak_ptr m_cache; int m_refreshInterval = 0; }; class KeyCache::Private { friend class ::Kleo::KeyCache; KeyCache *const q; public: explicit Private(KeyCache *qq) : q(qq) , m_refreshInterval(1) , m_initalized(false) , m_pgpOnly(true) , m_remarks_enabled(false) { connect(&m_autoKeyListingTimer, &QTimer::timeout, q, [this]() { q->startKeyListing(); }); updateAutoKeyListingTimer(); } ~Private() { if (m_refreshJob) { m_refreshJob->cancel(); } } template class Op> class Comp> std::vector::const_iterator find(const std::vector &keys, const char *key) const { ensureCachePopulated(); const auto it = std::lower_bound(keys.begin(), keys.end(), key, Comp()); if (it == keys.end() || Comp()(*it, key)) { return it; } else { return keys.end(); } } template class Op> class Comp> std::vector::const_iterator find(const std::vector &keys, const char *key) const { ensureCachePopulated(); const auto it = std::lower_bound(keys.begin(), keys.end(), key, Comp()); if (it == keys.end() || Comp()(*it, key)) { return it; } else { return keys.end(); } } std::vector::const_iterator find_fpr(const char *fpr) const { return find<_detail::ByFingerprint>(by.fpr, fpr); } std::pair>::const_iterator, std::vector>::const_iterator> find_email(const char *email) const { ensureCachePopulated(); return std::equal_range(by.email.begin(), by.email.end(), email, ByEMail()); } std::vector find_mailbox(const QString &email, bool sign) const; std::vector::const_iterator find_keygrip(const char *keygrip) const { return find<_detail::ByKeyGrip>(by.keygrip, keygrip); } std::vector::const_iterator find_subkeyid(const char *subkeyid) const { return find<_detail::ByKeyID>(by.subkeyid, subkeyid); } std::vector::const_iterator find_keyid(const char *keyid) const { return find<_detail::ByKeyID>(by.keyid, keyid); } std::vector::const_iterator find_shortkeyid(const char *shortkeyid) const { return find<_detail::ByShortKeyID>(by.shortkeyid, shortkeyid); } std::pair::const_iterator, std::vector::const_iterator> find_subjects(const char *chain_id) const { ensureCachePopulated(); return std::equal_range(by.chainid.begin(), by.chainid.end(), chain_id, _detail::ByChainID()); } void refreshJobDone(const KeyListResult &result); void setRefreshInterval(int interval) { m_refreshInterval = interval; updateAutoKeyListingTimer(); } int refreshInterval() const { return m_refreshInterval; } void updateAutoKeyListingTimer() { setAutoKeyListingInterval(hours2ms * m_refreshInterval); } void setAutoKeyListingInterval(int ms) { m_autoKeyListingTimer.stop(); m_autoKeyListingTimer.setInterval(ms); if (ms != 0) { m_autoKeyListingTimer.start(); } } void ensureCachePopulated() const; void readGroupsFromGpgConf() { // According to Werner Koch groups are more of a hack to solve // a valid usecase (e.g. several keys defined for an internal mailing list) // that won't make it in the proper keylist interface. And using gpgconf // was the suggested way to support groups. auto conf = QGpgME::cryptoConfig(); if (!conf) { return; } auto entry = getCryptoConfigEntry(conf, "gpg", "group"); if (!entry) { return; } // collect the key fingerprints for all groups read from the configuration QMap fingerprints; const auto stringValueList = entry->stringValueList(); for (const QString &value : stringValueList) { const QStringList split = value.split(QLatin1Char('=')); if (split.size() != 2) { qCDebug(LIBKLEO_LOG) << "Ignoring invalid group config:" << value; continue; } const QString groupName = split[0]; const QString fingerprint = split[1]; fingerprints[groupName].push_back(fingerprint); } // add all groups read from the configuration to the list of groups for (auto it = fingerprints.cbegin(); it != fingerprints.cend(); ++it) { const QString groupName = it.key(); const std::vector groupKeys = q->findByFingerprint(toStdStrings(it.value())); KeyGroup g(groupName, groupName, groupKeys, KeyGroup::GnuPGConfig); m_groups.push_back(g); } } void readGroupsFromGroupsConfig() { Q_ASSERT(m_groupConfig); if (!m_groupConfig) { qCWarning(LIBKLEO_LOG) << __func__ << "group config not set"; return; } m_groups = m_groupConfig->readGroups(); } KeyGroup writeGroupToGroupsConfig(const KeyGroup &group) { Q_ASSERT(m_groupConfig); if (!m_groupConfig) { qCWarning(LIBKLEO_LOG) << __func__ << "group config not set"; return {}; } Q_ASSERT(!group.isNull()); Q_ASSERT(group.source() == KeyGroup::ApplicationConfig); if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) { qCDebug(LIBKLEO_LOG) << __func__ << "group cannot be written to application configuration:" << group; return group; } return m_groupConfig->writeGroup(group); } bool removeGroupFromGroupsConfig(const KeyGroup &group) { Q_ASSERT(m_groupConfig); if (!m_groupConfig) { qCWarning(LIBKLEO_LOG) << __func__ << "group config not set"; return false; } Q_ASSERT(!group.isNull()); Q_ASSERT(group.source() == KeyGroup::ApplicationConfig); if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) { qCDebug(LIBKLEO_LOG) << __func__ << "group cannot be removed from application configuration:" << group; return false; } return m_groupConfig->removeGroup(group); } void updateGroupCache() { // Update Group Keys // this is a quick thing as it only involves reading the config // so no need for a job. m_groups.clear(); if (m_groupsEnabled) { readGroupsFromGpgConf(); readGroupsFromGroupsConfig(); } } bool insert(const KeyGroup &group) { Q_ASSERT(!group.isNull()); Q_ASSERT(group.source() == KeyGroup::ApplicationConfig); if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Invalid group:" << group; return false; } const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [group](const auto &g) { return g.source() == group.source() && g.id() == group.id(); }); if (it != m_groups.cend()) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Group already present in list of groups:" << group; return false; } const KeyGroup savedGroup = writeGroupToGroupsConfig(group); if (savedGroup.isNull()) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Writing group" << group.id() << "to config file failed"; return false; } m_groups.push_back(savedGroup); Q_EMIT q->groupAdded(savedGroup); return true; } bool update(const KeyGroup &group) { Q_ASSERT(!group.isNull()); Q_ASSERT(group.source() == KeyGroup::ApplicationConfig); if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Invalid group:" << group; return false; } const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [group](const auto &g) { return g.source() == group.source() && g.id() == group.id(); }); if (it == m_groups.cend()) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Group not found in list of groups:" << group; return false; } const auto groupIndex = std::distance(m_groups.cbegin(), it); const KeyGroup savedGroup = writeGroupToGroupsConfig(group); if (savedGroup.isNull()) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Writing group" << group.id() << "to config file failed"; return false; } m_groups[groupIndex] = savedGroup; Q_EMIT q->groupUpdated(savedGroup); return true; } bool remove(const KeyGroup &group) { Q_ASSERT(!group.isNull()); Q_ASSERT(group.source() == KeyGroup::ApplicationConfig); if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Invalid group:" << group; return false; } const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [group](const auto &g) { return g.source() == group.source() && g.id() == group.id(); }); if (it == m_groups.cend()) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Group not found in list of groups:" << group; return false; } const bool success = removeGroupFromGroupsConfig(group); if (!success) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Removing group" << group.id() << "from config file failed"; return false; } m_groups.erase(it); Q_EMIT q->groupRemoved(group); return true; } private: QPointer m_refreshJob; std::vector> m_fsWatchers; QTimer m_autoKeyListingTimer; int m_refreshInterval; struct By { std::vector fpr, keyid, shortkeyid, chainid; std::vector> email; std::vector subkeyid, keygrip; } by; bool m_initalized; bool m_pgpOnly; bool m_remarks_enabled; bool m_groupsEnabled = false; std::shared_ptr m_groupConfig; std::vector m_groups; }; std::shared_ptr KeyCache::instance() { return mutableInstance(); } std::shared_ptr KeyCache::mutableInstance() { static std::weak_ptr self; try { return std::shared_ptr(self); } catch (const std::bad_weak_ptr &) { const std::shared_ptr s(new KeyCache); self = s; return s; } } KeyCache::KeyCache() : QObject() , d(new Private(this)) { } KeyCache::~KeyCache() { } void KeyCache::setGroupsEnabled(bool enabled) { d->m_groupsEnabled = enabled; if (d->m_initalized) { d->updateGroupCache(); } } void KeyCache::setGroupConfig(const std::shared_ptr &groupConfig) { d->m_groupConfig = groupConfig; } void KeyCache::enableFileSystemWatcher(bool enable) { for (const auto &i : std::as_const(d->m_fsWatchers)) { i->setEnabled(enable); } } void KeyCache::setRefreshInterval(int hours) { d->setRefreshInterval(hours); } int KeyCache::refreshInterval() const { return d->refreshInterval(); } std::shared_ptr KeyCache::suspendAutoRefresh() { return KeyCacheAutoRefreshSuspension::instance(); } void KeyCache::reload(GpgME::Protocol /*proto*/) { if (d->m_refreshJob) { return; } d->updateAutoKeyListingTimer(); enableFileSystemWatcher(false); d->m_refreshJob = new RefreshKeysJob(this); connect(d->m_refreshJob.data(), &RefreshKeysJob::done, this, [this](const GpgME::KeyListResult &r) { d->refreshJobDone(r); }); connect(d->m_refreshJob.data(), &RefreshKeysJob::canceled, this, [this]() { d->m_refreshJob.clear(); }); d->m_refreshJob->start(); } void KeyCache::cancelKeyListing() { if (!d->m_refreshJob) { return; } d->m_refreshJob->cancel(); } void KeyCache::addFileSystemWatcher(const std::shared_ptr &watcher) { if (!watcher) { return; } d->m_fsWatchers.push_back(watcher); connect(watcher.get(), &FileSystemWatcher::directoryChanged, this, [this]() { startKeyListing(); }); connect(watcher.get(), &FileSystemWatcher::fileChanged, this, [this]() { startKeyListing(); }); watcher->setEnabled(d->m_refreshJob.isNull()); } void KeyCache::enableRemarks(bool value) { if (!d->m_remarks_enabled && value) { d->m_remarks_enabled = value; if (d->m_initalized && !d->m_refreshJob) { qCDebug(LIBKLEO_LOG) << "Reloading keycache with remarks enabled"; reload(); } else { connect(d->m_refreshJob.data(), &RefreshKeysJob::done, this, [this](const GpgME::KeyListResult &) { qCDebug(LIBKLEO_LOG) << "Reloading keycache with remarks enabled"; QTimer::singleShot(1s, this, [this]() { reload(); }); }); } } else { d->m_remarks_enabled = value; } } bool KeyCache::remarksEnabled() const { return d->m_remarks_enabled; } void KeyCache::Private::refreshJobDone(const KeyListResult &result) { m_refreshJob.clear(); q->enableFileSystemWatcher(true); m_initalized = true; updateGroupCache(); Q_EMIT q->keyListingDone(result); } const Key &KeyCache::findByFingerprint(const char *fpr) const { const std::vector::const_iterator it = d->find_fpr(fpr); if (it == d->by.fpr.end()) { static const Key null; return null; } else { return *it; } } const Key &KeyCache::findByFingerprint(const std::string &fpr) const { return findByFingerprint(fpr.c_str()); } std::vector KeyCache::findByFingerprint(const std::vector &fprs) const { std::vector keys; keys.reserve(fprs.size()); for (const auto &fpr : fprs) { const Key key = findByFingerprint(fpr.c_str()); if (key.isNull()) { qCDebug(LIBKLEO_LOG) << __func__ << "Ignoring unknown key with fingerprint:" << fpr.c_str(); continue; } keys.push_back(key); } return keys; } std::vector KeyCache::findByEMailAddress(const char *email) const { const auto pair = d->find_email(email); std::vector result; result.reserve(std::distance(pair.first, pair.second)); std::transform(pair.first, pair.second, std::back_inserter(result), [](const std::pair &pair) { return pair.second; }); return result; } std::vector KeyCache::findByEMailAddress(const std::string &email) const { return findByEMailAddress(email.c_str()); } const Key &KeyCache::findByShortKeyID(const char *id) const { const std::vector::const_iterator it = d->find_shortkeyid(id); if (it != d->by.shortkeyid.end()) { return *it; } static const Key null; return null; } const Key &KeyCache::findByShortKeyID(const std::string &id) const { return findByShortKeyID(id.c_str()); } const Key &KeyCache::findByKeyIDOrFingerprint(const char *id) const { { // try by.fpr first: const std::vector::const_iterator it = d->find_fpr(id); if (it != d->by.fpr.end()) { return *it; } } { // try by.keyid next: const std::vector::const_iterator it = d->find_keyid(id); if (it != d->by.keyid.end()) { return *it; } } static const Key null; return null; } const Key &KeyCache::findByKeyIDOrFingerprint(const std::string &id) const { return findByKeyIDOrFingerprint(id.c_str()); } std::vector KeyCache::findByKeyIDOrFingerprint(const std::vector &ids) const { std::vector keyids; std::remove_copy_if(ids.begin(), ids.end(), std::back_inserter(keyids), [](const std::string &str) { return !str.c_str() || !*str.c_str(); }); // this is just case-insensitive string search: std::sort(keyids.begin(), keyids.end(), _detail::ByFingerprint()); std::vector result; result.reserve(keyids.size()); // dups shouldn't happen d->ensureCachePopulated(); kdtools::set_intersection(d->by.fpr.begin(), d->by.fpr.end(), keyids.begin(), keyids.end(), std::back_inserter(result), _detail::ByFingerprint()); if (result.size() < keyids.size()) { // note that By{Fingerprint,KeyID,ShortKeyID} define the same // order for _strings_ kdtools::set_intersection(d->by.keyid.begin(), d->by.keyid.end(), keyids.begin(), keyids.end(), std::back_inserter(result), _detail::ByKeyID()); } // duplicates shouldn't happen, but make sure nonetheless: std::sort(result.begin(), result.end(), _detail::ByFingerprint()); result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint()), result.end()); // we skip looking into short key ids here, as it's highly // unlikely they're used for this purpose. We might need to revise // this decision, but only after testing. return result; } const Subkey &KeyCache::findSubkeyByKeyGrip(const char *grip, Protocol protocol) const { static const Subkey null; d->ensureCachePopulated(); const auto range = std::equal_range(d->by.keygrip.begin(), d->by.keygrip.end(), grip, _detail::ByKeyGrip()); if (range.first == range.second) { return null; } else if (protocol == UnknownProtocol) { return *range.first; } else { for (auto it = range.first; it != range.second; ++it) { if (it->parent().protocol() == protocol) { return *it; } } } return null; } const Subkey &KeyCache::findSubkeyByKeyGrip(const std::string &grip, Protocol protocol) const { return findSubkeyByKeyGrip(grip.c_str(), protocol); } std::vector KeyCache::findSubkeysByKeyID(const std::vector &ids) const { std::vector sorted; sorted.reserve(ids.size()); std::remove_copy_if(ids.begin(), ids.end(), std::back_inserter(sorted), [](const std::string &str) { return !str.c_str() || !*str.c_str(); }); std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID()); std::vector result; d->ensureCachePopulated(); kdtools::set_intersection(d->by.subkeyid.begin(), d->by.subkeyid.end(), sorted.begin(), sorted.end(), std::back_inserter(result), _detail::ByKeyID()); return result; } std::vector KeyCache::findRecipients(const DecryptionResult &res) const { std::vector keyids; const auto recipients = res.recipients(); for (const DecryptionResult::Recipient &r : recipients) { if (const char *kid = r.keyID()) { keyids.push_back(kid); } } const std::vector subkeys = findSubkeysByKeyID(keyids); std::vector result; result.reserve(subkeys.size()); std::transform(subkeys.begin(), subkeys.end(), std::back_inserter(result), std::mem_fn(&Subkey::parent)); std::sort(result.begin(), result.end(), _detail::ByFingerprint()); result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint()), result.end()); return result; } std::vector KeyCache::findSigners(const VerificationResult &res) const { std::vector fprs; const auto signatures = res.signatures(); for (const Signature &s : signatures) { if (const char *fpr = s.fingerprint()) { fprs.push_back(fpr); } } return findByKeyIDOrFingerprint(fprs); } std::vector KeyCache::findSigningKeysByMailbox(const QString &mb) const { return d->find_mailbox(mb, true); } std::vector KeyCache::findEncryptionKeysByMailbox(const QString &mb) const { return d->find_mailbox(mb, false); } namespace { #define DO(op, meth, meth2) \ if (op key.meth()) { \ } else { \ qDebug("rejecting for signing: %s: %s", #meth2, key.primaryFingerprint()); \ return false; \ } #define ACCEPT(meth) DO(!!, meth, !meth) #define REJECT(meth) DO(!, meth, meth) struct ready_for_signing { bool operator()(const Key &key) const { #if 1 ACCEPT(hasSecret); ACCEPT(canReallySign); REJECT(isRevoked); REJECT(isExpired); REJECT(isDisabled); REJECT(isInvalid); return true; #else return key.hasSecret() && key.canReallySign() && !key.isRevoked() && !key.isExpired() && !key.isDisabled() && !key.isInvalid(); #endif #undef DO } }; #define DO(op, meth, meth2) \ if (op key.meth()) { \ } else { \ qDebug("rejecting for encrypting: %s: %s", #meth2, key.primaryFingerprint()); \ return false; \ } struct ready_for_encryption { bool operator()(const Key &key) const { #if 1 ACCEPT(canEncrypt); REJECT(isRevoked); REJECT(isExpired); REJECT(isDisabled); REJECT(isInvalid); return true; #else return key.canEncrypt() && !key.isRevoked() && !key.isExpired() && !key.isDisabled() && !key.isInvalid(); #endif } #undef DO #undef ACCEPT #undef REJECT }; } std::vector KeyCache::Private::find_mailbox(const QString &email, bool sign) const { if (email.isEmpty()) { return std::vector(); } const auto pair = find_email(email.toUtf8().constData()); std::vector result; result.reserve(std::distance(pair.first, pair.second)); if (sign) { kdtools::copy_2nd_if(pair.first, pair.second, std::back_inserter(result), ready_for_signing()); } else { kdtools::copy_2nd_if(pair.first, pair.second, std::back_inserter(result), ready_for_encryption()); } return result; } std::vector KeyCache::findSubjects(const GpgME::Key &key, Options options) const { return findSubjects(std::vector(1, key), options); } std::vector KeyCache::findSubjects(const std::vector &keys, Options options) const { return findSubjects(keys.begin(), keys.end(), options); } std::vector KeyCache::findSubjects(std::vector::const_iterator first, std::vector::const_iterator last, Options options) const { if (first == last) { return std::vector(); } std::vector result; while (first != last) { const auto pair = d->find_subjects(first->primaryFingerprint()); result.insert(result.end(), pair.first, pair.second); ++first; } std::sort(result.begin(), result.end(), _detail::ByFingerprint()); result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint()), result.end()); if (options & RecursiveSearch) { const std::vector furtherSubjects = findSubjects(result, options); std::vector combined; combined.reserve(result.size() + furtherSubjects.size()); std::merge(result.begin(), result.end(), furtherSubjects.begin(), furtherSubjects.end(), std::back_inserter(combined), _detail::ByFingerprint()); combined.erase(std::unique(combined.begin(), combined.end(), _detail::ByFingerprint()), combined.end()); result.swap(combined); } return result; } std::vector KeyCache::findIssuers(const Key &key, Options options) const { std::vector result; if (key.isNull()) { return result; } if (options & IncludeSubject) { result.push_back(key); } if (key.isRoot()) { return result; } const Key &issuer = findByFingerprint(key.chainID()); if (issuer.isNull()) { return result; } result.push_back(issuer); if (!(options & RecursiveSearch)) { return result; } while (true) { const Key &issuer = findByFingerprint(result.back().chainID()); if (issuer.isNull()) { break; } const bool chainAlreadyContainsIssuer = Kleo::contains_if(result, [issuer](const auto &key) { return _detail::ByFingerprint()(issuer, key); }); // we also add the issuer if the chain already contains it, so that // the user can spot the recursion result.push_back(issuer); if (issuer.isRoot() || chainAlreadyContainsIssuer) { break; } } return result; } static std::string email(const UserID &uid) { // Prefer the gnupg normalized one const std::string addr = uid.addrSpec(); if (!addr.empty()) { return addr; } const std::string email = uid.email(); if (email.empty()) { return DN(uid.id())[QStringLiteral("EMAIL")].trimmed().toUtf8().constData(); } if (email[0] == '<' && email[email.size() - 1] == '>') { return email.substr(1, email.size() - 2); } else { return email; } } static std::vector emails(const Key &key) { std::vector emails; const auto userIDs = key.userIDs(); for (const UserID &uid : userIDs) { const std::string e = email(uid); if (!e.empty()) { emails.push_back(e); } } std::sort(emails.begin(), emails.end(), ByEMail()); emails.erase(std::unique(emails.begin(), emails.end(), ByEMail()), emails.end()); return emails; } void KeyCache::remove(const Key &key) { if (key.isNull()) { return; } const char *fpr = key.primaryFingerprint(); if (!fpr) { return; } Q_EMIT aboutToRemove(key); { const auto range = std::equal_range(d->by.fpr.begin(), d->by.fpr.end(), fpr, _detail::ByFingerprint()); d->by.fpr.erase(range.first, range.second); } if (const char *keyid = key.keyID()) { const auto range = std::equal_range(d->by.keyid.begin(), d->by.keyid.end(), keyid, _detail::ByKeyID()); const auto it = std::remove_if(range.first, range.second, [fpr](const GpgME::Key &key) { return _detail::ByFingerprint()(fpr, key); }); d->by.keyid.erase(it, range.second); } if (const char *shortkeyid = key.shortKeyID()) { const auto range = std::equal_range(d->by.shortkeyid.begin(), d->by.shortkeyid.end(), shortkeyid, _detail::ByShortKeyID()); const auto it = std::remove_if(range.first, range.second, [fpr](const GpgME::Key &key) { return _detail::ByFingerprint()(fpr, key); }); d->by.shortkeyid.erase(it, range.second); } if (const char *chainid = key.chainID()) { const auto range = std::equal_range(d->by.chainid.begin(), d->by.chainid.end(), chainid, _detail::ByChainID()); const auto range2 = std::equal_range(range.first, range.second, fpr, _detail::ByFingerprint()); d->by.chainid.erase(range2.first, range2.second); } const auto emailsKey{emails(key)}; for (const std::string &email : emailsKey) { const auto range = std::equal_range(d->by.email.begin(), d->by.email.end(), email, ByEMail()); const auto it = std::remove_if(range.first, range.second, [fpr](const std::pair &pair) { return qstricmp(fpr, pair.second.primaryFingerprint()) == 0; }); d->by.email.erase(it, range.second); } const auto keySubKeys{key.subkeys()}; for (const Subkey &subkey : keySubKeys) { if (const char *keyid = subkey.keyID()) { const auto range = std::equal_range(d->by.subkeyid.begin(), d->by.subkeyid.end(), keyid, _detail::ByKeyID()); const auto it = std::remove_if(range.first, range.second, [fpr](const Subkey &subkey) { return !qstricmp(fpr, subkey.parent().primaryFingerprint()); }); d->by.subkeyid.erase(it, range.second); } if (const char *keygrip = subkey.keyGrip()) { const auto range = std::equal_range(d->by.keygrip.begin(), d->by.keygrip.end(), keygrip, _detail::ByKeyGrip()); const auto it = std::remove_if(range.first, range.second, [fpr](const Subkey &subkey) { return !qstricmp(fpr, subkey.parent().primaryFingerprint()); }); d->by.keygrip.erase(it, range.second); } } } void KeyCache::remove(const std::vector &keys) { for (const Key &key : keys) { remove(key); } } const std::vector &KeyCache::keys() const { d->ensureCachePopulated(); return d->by.fpr; } std::vector KeyCache::secretKeys() const { std::vector keys = this->keys(); keys.erase(std::remove_if(keys.begin(), keys.end(), [](const Key &key) { return !key.hasSecret(); }), keys.end()); return keys; } KeyGroup KeyCache::group(const QString &id) const { KeyGroup result{}; const auto it = std::find_if(std::cbegin(d->m_groups), std::cend(d->m_groups), [id](const auto &g) { return g.id() == id; }); if (it != std::cend(d->m_groups)) { result = *it; } return result; } std::vector KeyCache::groups() const { d->ensureCachePopulated(); return d->m_groups; } std::vector KeyCache::configurableGroups() const { std::vector groups; groups.reserve(d->m_groups.size()); std::copy_if(d->m_groups.cbegin(), d->m_groups.cend(), std::back_inserter(groups), [](const KeyGroup &group) { return group.source() == KeyGroup::ApplicationConfig; }); return groups; } namespace { bool compareById(const KeyGroup &lhs, const KeyGroup &rhs) { return lhs.id() < rhs.id(); } std::vector sortedById(std::vector groups) { std::sort(groups.begin(), groups.end(), &compareById); return groups; } } void KeyCache::saveConfigurableGroups(const std::vector &groups) { const std::vector oldGroups = sortedById(configurableGroups()); const std::vector newGroups = sortedById(groups); { std::vector removedGroups; std::set_difference(oldGroups.begin(), oldGroups.end(), newGroups.begin(), newGroups.end(), std::back_inserter(removedGroups), &compareById); for (const auto &group : std::as_const(removedGroups)) { qCDebug(LIBKLEO_LOG) << "Removing group" << group; d->remove(group); } } { std::vector updatedGroups; std::set_intersection(newGroups.begin(), newGroups.end(), oldGroups.begin(), oldGroups.end(), std::back_inserter(updatedGroups), &compareById); for (const auto &group : std::as_const(updatedGroups)) { qCDebug(LIBKLEO_LOG) << "Updating group" << group; d->update(group); } } { std::vector addedGroups; std::set_difference(newGroups.begin(), newGroups.end(), oldGroups.begin(), oldGroups.end(), std::back_inserter(addedGroups), &compareById); for (const auto &group : std::as_const(addedGroups)) { qCDebug(LIBKLEO_LOG) << "Adding group" << group; d->insert(group); } } Q_EMIT keysMayHaveChanged(); } bool KeyCache::insert(const KeyGroup &group) { if (!d->insert(group)) { return false; } Q_EMIT keysMayHaveChanged(); return true; } bool KeyCache::update(const KeyGroup &group) { if (!d->update(group)) { return false; } Q_EMIT keysMayHaveChanged(); return true; } bool KeyCache::remove(const KeyGroup &group) { if (!d->remove(group)) { return false; } Q_EMIT keysMayHaveChanged(); return true; } void KeyCache::refresh(const std::vector &keys) { // make this better... clear(); insert(keys); } void KeyCache::insert(const Key &key) { insert(std::vector(1, key)); } namespace { template class Op> class T1, template class Op> class T2> struct lexicographically { using result_type = bool; template bool operator()(const U &lhs, const V &rhs) const { return T1()(lhs, rhs) // || (T1()(lhs, rhs) && T2()(lhs, rhs)); } }; } void KeyCache::insert(const std::vector &keys) { // 1. remove those with empty fingerprints: std::vector sorted; sorted.reserve(keys.size()); std::remove_copy_if(keys.begin(), keys.end(), std::back_inserter(sorted), [](const Key &key) { auto fp = key.primaryFingerprint(); return !fp || !*fp; }); Q_FOREACH (const Key &key, sorted) { remove(key); // this is sub-optimal, but makes implementation from here on much easier } // 2. sort by fingerprint: std::sort(sorted.begin(), sorted.end(), _detail::ByFingerprint()); // 2a. insert into fpr index: std::vector by_fpr; by_fpr.reserve(sorted.size() + d->by.fpr.size()); std::merge(sorted.begin(), sorted.end(), d->by.fpr.begin(), d->by.fpr.end(), std::back_inserter(by_fpr), _detail::ByFingerprint()); // 3. build email index: std::vector> pairs; pairs.reserve(sorted.size()); for (const Key &key : std::as_const(sorted)) { const std::vector emails = ::emails(key); for (const std::string &e : emails) { pairs.push_back(std::make_pair(e, key)); } } std::sort(pairs.begin(), pairs.end(), ByEMail()); // 3a. insert into email index: std::vector> by_email; by_email.reserve(pairs.size() + d->by.email.size()); std::merge(pairs.begin(), pairs.end(), d->by.email.begin(), d->by.email.end(), std::back_inserter(by_email), ByEMail()); // 3.5: stable-sort by chain-id (effectively lexicographically) std::stable_sort(sorted.begin(), sorted.end(), _detail::ByChainID()); // 3.5a: insert into chain-id index: std::vector nonroot; nonroot.reserve(sorted.size()); std::vector by_chainid; by_chainid.reserve(sorted.size() + d->by.chainid.size()); std::copy_if(sorted.cbegin(), sorted.cend(), std::back_inserter(nonroot), [](const Key &key) { return !key.isRoot(); }); std::merge(nonroot.cbegin(), nonroot.cend(), d->by.chainid.cbegin(), d->by.chainid.cend(), std::back_inserter(by_chainid), lexicographically<_detail::ByChainID, _detail::ByFingerprint>()); // 4. sort by key id: std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID()); // 4a. insert into keyid index: std::vector by_keyid; by_keyid.reserve(sorted.size() + d->by.keyid.size()); std::merge(sorted.begin(), sorted.end(), d->by.keyid.begin(), d->by.keyid.end(), std::back_inserter(by_keyid), _detail::ByKeyID()); // 5. sort by short key id: std::sort(sorted.begin(), sorted.end(), _detail::ByShortKeyID()); // 5a. insert into short keyid index: std::vector by_shortkeyid; by_shortkeyid.reserve(sorted.size() + d->by.shortkeyid.size()); std::merge(sorted.begin(), sorted.end(), d->by.shortkeyid.begin(), d->by.shortkeyid.end(), std::back_inserter(by_shortkeyid), _detail::ByShortKeyID()); // 6. build subkey ID index: std::vector subkeys; subkeys.reserve(sorted.size()); for (const Key &key : std::as_const(sorted)) { const auto keySubkeys{key.subkeys()}; for (const Subkey &subkey : keySubkeys) { subkeys.push_back(subkey); } } // 6a sort by key id: std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyID()); // 6b. insert into subkey ID index: std::vector by_subkeyid; by_subkeyid.reserve(subkeys.size() + d->by.subkeyid.size()); std::merge(subkeys.begin(), subkeys.end(), d->by.subkeyid.begin(), d->by.subkeyid.end(), std::back_inserter(by_subkeyid), _detail::ByKeyID()); // 6c. sort by key grip std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyGrip()); // 6d. insert into subkey keygrip index: std::vector by_keygrip; by_keygrip.reserve(subkeys.size() + d->by.keygrip.size()); std::merge(subkeys.begin(), subkeys.end(), d->by.keygrip.begin(), d->by.keygrip.end(), std::back_inserter(by_keygrip), _detail::ByKeyGrip()); // now commit (well, we already removed keys...) by_fpr.swap(d->by.fpr); by_keyid.swap(d->by.keyid); by_shortkeyid.swap(d->by.shortkeyid); by_email.swap(d->by.email); by_subkeyid.swap(d->by.subkeyid); by_keygrip.swap(d->by.keygrip); by_chainid.swap(d->by.chainid); for (const Key &key : std::as_const(sorted)) { d->m_pgpOnly &= key.protocol() == GpgME::OpenPGP; Q_EMIT added(key); } Q_EMIT keysMayHaveChanged(); } void KeyCache::clear() { d->by = Private::By(); } // // // RefreshKeysJob // // class KeyCache::RefreshKeysJob::Private { RefreshKeysJob *const q; public: Private(KeyCache *cache, RefreshKeysJob *qq); void doStart(); Error startKeyListing(GpgME::Protocol protocol); void listAllKeysJobDone(const KeyListResult &res, const std::vector &nextKeys) { std::vector keys; keys.reserve(m_keys.size() + nextKeys.size()); if (m_keys.empty()) { keys = nextKeys; } else { std::merge(m_keys.begin(), m_keys.end(), nextKeys.begin(), nextKeys.end(), std::back_inserter(keys), _detail::ByFingerprint()); } m_keys.swap(keys); jobDone(res); } void emitDone(const KeyListResult &result); void updateKeyCache(); QPointer m_cache; QVector m_jobsPending; std::vector m_keys; KeyListResult m_mergedResult; bool m_canceled; private: void jobDone(const KeyListResult &res); }; KeyCache::RefreshKeysJob::Private::Private(KeyCache *cache, RefreshKeysJob *qq) : q(qq) , m_cache(cache) , m_canceled(false) { Q_ASSERT(m_cache); } void KeyCache::RefreshKeysJob::Private::jobDone(const KeyListResult &result) { if (m_canceled) { q->deleteLater(); return; } QObject *const sender = q->sender(); if (sender) { sender->disconnect(q); } Q_ASSERT(m_jobsPending.size() > 0); m_jobsPending.removeOne(qobject_cast(sender)); m_mergedResult.mergeWith(result); if (m_jobsPending.size() > 0) { return; } updateKeyCache(); emitDone(m_mergedResult); } void KeyCache::RefreshKeysJob::Private::emitDone(const KeyListResult &res) { q->deleteLater(); Q_EMIT q->done(res); } KeyCache::RefreshKeysJob::RefreshKeysJob(KeyCache *cache, QObject *parent) : QObject(parent) , d(new Private(cache, this)) { } KeyCache::RefreshKeysJob::~RefreshKeysJob() { delete d; } void KeyCache::RefreshKeysJob::start() { QTimer::singleShot(0, this, [this]() { d->doStart(); }); } void KeyCache::RefreshKeysJob::cancel() { d->m_canceled = true; std::for_each(d->m_jobsPending.begin(), d->m_jobsPending.end(), std::mem_fn(&QGpgME::ListAllKeysJob::slotCancel)); Q_EMIT canceled(); } void KeyCache::RefreshKeysJob::Private::doStart() { if (m_canceled) { q->deleteLater(); return; } Q_ASSERT(m_jobsPending.size() == 0); m_mergedResult.mergeWith(KeyListResult(startKeyListing(GpgME::OpenPGP))); m_mergedResult.mergeWith(KeyListResult(startKeyListing(GpgME::CMS))); if (m_jobsPending.size() != 0) { return; } const bool hasError = m_mergedResult.error() || m_mergedResult.error().isCanceled(); emitDone(hasError ? m_mergedResult : KeyListResult(Error(GPG_ERR_UNSUPPORTED_OPERATION))); } void KeyCache::RefreshKeysJob::Private::updateKeyCache() { if (!m_cache || m_canceled) { q->deleteLater(); return; } std::vector cachedKeys = m_cache->initialized() ? m_cache->keys() : std::vector(); std::sort(cachedKeys.begin(), cachedKeys.end(), _detail::ByFingerprint()); std::vector keysToRemove; std::set_difference(cachedKeys.begin(), cachedKeys.end(), m_keys.begin(), m_keys.end(), std::back_inserter(keysToRemove), _detail::ByFingerprint()); m_cache->remove(keysToRemove); m_cache->refresh(m_keys); } Error KeyCache::RefreshKeysJob::Private::startKeyListing(GpgME::Protocol proto) { const auto *const protocol = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); if (!protocol) { return Error(); } QGpgME::ListAllKeysJob *const job = protocol->listAllKeysJob(/*includeSigs*/ false, /*validate*/ true); if (!job) { return Error(); } #if 0 aheinecke: 2017.01.12: For unknown reasons the new style connect fails at runtime over library borders into QGpgME from the GpgME repo when cross compiled for Windows and default arguments are used in the Signal. This was tested with gcc 4.9 (Mingw 3.0.2) and we could not find an explanation for this. So until this is fixed or we understand the problem we need to use the old style connect for QGpgME signals. The new style connect of the canceled signal right below works fine. connect(job, &QGpgME::ListAllKeysJob::result, q, [this](const GpgME::KeyListResult &res, const std::vector &keys) { listAllKeysJobDone(res, keys); }); #endif connect(job, SIGNAL(result(GpgME::KeyListResult, std::vector)), q, SLOT(listAllKeysJobDone(GpgME::KeyListResult, std::vector))); connect(q, &RefreshKeysJob::canceled, job, &QGpgME::Job::slotCancel); // Only do this for initialized keycaches to avoid huge waits for // signature notations during initial keylisting. if (proto == GpgME::OpenPGP && m_cache->remarksEnabled() && m_cache->initialized()) { auto ctx = QGpgME::Job::context(job); if (ctx) { ctx->addKeyListMode(KeyListMode::Signatures | KeyListMode::SignatureNotations); } } const Error error = job->start(true); if (!error && !error.isCanceled()) { m_jobsPending.push_back(job); } return error; } bool KeyCache::initialized() const { return d->m_initalized; } void KeyCache::Private::ensureCachePopulated() const { if (!m_initalized) { q->startKeyListing(); QEventLoop loop; loop.connect(q, &KeyCache::keyListingDone, &loop, &QEventLoop::quit); qCDebug(LIBKLEO_LOG) << "Waiting for keycache."; loop.exec(); qCDebug(LIBKLEO_LOG) << "Keycache available."; } } bool KeyCache::pgpOnly() const { return d->m_pgpOnly; } static bool keyIsOk(const Key &k) { return !k.isExpired() && !k.isRevoked() && !k.isInvalid() && !k.isDisabled(); } static bool uidIsOk(const UserID &uid) { return keyIsOk(uid.parent()) && !uid.isRevoked() && !uid.isInvalid(); } static bool subkeyIsOk(const Subkey &s) { return !s.isRevoked() && !s.isInvalid() && !s.isDisabled(); } namespace { time_t creationTimeOfNewestSuitableSubKey(const Key &key, KeyCache::KeyUsage usage) { time_t creationTime = 0; for (const Subkey &s : key.subkeys()) { if (!subkeyIsOk(s)) { continue; } if (usage == KeyCache::KeyUsage::Sign && !s.canSign()) { continue; } if (usage == KeyCache::KeyUsage::Encrypt && !s.canEncrypt()) { continue; } if (s.creationTime() > creationTime) { creationTime = s.creationTime(); } } return creationTime; } struct BestMatch { Key key; UserID uid; time_t creationTime = 0; }; } GpgME::Key KeyCache::findBestByMailBox(const char *addr, GpgME::Protocol proto, KeyUsage usage) const { d->ensureCachePopulated(); if (!addr) { return {}; } // support lookup of email addresses enclosed in angle brackets QByteArray address(addr); if (address[0] == '<' && address[address.size() - 1] == '>') { address = address.mid(1, address.size() - 2); } address = address.toLower(); BestMatch best; for (const Key &k : findByEMailAddress(address.constData())) { if (proto != Protocol::UnknownProtocol && k.protocol() != proto) { continue; } if (usage == KeyUsage::Encrypt && !k.canEncrypt()) { continue; } if (usage == KeyUsage::Sign && (!k.canSign() || !k.hasSecret())) { continue; } const time_t creationTime = creationTimeOfNewestSuitableSubKey(k, usage); if (creationTime == 0) { // key does not have a suitable (and usable) subkey continue; } for (const UserID &u : k.userIDs()) { if (QByteArray::fromStdString(u.addrSpec()).toLower() != address) { // user ID does not match the given email address continue; } if (best.uid.isNull()) { // we have found our first candidate best = {k, u, creationTime}; } else if (!uidIsOk(best.uid) && uidIsOk(u)) { // validity of the new key is better best = {k, u, creationTime}; } else if (!k.isExpired() && best.uid.validity() < u.validity()) { // validity of the new key is better best = {k, u, creationTime}; } else if (best.key.isExpired() && !k.isExpired()) { // validity of the new key is better best = {k, u, creationTime}; } else if (best.uid.validity() == u.validity() && uidIsOk(u) && best.creationTime < creationTime) { // both keys/user IDs have same validity, but the new key is newer best = {k, u, creationTime}; } } } return best.key; } namespace { template bool allKeysAllowUsage(const T &keys, KeyCache::KeyUsage usage) { switch (usage) { case KeyCache::KeyUsage::AnyUsage: return true; case KeyCache::KeyUsage::Sign: return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::canSign)); case KeyCache::KeyUsage::Encrypt: return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::canEncrypt)); case KeyCache::KeyUsage::Certify: return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::canCertify)); case KeyCache::KeyUsage::Authenticate: return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::canAuthenticate)); } qCDebug(LIBKLEO_LOG) << __func__ << "called with invalid usage" << int(usage); return false; } template bool allKeysHaveProtocol(const T &keys, Protocol protocol) { return std::all_of(std::begin(keys), std::end(keys), [protocol](const auto &key) { return key.protocol() == protocol; }); } } KeyGroup KeyCache::findGroup(const QString &name, Protocol protocol, KeyUsage usage) const { d->ensureCachePopulated(); Q_ASSERT(usage == KeyUsage::Sign || usage == KeyUsage::Encrypt); for (const auto &group : std::as_const(d->m_groups)) { if (group.name() == name) { const KeyGroup::Keys &keys = group.keys(); if (allKeysAllowUsage(keys, usage) && (protocol == UnknownProtocol || allKeysHaveProtocol(keys, protocol))) { return group; } } } return {}; } std::vector KeyCache::getGroupKeys(const QString &groupName) const { std::vector result; for (const KeyGroup &g : std::as_const(d->m_groups)) { if (g.name() == groupName) { const KeyGroup::Keys &keys = g.keys(); std::copy(keys.cbegin(), keys.cend(), std::back_inserter(result)); } } _detail::sort_by_fpr(result); _detail::remove_duplicates_by_fpr(result); return result; } void KeyCache::setKeys(const std::vector &keys) { // disable regular key listing and cancel running key listing setRefreshInterval(0); cancelKeyListing(); clear(); insert(keys); d->m_initalized = true; Q_EMIT keyListingDone(KeyListResult()); } void KeyCache::setGroups(const std::vector &groups) { Q_ASSERT(d->m_initalized && "Call setKeys() before setting groups"); d->m_groups = groups; Q_EMIT keysMayHaveChanged(); } #include "moc_keycache.cpp" #include "moc_keycache_p.cpp" diff --git a/src/models/keycache.h b/src/models/keycache.h index cf002cdd9..5e61ae48c 100644 --- a/src/models/keycache.h +++ b/src/models/keycache.h @@ -1,220 +1,220 @@ /* -*- mode: c++; c-basic-offset:4 -*- models/keycache.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include - #include "kleo_export.h" +#include + #include #include #include #include namespace GpgME { class Key; class DecryptionResult; class VerificationResult; class KeyListResult; class Subkey; } namespace KMime { namespace Types { class Mailbox; } } namespace Kleo { class FileSystemWatcher; class KeyGroup; class KeyGroupConfig; class KeyCacheAutoRefreshSuspension; class KLEO_EXPORT KeyCache : public QObject { Q_OBJECT protected: explicit KeyCache(); public: enum class KeyUsage { AnyUsage, Sign, Encrypt, Certify, Authenticate, }; static std::shared_ptr instance(); static std::shared_ptr mutableInstance(); ~KeyCache() override; void setGroupsEnabled(bool enabled); void setGroupConfig(const std::shared_ptr &groupConfig); void insert(const GpgME::Key &key); void insert(const std::vector &keys); bool insert(const KeyGroup &group); void refresh(const std::vector &keys); bool update(const KeyGroup &group); void remove(const GpgME::Key &key); void remove(const std::vector &keys); bool remove(const KeyGroup &group); void addFileSystemWatcher(const std::shared_ptr &watcher); void enableFileSystemWatcher(bool enable); void setRefreshInterval(int hours); int refreshInterval() const; std::shared_ptr suspendAutoRefresh(); void enableRemarks(bool enable); bool remarksEnabled() const; const std::vector &keys() const; std::vector secretKeys() const; KeyGroup group(const QString &id) const; std::vector groups() const; std::vector configurableGroups() const; void saveConfigurableGroups(const std::vector &groups); const GpgME::Key &findByFingerprint(const char *fpr) const; const GpgME::Key &findByFingerprint(const std::string &fpr) const; std::vector findByFingerprint(const std::vector &fprs) const; std::vector findByEMailAddress(const char *email) const; std::vector findByEMailAddress(const std::string &email) const; /** Look through the cache and search for the best key for a mailbox. * * The best key is the key with a UID for the provided mailbox that * has the highest validity and a subkey that is capable for the given * usage. * If more then one key have a UID with the same validity * the most recently created key is taken. * * @returns the "best" key for the mailbox. */ GpgME::Key findBestByMailBox(const char *addr, GpgME::Protocol proto, KeyUsage usage) const; /** * Looks for a group named @a name which contains keys with protocol @a protocol * that are suitable for the usage @a usage. * * If @a protocol is GpgME::OpenPGP or GpgME::CMS, then only groups consisting of keys * matching this protocol are considered. Use @a protocol GpgME::UnknownProtocol to consider * any groups regardless of the protocol including mixed-protocol groups. * * If @a usage is not KeyUsage::AnyUsage, then only groups consisting of keys supporting this usage * are considered. * The validity of keys and the presence of a private key (necessary for signing, certification, and * authentication) is not taken into account. * * The first group that fulfills all conditions is returned. * * @returns a matching group or a null group if no matching group is found. */ KeyGroup findGroup(const QString &name, GpgME::Protocol protocol, KeyUsage usage) const; const GpgME::Key &findByShortKeyID(const char *id) const; const GpgME::Key &findByShortKeyID(const std::string &id) const; const GpgME::Key &findByKeyIDOrFingerprint(const char *id) const; const GpgME::Key &findByKeyIDOrFingerprint(const std::string &id) const; std::vector findByKeyIDOrFingerprint(const std::vector &ids) const; const GpgME::Subkey &findSubkeyByKeyGrip(const char *grip, GpgME::Protocol protocol = GpgME::UnknownProtocol) const; const GpgME::Subkey &findSubkeyByKeyGrip(const std::string &grip, GpgME::Protocol protocol = GpgME::UnknownProtocol) const; std::vector findSubkeysByKeyID(const std::vector &ids) const; std::vector findRecipients(const GpgME::DecryptionResult &result) const; std::vector findSigners(const GpgME::VerificationResult &result) const; std::vector findSigningKeysByMailbox(const QString &mb) const; std::vector findEncryptionKeysByMailbox(const QString &mb) const; /** Check for group keys. * * @returns A list of keys configured for groupName. Empty if no group cached.*/ std::vector getGroupKeys(const QString &groupName) const; enum Option { // clang-format off NoOption = 0, RecursiveSearch = 1, IncludeSubject = 2, // clang-format on }; Q_DECLARE_FLAGS(Options, Option) std::vector findSubjects(const GpgME::Key &key, Options option = RecursiveSearch) const; std::vector findSubjects(const std::vector &keys, Options options = RecursiveSearch) const; std::vector findSubjects(std::vector::const_iterator first, std::vector::const_iterator last, Options options = RecursiveSearch) const; std::vector findIssuers(const GpgME::Key &key, Options options = RecursiveSearch) const; /** Check if at least one keylisting was finished. */ bool initialized() const; /** Check if all keys have OpenPGP Protocol. */ bool pgpOnly() const; /** Set the keys the cache shall contain. Marks cache as initialized. Use for tests only. */ void setKeys(const std::vector &keys); void setGroups(const std::vector &groups); public Q_SLOTS: void clear(); void startKeyListing(GpgME::Protocol proto = GpgME::UnknownProtocol) { reload(proto); } void reload(GpgME::Protocol proto = GpgME::UnknownProtocol); void cancelKeyListing(); Q_SIGNALS: // void changed( const GpgME::Key & key ); void aboutToRemove(const GpgME::Key &key); void added(const GpgME::Key &key); void keyListingDone(const GpgME::KeyListResult &result); void keysMayHaveChanged(); void groupAdded(const Kleo::KeyGroup &group); void groupUpdated(const Kleo::KeyGroup &group); void groupRemoved(const Kleo::KeyGroup &group); private: class RefreshKeysJob; class Private; QScopedPointer const d; }; } Q_DECLARE_OPERATORS_FOR_FLAGS(Kleo::KeyCache::Options) diff --git a/src/models/keylistmodel.cpp b/src/models/keylistmodel.cpp index 9c9bf529c..e2d99ca77 100644 --- a/src/models/keylistmodel.cpp +++ b/src/models/keylistmodel.cpp @@ -1,1595 +1,1595 @@ /* -*- mode: c++; c-basic-offset:4 -*- models/keylistmodel.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keylistmodel.h" #include "keycache.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include + +#include #ifdef KLEO_MODEL_TEST #include #endif - -#include - #include #include #include #include #include + #include #ifndef Q_MOC_RUN // QTBUG-22829 #include #include #endif #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace Kleo::KeyList; Q_DECLARE_METATYPE(GpgME::Key) Q_DECLARE_METATYPE(KeyGroup) class AbstractKeyListModel::Private { AbstractKeyListModel *const q; public: explicit Private(AbstractKeyListModel *qq); void updateFromKeyCache(); QString getEMail(const Key &key) const; public: int m_toolTipOptions = Formatting::Validity; mutable QHash prettyEMailCache; mutable QHash remarksCache; bool m_useKeyCache = false; bool m_modelResetInProgress = false; KeyList::Options m_keyListOptions = AllKeys; std::vector m_remarkKeys; }; AbstractKeyListModel::Private::Private(Kleo::AbstractKeyListModel *qq) : q(qq) { } void AbstractKeyListModel::Private::updateFromKeyCache() { if (m_useKeyCache) { q->setKeys(m_keyListOptions == SecretKeysOnly ? KeyCache::instance()->secretKeys() : KeyCache::instance()->keys()); if (m_keyListOptions == IncludeGroups) { q->setGroups(KeyCache::instance()->groups()); } } } QString AbstractKeyListModel::Private::getEMail(const Key &key) const { QString email; if (const auto fpr = key.primaryFingerprint()) { const auto it = prettyEMailCache.constFind(fpr); if (it != prettyEMailCache.constEnd()) { email = *it; } else { email = Formatting::prettyEMail(key); prettyEMailCache[fpr] = email; } } return email; } AbstractKeyListModel::AbstractKeyListModel(QObject *p) : QAbstractItemModel(p) , KeyListModelInterface() , d(new Private(this)) { connect(this, &QAbstractItemModel::modelAboutToBeReset, this, [this]() { d->m_modelResetInProgress = true; }); connect(this, &QAbstractItemModel::modelReset, this, [this]() { d->m_modelResetInProgress = false; }); } AbstractKeyListModel::~AbstractKeyListModel() { } void AbstractKeyListModel::setToolTipOptions(int opts) { d->m_toolTipOptions = opts; } int AbstractKeyListModel::toolTipOptions() const { return d->m_toolTipOptions; } void AbstractKeyListModel::setRemarkKeys(const std::vector &keys) { d->m_remarkKeys = keys; } std::vector AbstractKeyListModel::remarkKeys() const { return d->m_remarkKeys; } Key AbstractKeyListModel::key(const QModelIndex &idx) const { Key key = Key::null; if (idx.isValid()) { key = doMapToKey(idx); } return key; } std::vector AbstractKeyListModel::keys(const QList &indexes) const { std::vector result; result.reserve(indexes.size()); std::transform(indexes.begin(), // indexes.end(), std::back_inserter(result), [this](const QModelIndex &idx) { return this->key(idx); }); result.erase(std::remove_if(result.begin(), result.end(), std::mem_fn(&GpgME::Key::isNull)), result.end()); _detail::remove_duplicates_by_fpr(result); return result; } KeyGroup AbstractKeyListModel::group(const QModelIndex &idx) const { if (idx.isValid()) { return doMapToGroup(idx); } else { return KeyGroup(); } } QModelIndex AbstractKeyListModel::index(const Key &key) const { return index(key, 0); } QModelIndex AbstractKeyListModel::index(const Key &key, int col) const { if (key.isNull() || col < 0 || col >= NumColumns) { return {}; } else { return doMapFromKey(key, col); } } QList AbstractKeyListModel::indexes(const std::vector &keys) const { QList result; result.reserve(keys.size()); std::transform(keys.begin(), // keys.end(), std::back_inserter(result), [this](const Key &key) { return this->index(key); }); return result; } QModelIndex AbstractKeyListModel::index(const KeyGroup &group) const { return index(group, 0); } QModelIndex AbstractKeyListModel::index(const KeyGroup &group, int col) const { if (group.isNull() || col < 0 || col >= NumColumns) { return {}; } else { return doMapFromGroup(group, col); } } void AbstractKeyListModel::setKeys(const std::vector &keys) { beginResetModel(); clear(Keys); addKeys(keys); endResetModel(); } QModelIndex AbstractKeyListModel::addKey(const Key &key) { const std::vector vec(1, key); const QList l = doAddKeys(vec); return l.empty() ? QModelIndex() : l.front(); } void AbstractKeyListModel::removeKey(const Key &key) { if (key.isNull()) { return; } doRemoveKey(key); d->prettyEMailCache.remove(key.primaryFingerprint()); d->remarksCache.remove(key.primaryFingerprint()); } QList AbstractKeyListModel::addKeys(const std::vector &keys) { std::vector sorted; sorted.reserve(keys.size()); std::remove_copy_if(keys.begin(), keys.end(), std::back_inserter(sorted), std::mem_fn(&Key::isNull)); std::sort(sorted.begin(), sorted.end(), _detail::ByFingerprint()); return doAddKeys(sorted); } void AbstractKeyListModel::setGroups(const std::vector &groups) { beginResetModel(); clear(Groups); doSetGroups(groups); endResetModel(); } QModelIndex AbstractKeyListModel::addGroup(const KeyGroup &group) { if (group.isNull()) { return QModelIndex(); } return doAddGroup(group); } bool AbstractKeyListModel::removeGroup(const KeyGroup &group) { if (group.isNull()) { return false; } return doRemoveGroup(group); } void AbstractKeyListModel::clear(ItemTypes types) { const bool inReset = modelResetInProgress(); if (!inReset) { beginResetModel(); } doClear(types); if (types & Keys) { d->prettyEMailCache.clear(); d->remarksCache.clear(); } if (!inReset) { endResetModel(); } } int AbstractKeyListModel::columnCount(const QModelIndex &) const { return NumColumns; } QVariant AbstractKeyListModel::headerData(int section, Qt::Orientation o, int role) const { if (o == Qt::Horizontal) { if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::ToolTipRole) { switch (section) { case PrettyName: return i18n("Name"); case PrettyEMail: return i18n("E-Mail"); case Validity: return i18n("User-IDs"); case ValidFrom: return i18n("Valid From"); case ValidUntil: return i18n("Valid Until"); case TechnicalDetails: return i18n("Protocol"); case ShortKeyID: return i18n("Key-ID"); case KeyID: return i18n("Key-ID"); case Fingerprint: return i18n("Fingerprint"); case Issuer: return i18n("Issuer"); case SerialNumber: return i18n("Serial Number"); case Origin: return i18n("Origin"); case LastUpdate: return i18n("Last Update"); case OwnerTrust: return i18n("Certification Trust"); case Remarks: return i18n("Tags"); case NumColumns:; } } } return QVariant(); } static QVariant returnIfValid(const QColor &t) { if (t.isValid()) { return t; } else { return QVariant(); } } static QVariant returnIfValid(const QIcon &t) { if (!t.isNull()) { return t; } else { return QVariant(); } } QVariant AbstractKeyListModel::data(const QModelIndex &index, int role) const { const Key key = this->key(index); if (!key.isNull()) { return data(key, index.column(), role); } const KeyGroup group = this->group(index); if (!group.isNull()) { return data(group, index.column(), role); } return QVariant(); } QVariant AbstractKeyListModel::data(const Key &key, int column, int role) const { if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::AccessibleTextRole) { switch (column) { case PrettyName: { const auto name = Formatting::prettyName(key); if (role == Qt::AccessibleTextRole) { return name.isEmpty() ? i18nc("text for screen readers for an empty name", "no name") : name; } return name; } case PrettyEMail: { const auto email = d->getEMail(key); if (role == Qt::AccessibleTextRole) { return email.isEmpty() ? i18nc("text for screen readers for an empty email address", "no email") : email; } return email; } case Validity: return Formatting::complianceStringShort(key); case ValidFrom: if (role == Qt::EditRole) { return Formatting::creationDate(key); } else if (role == Qt::AccessibleTextRole) { return Formatting::accessibleCreationDate(key); } else { return Formatting::creationDateString(key); } case ValidUntil: if (role == Qt::EditRole) { return Formatting::expirationDate(key); } else if (role == Qt::AccessibleTextRole) { return Formatting::accessibleExpirationDate(key); } else { return Formatting::expirationDateString(key); } case TechnicalDetails: return Formatting::type(key); case ShortKeyID: if (role == Qt::AccessibleTextRole) { return Formatting::accessibleHexID(key.shortKeyID()); } else { return Formatting::prettyID(key.shortKeyID()); } case KeyID: if (role == Qt::AccessibleTextRole) { return Formatting::accessibleHexID(key.keyID()); } else { return Formatting::prettyID(key.keyID()); } case Summary: return Formatting::summaryLine(key); case Fingerprint: if (role == Qt::AccessibleTextRole) { return Formatting::accessibleHexID(key.primaryFingerprint()); } else { return Formatting::prettyID(key.primaryFingerprint()); } case Issuer: return QString::fromUtf8(key.issuerName()); case Origin: return Formatting::origin(key.origin()); case LastUpdate: if (role == Qt::AccessibleTextRole) { return Formatting::accessibleDate(key.lastUpdate()); } else { return Formatting::dateString(key.lastUpdate()); } case SerialNumber: return QString::fromUtf8(key.issuerSerial()); case OwnerTrust: return Formatting::ownerTrustShort(key.ownerTrust()); case Remarks: { const char *const fpr = key.primaryFingerprint(); if (fpr && key.protocol() == GpgME::OpenPGP && key.numUserIDs() && d->m_remarkKeys.size()) { if (!(key.keyListMode() & GpgME::SignatureNotations)) { return i18n("Loading..."); } const QHash::const_iterator it = d->remarksCache.constFind(fpr); if (it != d->remarksCache.constEnd()) { return *it; } else { GpgME::Error err; const auto remarks = key.userID(0).remarks(d->m_remarkKeys, err); if (remarks.size() == 1) { const auto remark = QString::fromStdString(remarks[0]); return d->remarksCache[fpr] = remark; } else { QStringList remarkList; remarkList.reserve(remarks.size()); for (const auto &rem : remarks) { remarkList << QString::fromStdString(rem); } const auto remark = remarkList.join(QStringLiteral("; ")); return d->remarksCache[fpr] = remark; } } } else { return QVariant(); } } return QVariant(); case NumColumns: break; } } else if (role == Qt::ToolTipRole) { return Formatting::toolTip(key, toolTipOptions()); } else if (role == Qt::FontRole) { return KeyFilterManager::instance()->font(key, (column == ShortKeyID || column == KeyID || column == Fingerprint) ? QFont(QStringLiteral("monospace")) : QFont()); } else if (role == Qt::DecorationRole) { return column == Icon ? returnIfValid(KeyFilterManager::instance()->icon(key)) : QVariant(); } else if (role == Qt::BackgroundRole) { return returnIfValid(KeyFilterManager::instance()->bgColor(key)); } else if (role == Qt::ForegroundRole) { return returnIfValid(KeyFilterManager::instance()->fgColor(key)); } else if (role == FingerprintRole) { return QString::fromLatin1(key.primaryFingerprint()); } else if (role == KeyRole) { return QVariant::fromValue(key); } return QVariant(); } QVariant AbstractKeyListModel::data(const KeyGroup &group, int column, int role) const { if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::AccessibleTextRole) { switch (column) { case PrettyName: return group.name(); case Validity: return Formatting::complianceStringShort(group); case TechnicalDetails: return Formatting::type(group); case Summary: return Formatting::summaryLine(group); // used for filtering case PrettyEMail: case ValidFrom: case ValidUntil: case ShortKeyID: case KeyID: case Fingerprint: case Issuer: case Origin: case LastUpdate: case SerialNumber: case OwnerTrust: case Remarks: if (role == Qt::AccessibleTextRole) { return i18nc("text for screen readers", "not applicable"); } break; case NumColumns: break; } } else if (role == Qt::ToolTipRole) { return Formatting::toolTip(group, toolTipOptions()); } else if (role == Qt::FontRole) { return QFont(); } else if (role == Qt::DecorationRole) { return column == Icon ? QIcon::fromTheme(QStringLiteral("group")) : QVariant(); } else if (role == Qt::BackgroundRole) { } else if (role == Qt::ForegroundRole) { } else if (role == GroupRole) { return QVariant::fromValue(group); } return QVariant(); } bool AbstractKeyListModel::setData(const QModelIndex &index, const QVariant &value, int role) { Q_UNUSED(role) Q_ASSERT(value.canConvert()); if (value.canConvert()) { const KeyGroup group = value.value(); return doSetGroupData(index, group); } return false; } bool AbstractKeyListModel::modelResetInProgress() { return d->m_modelResetInProgress; } namespace { template class TableModelMixin : public Base { public: explicit TableModelMixin(QObject *p = nullptr) : Base(p) { } ~TableModelMixin() override { } using Base::index; QModelIndex index(int row, int column, const QModelIndex &pidx = QModelIndex()) const override { return this->hasIndex(row, column, pidx) ? this->createIndex(row, column, nullptr) : QModelIndex(); } private: QModelIndex parent(const QModelIndex &) const override { return QModelIndex(); } bool hasChildren(const QModelIndex &pidx) const override { return (pidx.model() == this || !pidx.isValid()) && this->rowCount(pidx) > 0 && this->columnCount(pidx) > 0; } }; class FlatKeyListModel #ifndef Q_MOC_RUN : public TableModelMixin #else : public AbstractKeyListModel #endif { Q_OBJECT public: explicit FlatKeyListModel(QObject *parent = nullptr); ~FlatKeyListModel() override; int rowCount(const QModelIndex &pidx) const override { return pidx.isValid() ? 0 : mKeysByFingerprint.size() + mGroups.size(); } private: Key doMapToKey(const QModelIndex &index) const override; QModelIndex doMapFromKey(const Key &key, int col) const override; QList doAddKeys(const std::vector &keys) override; void doRemoveKey(const Key &key) override; KeyGroup doMapToGroup(const QModelIndex &index) const override; QModelIndex doMapFromGroup(const KeyGroup &group, int column) const override; void doSetGroups(const std::vector &groups) override; QModelIndex doAddGroup(const KeyGroup &group) override; bool doSetGroupData(const QModelIndex &index, const KeyGroup &group) override; bool doRemoveGroup(const KeyGroup &group) override; void doClear(ItemTypes types) override { if (types & Keys) { mKeysByFingerprint.clear(); } if (types & Groups) { mGroups.clear(); } } int firstGroupRow() const { return mKeysByFingerprint.size(); } int lastGroupRow() const { return mKeysByFingerprint.size() + mGroups.size() - 1; } int groupIndex(const QModelIndex &index) const { if (!index.isValid() || index.row() < firstGroupRow() || index.row() > lastGroupRow() || index.column() >= NumColumns) { return -1; } return index.row() - firstGroupRow(); } private: std::vector mKeysByFingerprint; std::vector mGroups; }; class HierarchicalKeyListModel : public AbstractKeyListModel { Q_OBJECT public: explicit HierarchicalKeyListModel(QObject *parent = nullptr); ~HierarchicalKeyListModel() override; int rowCount(const QModelIndex &pidx) const override; using AbstractKeyListModel::index; QModelIndex index(int row, int col, const QModelIndex &pidx) const override; QModelIndex parent(const QModelIndex &idx) const override; bool hasChildren(const QModelIndex &pidx) const override { return rowCount(pidx) > 0; } private: Key doMapToKey(const QModelIndex &index) const override; QModelIndex doMapFromKey(const Key &key, int col) const override; QList doAddKeys(const std::vector &keys) override; void doRemoveKey(const Key &key) override; KeyGroup doMapToGroup(const QModelIndex &index) const override; QModelIndex doMapFromGroup(const KeyGroup &group, int column) const override; void doSetGroups(const std::vector &groups) override; QModelIndex doAddGroup(const KeyGroup &group) override; bool doSetGroupData(const QModelIndex &index, const KeyGroup &group) override; bool doRemoveGroup(const KeyGroup &group) override; void doClear(ItemTypes types) override; int firstGroupRow() const { return mTopLevels.size(); } int lastGroupRow() const { return mTopLevels.size() + mGroups.size() - 1; } int groupIndex(const QModelIndex &index) const { if (!index.isValid() || index.row() < firstGroupRow() || index.row() > lastGroupRow() || index.column() >= NumColumns) { return -1; } return index.row() - firstGroupRow(); } private: void addTopLevelKey(const Key &key); void addKeyWithParent(const char *issuer_fpr, const Key &key); void addKeyWithoutParent(const char *issuer_fpr, const Key &key); private: typedef std::map> Map; std::vector mKeysByFingerprint; // all keys Map mKeysByExistingParent, mKeysByNonExistingParent; // parent->child map std::vector mTopLevels; // all roots + parent-less std::vector mGroups; }; class Issuers { Issuers() { } public: static Issuers *instance() { static auto self = std::unique_ptr{new Issuers{}}; return self.get(); } const char *cleanChainID(const Key &key) const { const char *chainID = ""; if (!key.isRoot()) { const char *const chid = key.chainID(); if (chid && mKeysWithMaskedIssuer.find(key) == std::end(mKeysWithMaskedIssuer)) { chainID = chid; } } return chainID; } void maskIssuerOfKey(const Key &key) { mKeysWithMaskedIssuer.insert(key); } void clear() { mKeysWithMaskedIssuer.clear(); } private: std::set> mKeysWithMaskedIssuer; }; static const char *cleanChainID(const Key &key) { return Issuers::instance()->cleanChainID(key); } } FlatKeyListModel::FlatKeyListModel(QObject *p) : TableModelMixin(p) { } FlatKeyListModel::~FlatKeyListModel() { } Key FlatKeyListModel::doMapToKey(const QModelIndex &idx) const { Q_ASSERT(idx.isValid()); if (static_cast(idx.row()) < mKeysByFingerprint.size() && idx.column() < NumColumns) { return mKeysByFingerprint[idx.row()]; } else { return Key::null; } } QModelIndex FlatKeyListModel::doMapFromKey(const Key &key, int col) const { Q_ASSERT(!key.isNull()); const std::vector::const_iterator it = std::lower_bound(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), key, _detail::ByFingerprint()); if (it == mKeysByFingerprint.end() || !_detail::ByFingerprint()(*it, key)) { return {}; } else { return createIndex(it - mKeysByFingerprint.begin(), col); } } QList FlatKeyListModel::doAddKeys(const std::vector &keys) { Q_ASSERT(std::is_sorted(keys.begin(), keys.end(), _detail::ByFingerprint())); if (keys.empty()) { return QList(); } for (auto it = keys.begin(), end = keys.end(); it != end; ++it) { // find an insertion point: const std::vector::iterator pos = std::upper_bound(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), *it, _detail::ByFingerprint()); const unsigned int idx = std::distance(mKeysByFingerprint.begin(), pos); if (idx > 0 && qstrcmp(mKeysByFingerprint[idx - 1].primaryFingerprint(), it->primaryFingerprint()) == 0) { // key existed before - replace with new one: mKeysByFingerprint[idx - 1] = *it; if (!modelResetInProgress()) { Q_EMIT dataChanged(createIndex(idx - 1, 0), createIndex(idx - 1, NumColumns - 1)); } } else { // new key - insert: if (!modelResetInProgress()) { beginInsertRows(QModelIndex(), idx, idx); } mKeysByFingerprint.insert(pos, *it); if (!modelResetInProgress()) { endInsertRows(); } } } return indexes(keys); } void FlatKeyListModel::doRemoveKey(const Key &key) { const std::vector::iterator it = Kleo::binary_find(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), key, _detail::ByFingerprint()); if (it == mKeysByFingerprint.end()) { return; } const unsigned int row = std::distance(mKeysByFingerprint.begin(), it); if (!modelResetInProgress()) { beginRemoveRows(QModelIndex(), row, row); } mKeysByFingerprint.erase(it); if (!modelResetInProgress()) { endRemoveRows(); } } KeyGroup FlatKeyListModel::doMapToGroup(const QModelIndex &idx) const { Q_ASSERT(idx.isValid()); if (static_cast(idx.row()) >= mKeysByFingerprint.size() && static_cast(idx.row()) < mKeysByFingerprint.size() + mGroups.size() && idx.column() < NumColumns) { return mGroups[idx.row() - mKeysByFingerprint.size()]; } else { return KeyGroup(); } } QModelIndex FlatKeyListModel::doMapFromGroup(const KeyGroup &group, int column) const { Q_ASSERT(!group.isNull()); const auto it = std::find_if(mGroups.cbegin(), mGroups.cend(), [group](const KeyGroup &g) { return g.source() == group.source() && g.id() == group.id(); }); if (it == mGroups.cend()) { return QModelIndex(); } else { return createIndex(it - mGroups.cbegin() + mKeysByFingerprint.size(), column); } } void FlatKeyListModel::doSetGroups(const std::vector &groups) { Q_ASSERT(mGroups.empty()); // ensure that groups have been cleared const int first = mKeysByFingerprint.size(); const int last = first + groups.size() - 1; if (!modelResetInProgress()) { beginInsertRows(QModelIndex(), first, last); } mGroups = groups; if (!modelResetInProgress()) { endInsertRows(); } } QModelIndex FlatKeyListModel::doAddGroup(const KeyGroup &group) { const int newRow = lastGroupRow() + 1; if (!modelResetInProgress()) { beginInsertRows(QModelIndex(), newRow, newRow); } mGroups.push_back(group); if (!modelResetInProgress()) { endInsertRows(); } return createIndex(newRow, 0); } bool FlatKeyListModel::doSetGroupData(const QModelIndex &index, const KeyGroup &group) { if (group.isNull()) { return false; } const int groupIndex = this->groupIndex(index); if (groupIndex == -1) { return false; } mGroups[groupIndex] = group; if (!modelResetInProgress()) { Q_EMIT dataChanged(createIndex(index.row(), 0), createIndex(index.row(), NumColumns - 1)); } return true; } bool FlatKeyListModel::doRemoveGroup(const KeyGroup &group) { const QModelIndex modelIndex = doMapFromGroup(group, 0); if (!modelIndex.isValid()) { return false; } const int groupIndex = this->groupIndex(modelIndex); Q_ASSERT(groupIndex != -1); if (groupIndex == -1) { return false; } if (!modelResetInProgress()) { beginRemoveRows(QModelIndex(), modelIndex.row(), modelIndex.row()); } mGroups.erase(mGroups.begin() + groupIndex); if (!modelResetInProgress()) { endRemoveRows(); } return true; } HierarchicalKeyListModel::HierarchicalKeyListModel(QObject *p) : AbstractKeyListModel(p) , mKeysByFingerprint() , mKeysByExistingParent() , mKeysByNonExistingParent() , mTopLevels() { } HierarchicalKeyListModel::~HierarchicalKeyListModel() { } int HierarchicalKeyListModel::rowCount(const QModelIndex &pidx) const { // toplevel item: if (!pidx.isValid()) { return mTopLevels.size() + mGroups.size(); } if (pidx.column() != 0) { return 0; } // non-toplevel item - find the number of subjects for this issuer: const Key issuer = this->key(pidx); const char *const fpr = issuer.primaryFingerprint(); if (!fpr || !*fpr) { return 0; } const Map::const_iterator it = mKeysByExistingParent.find(fpr); if (it == mKeysByExistingParent.end()) { return 0; } return it->second.size(); } QModelIndex HierarchicalKeyListModel::index(int row, int col, const QModelIndex &pidx) const { if (row < 0 || col < 0 || col >= NumColumns) { return {}; } // toplevel item: if (!pidx.isValid()) { if (static_cast(row) < mTopLevels.size()) { return index(mTopLevels[row], col); } else if (static_cast(row) < mTopLevels.size() + mGroups.size()) { return index(mGroups[row - mTopLevels.size()], col); } else { return QModelIndex(); } } // non-toplevel item - find the row'th subject of this key: const Key issuer = this->key(pidx); const char *const fpr = issuer.primaryFingerprint(); if (!fpr || !*fpr) { return QModelIndex(); } const Map::const_iterator it = mKeysByExistingParent.find(fpr); if (it == mKeysByExistingParent.end() || static_cast(row) >= it->second.size()) { return QModelIndex(); } return index(it->second[row], col); } QModelIndex HierarchicalKeyListModel::parent(const QModelIndex &idx) const { const Key key = this->key(idx); if (key.isNull() || key.isRoot()) { return {}; } const std::vector::const_iterator it = Kleo::binary_find(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), cleanChainID(key), _detail::ByFingerprint()); return it != mKeysByFingerprint.end() ? index(*it) : QModelIndex(); } Key HierarchicalKeyListModel::doMapToKey(const QModelIndex &idx) const { Key key = Key::null; if (idx.isValid()) { const char *const issuer_fpr = static_cast(idx.internalPointer()); if (!issuer_fpr || !*issuer_fpr) { // top-level: if (static_cast(idx.row()) < mTopLevels.size()) { key = mTopLevels[idx.row()]; } } else { // non-toplevel: const Map::const_iterator it = mKeysByExistingParent.find(issuer_fpr); if (it != mKeysByExistingParent.end() && static_cast(idx.row()) < it->second.size()) { key = it->second[idx.row()]; } } } return key; } QModelIndex HierarchicalKeyListModel::doMapFromKey(const Key &key, int col) const { if (key.isNull()) { return {}; } const char *issuer_fpr = cleanChainID(key); // we need to look in the toplevels list,... const std::vector *v = &mTopLevels; if (issuer_fpr && *issuer_fpr) { const std::map>::const_iterator it = mKeysByExistingParent.find(issuer_fpr); // ...unless we find an existing parent: if (it != mKeysByExistingParent.end()) { v = &it->second; } else { issuer_fpr = nullptr; // force internalPointer to zero for toplevels } } const std::vector::const_iterator it = std::lower_bound(v->begin(), v->end(), key, _detail::ByFingerprint()); if (it == v->end() || !_detail::ByFingerprint()(*it, key)) { return QModelIndex(); } const unsigned int row = std::distance(v->begin(), it); return createIndex(row, col, const_cast(issuer_fpr)); } void HierarchicalKeyListModel::addKeyWithParent(const char *issuer_fpr, const Key &key) { Q_ASSERT(issuer_fpr); Q_ASSERT(*issuer_fpr); Q_ASSERT(!key.isNull()); std::vector &subjects = mKeysByExistingParent[issuer_fpr]; // find insertion point: const std::vector::iterator it = std::lower_bound(subjects.begin(), subjects.end(), key, _detail::ByFingerprint()); const int row = std::distance(subjects.begin(), it); if (it != subjects.end() && qstricmp(it->primaryFingerprint(), key.primaryFingerprint()) == 0) { // exists -> replace *it = key; if (!modelResetInProgress()) { Q_EMIT dataChanged(createIndex(row, 0, const_cast(issuer_fpr)), createIndex(row, NumColumns - 1, const_cast(issuer_fpr))); } } else { // doesn't exist -> insert const std::vector::const_iterator pos = Kleo::binary_find(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), issuer_fpr, _detail::ByFingerprint()); Q_ASSERT(pos != mKeysByFingerprint.end()); if (!modelResetInProgress()) { beginInsertRows(index(*pos), row, row); } subjects.insert(it, key); if (!modelResetInProgress()) { endInsertRows(); } } } void HierarchicalKeyListModel::addKeyWithoutParent(const char *issuer_fpr, const Key &key) { Q_ASSERT(issuer_fpr); Q_ASSERT(*issuer_fpr); Q_ASSERT(!key.isNull()); std::vector &subjects = mKeysByNonExistingParent[issuer_fpr]; // find insertion point: const std::vector::iterator it = std::lower_bound(subjects.begin(), subjects.end(), key, _detail::ByFingerprint()); if (it != subjects.end() && qstricmp(it->primaryFingerprint(), key.primaryFingerprint()) == 0) { // exists -> replace *it = key; } else { // doesn't exist -> insert subjects.insert(it, key); } addTopLevelKey(key); } void HierarchicalKeyListModel::addTopLevelKey(const Key &key) { // find insertion point: const std::vector::iterator it = std::lower_bound(mTopLevels.begin(), mTopLevels.end(), key, _detail::ByFingerprint()); const int row = std::distance(mTopLevels.begin(), it); if (it != mTopLevels.end() && qstricmp(it->primaryFingerprint(), key.primaryFingerprint()) == 0) { // exists -> replace *it = key; if (!modelResetInProgress()) { Q_EMIT dataChanged(createIndex(row, 0), createIndex(row, NumColumns - 1)); } } else { // doesn't exist -> insert if (!modelResetInProgress()) { beginInsertRows(QModelIndex(), row, row); } mTopLevels.insert(it, key); if (!modelResetInProgress()) { endInsertRows(); } } } namespace { // based on https://www.boost.org/doc/libs/1_77_0/libs/graph/doc/file_dependency_example.html#sec:cycles struct cycle_detector : public boost::dfs_visitor<> { cycle_detector(bool &has_cycle) : _has_cycle{has_cycle} { } template void back_edge(Edge, Graph &) { _has_cycle = true; } private: bool &_has_cycle; }; static bool graph_has_cycle(const boost::adjacency_list<> &graph) { bool cycle_found = false; cycle_detector vis{cycle_found}; boost::depth_first_search(graph, visitor(vis)); return cycle_found; } static void find_keys_causing_cycles_and_mask_their_issuers(const std::vector &keys) { boost::adjacency_list<> graph{keys.size()}; for (unsigned int i = 0, end = keys.size(); i != end; ++i) { const auto &key = keys[i]; const char *const issuer_fpr = cleanChainID(key); if (!issuer_fpr || !*issuer_fpr) { continue; } const std::vector::const_iterator it = Kleo::binary_find(keys.begin(), keys.end(), issuer_fpr, _detail::ByFingerprint()); if (it == keys.end()) { continue; } const auto j = std::distance(keys.begin(), it); const auto edge = boost::add_edge(i, j, graph).first; if (graph_has_cycle(graph)) { Issuers::instance()->maskIssuerOfKey(key); boost::remove_edge(edge, graph); } } } static auto build_key_graph(const std::vector &keys) { boost::adjacency_list<> graph(keys.size()); // add edges from children to parents: for (unsigned int i = 0, end = keys.size(); i != end; ++i) { const char *const issuer_fpr = cleanChainID(keys[i]); if (!issuer_fpr || !*issuer_fpr) { continue; } const std::vector::const_iterator it = Kleo::binary_find(keys.begin(), keys.end(), issuer_fpr, _detail::ByFingerprint()); if (it == keys.end()) { continue; } const auto j = std::distance(keys.begin(), it); add_edge(i, j, graph); } return graph; } // sorts 'keys' such that parent always come before their children: static std::vector topological_sort(const std::vector &keys) { const auto graph = build_key_graph(keys); std::vector order; order.reserve(keys.size()); topological_sort(graph, std::back_inserter(order)); Q_ASSERT(order.size() == keys.size()); std::vector result; result.reserve(keys.size()); for (int i : std::as_const(order)) { result.push_back(keys[i]); } return result; } } QList HierarchicalKeyListModel::doAddKeys(const std::vector &keys) { Q_ASSERT(std::is_sorted(keys.begin(), keys.end(), _detail::ByFingerprint())); if (keys.empty()) { return QList(); } const std::vector oldKeys = mKeysByFingerprint; std::vector merged; merged.reserve(keys.size() + mKeysByFingerprint.size()); std::set_union(keys.begin(), keys.end(), mKeysByFingerprint.begin(), mKeysByFingerprint.end(), std::back_inserter(merged), _detail::ByFingerprint()); mKeysByFingerprint = merged; if (graph_has_cycle(build_key_graph(mKeysByFingerprint))) { find_keys_causing_cycles_and_mask_their_issuers(mKeysByFingerprint); } std::set> changedParents; const auto topologicalSortedList = topological_sort(keys); for (const Key &key : topologicalSortedList) { // check to see whether this key is a parent for a previously parent-less group: const char *const fpr = key.primaryFingerprint(); if (!fpr || !*fpr) { continue; } const bool keyAlreadyExisted = std::binary_search(oldKeys.begin(), oldKeys.end(), key, _detail::ByFingerprint()); const Map::iterator it = mKeysByNonExistingParent.find(fpr); const std::vector children = it != mKeysByNonExistingParent.end() ? it->second : std::vector(); if (it != mKeysByNonExistingParent.end()) { mKeysByNonExistingParent.erase(it); } // Step 1: For new keys, remove children from toplevel: if (!keyAlreadyExisted) { auto last = mTopLevels.begin(); auto lastFP = mKeysByFingerprint.begin(); for (const Key &k : children) { last = Kleo::binary_find(last, mTopLevels.end(), k, _detail::ByFingerprint()); Q_ASSERT(last != mTopLevels.end()); const int row = std::distance(mTopLevels.begin(), last); lastFP = Kleo::binary_find(lastFP, mKeysByFingerprint.end(), k, _detail::ByFingerprint()); Q_ASSERT(lastFP != mKeysByFingerprint.end()); Q_EMIT rowAboutToBeMoved(QModelIndex(), row); if (!modelResetInProgress()) { beginRemoveRows(QModelIndex(), row, row); } last = mTopLevels.erase(last); lastFP = mKeysByFingerprint.erase(lastFP); if (!modelResetInProgress()) { endRemoveRows(); } } } // Step 2: add/update key const char *const issuer_fpr = cleanChainID(key); if (!issuer_fpr || !*issuer_fpr) { // root or something... addTopLevelKey(key); } else if (std::binary_search(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), issuer_fpr, _detail::ByFingerprint())) { // parent exists... addKeyWithParent(issuer_fpr, key); } else { // parent doesn't exist yet... addKeyWithoutParent(issuer_fpr, key); } const QModelIndex key_idx = index(key); QModelIndex key_parent = key_idx.parent(); while (key_parent.isValid()) { changedParents.insert(doMapToKey(key_parent)); key_parent = key_parent.parent(); } // Step 3: Add children to new parent ( == key ) if (!keyAlreadyExisted && !children.empty()) { addKeys(children); const QModelIndex new_parent = index(key); // Q_EMIT the rowMoved() signals in reversed direction, so the // implementation can use a stack for mapping. for (int i = children.size() - 1; i >= 0; --i) { Q_EMIT rowMoved(new_parent, i); } } } // Q_EMIT dataChanged for all parents with new children. This triggers KeyListSortFilterProxyModel to // show a parent node if it just got children matching the proxy's filter if (!modelResetInProgress()) { for (const Key &i : std::as_const(changedParents)) { const QModelIndex idx = index(i); if (idx.isValid()) { Q_EMIT dataChanged(idx.sibling(idx.row(), 0), idx.sibling(idx.row(), NumColumns - 1)); } } } return indexes(keys); } void HierarchicalKeyListModel::doRemoveKey(const Key &key) { const QModelIndex idx = index(key); if (!idx.isValid()) { return; } const char *const fpr = key.primaryFingerprint(); if (mKeysByExistingParent.find(fpr) != mKeysByExistingParent.end()) { // handle non-leave nodes: std::vector keys = mKeysByFingerprint; const std::vector::iterator it = Kleo::binary_find(keys.begin(), keys.end(), key, _detail::ByFingerprint()); if (it == keys.end()) { return; } keys.erase(it); // FIXME for simplicity, we just clear the model and re-add all keys minus the removed one. This is suboptimal, // but acceptable given that deletion of non-leave nodes is rather rare. clear(Keys); addKeys(keys); return; } // handle leave nodes: const std::vector::iterator it = Kleo::binary_find(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), key, _detail::ByFingerprint()); Q_ASSERT(it != mKeysByFingerprint.end()); Q_ASSERT(mKeysByNonExistingParent.find(fpr) == mKeysByNonExistingParent.end()); Q_ASSERT(mKeysByExistingParent.find(fpr) == mKeysByExistingParent.end()); if (!modelResetInProgress()) { beginRemoveRows(parent(idx), idx.row(), idx.row()); } mKeysByFingerprint.erase(it); const char *const issuer_fpr = cleanChainID(key); const std::vector::iterator tlIt = Kleo::binary_find(mTopLevels.begin(), mTopLevels.end(), key, _detail::ByFingerprint()); if (tlIt != mTopLevels.end()) { mTopLevels.erase(tlIt); } if (issuer_fpr && *issuer_fpr) { const Map::iterator nexIt = mKeysByNonExistingParent.find(issuer_fpr); if (nexIt != mKeysByNonExistingParent.end()) { const std::vector::iterator eit = Kleo::binary_find(nexIt->second.begin(), nexIt->second.end(), key, _detail::ByFingerprint()); if (eit != nexIt->second.end()) { nexIt->second.erase(eit); } if (nexIt->second.empty()) { mKeysByNonExistingParent.erase(nexIt); } } const Map::iterator exIt = mKeysByExistingParent.find(issuer_fpr); if (exIt != mKeysByExistingParent.end()) { const std::vector::iterator eit = Kleo::binary_find(exIt->second.begin(), exIt->second.end(), key, _detail::ByFingerprint()); if (eit != exIt->second.end()) { exIt->second.erase(eit); } if (exIt->second.empty()) { mKeysByExistingParent.erase(exIt); } } } if (!modelResetInProgress()) { endRemoveRows(); } } KeyGroup HierarchicalKeyListModel::doMapToGroup(const QModelIndex &idx) const { Q_ASSERT(idx.isValid()); if (idx.parent().isValid()) { // groups are always top-level return KeyGroup(); } if (static_cast(idx.row()) >= mTopLevels.size() && static_cast(idx.row()) < mTopLevels.size() + mGroups.size() && idx.column() < NumColumns) { return mGroups[idx.row() - mTopLevels.size()]; } else { return KeyGroup(); } } QModelIndex HierarchicalKeyListModel::doMapFromGroup(const KeyGroup &group, int column) const { Q_ASSERT(!group.isNull()); const auto it = std::find_if(mGroups.cbegin(), mGroups.cend(), [group](const KeyGroup &g) { return g.source() == group.source() && g.id() == group.id(); }); if (it == mGroups.cend()) { return QModelIndex(); } else { return createIndex(it - mGroups.cbegin() + mTopLevels.size(), column); } } void HierarchicalKeyListModel::doSetGroups(const std::vector &groups) { Q_ASSERT(mGroups.empty()); // ensure that groups have been cleared const int first = mTopLevels.size(); const int last = first + groups.size() - 1; if (!modelResetInProgress()) { beginInsertRows(QModelIndex(), first, last); } mGroups = groups; if (!modelResetInProgress()) { endInsertRows(); } } QModelIndex HierarchicalKeyListModel::doAddGroup(const KeyGroup &group) { const int newRow = lastGroupRow() + 1; if (!modelResetInProgress()) { beginInsertRows(QModelIndex(), newRow, newRow); } mGroups.push_back(group); if (!modelResetInProgress()) { endInsertRows(); } return createIndex(newRow, 0); } bool HierarchicalKeyListModel::doSetGroupData(const QModelIndex &index, const KeyGroup &group) { if (group.isNull()) { return false; } const int groupIndex = this->groupIndex(index); if (groupIndex == -1) { return false; } mGroups[groupIndex] = group; if (!modelResetInProgress()) { Q_EMIT dataChanged(createIndex(index.row(), 0), createIndex(index.row(), NumColumns - 1)); } return true; } bool HierarchicalKeyListModel::doRemoveGroup(const KeyGroup &group) { const QModelIndex modelIndex = doMapFromGroup(group, 0); if (!modelIndex.isValid()) { return false; } const int groupIndex = this->groupIndex(modelIndex); Q_ASSERT(groupIndex != -1); if (groupIndex == -1) { return false; } if (!modelResetInProgress()) { beginRemoveRows(QModelIndex(), modelIndex.row(), modelIndex.row()); } mGroups.erase(mGroups.begin() + groupIndex); if (!modelResetInProgress()) { endRemoveRows(); } return true; } void HierarchicalKeyListModel::doClear(ItemTypes types) { if (types & Keys) { mTopLevels.clear(); mKeysByFingerprint.clear(); mKeysByExistingParent.clear(); mKeysByNonExistingParent.clear(); Issuers::instance()->clear(); } if (types & Groups) { mGroups.clear(); } } void AbstractKeyListModel::useKeyCache(bool value, KeyList::Options options) { d->m_keyListOptions = options; d->m_useKeyCache = value; if (!d->m_useKeyCache) { clear(All); } else { d->updateFromKeyCache(); } connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this] { d->updateFromKeyCache(); }); } // static AbstractKeyListModel *AbstractKeyListModel::createFlatKeyListModel(QObject *p) { AbstractKeyListModel *const m = new FlatKeyListModel(p); #ifdef KLEO_MODEL_TEST new QAbstractItemModelTester(m, p); #endif return m; } // static AbstractKeyListModel *AbstractKeyListModel::createHierarchicalKeyListModel(QObject *p) { AbstractKeyListModel *const m = new HierarchicalKeyListModel(p); #ifdef KLEO_MODEL_TEST new QAbstractItemModelTester(m, p); #endif return m; } #include "keylistmodel.moc" /*! \fn AbstractKeyListModel::rowAboutToBeMoved( const QModelIndex & old_parent, int old_row ) Emitted before the removal of a row from that model. It will later be added to the model again, in response to which rowMoved() will be emitted. If multiple rows are moved in one go, multiple rowAboutToBeMoved() signals are emitted before the corresponding number of rowMoved() signals is emitted - in reverse order. This works around the absence of move semantics in QAbstractItemModel. Clients can maintain a stack to perform the QModelIndex-mapping themselves, or, e.g., to preserve the selection status of the row: \code std::vector mMovingRowWasSelected; // transient, used when rows are moved // ... void slotRowAboutToBeMoved( const QModelIndex & p, int row ) { mMovingRowWasSelected.push_back( selectionModel()->isSelected( model()->index( row, 0, p ) ) ); } void slotRowMoved( const QModelIndex & p, int row ) { const bool wasSelected = mMovingRowWasSelected.back(); mMovingRowWasSelected.pop_back(); if ( wasSelected ) selectionModel()->select( model()->index( row, 0, p ), Select|Rows ); } \endcode A similar mechanism could be used to preserve the current item during moves. */ /*! \fn AbstractKeyListModel::rowMoved( const QModelIndex & new_parent, int new_parent ) See rowAboutToBeMoved() */ diff --git a/src/models/keylistmodel.h b/src/models/keylistmodel.h index 326c827e7..cc6421c2e 100644 --- a/src/models/keylistmodel.h +++ b/src/models/keylistmodel.h @@ -1,133 +1,132 @@ /* -*- mode: c++; c-basic-offset:4 -*- models/keylistmodel.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include - -#include "kleo_export.h" - #include "keylist.h" #include "keylistmodelinterface.h" +#include "kleo_export.h" #include +#include + #include namespace GpgME { class Key; } namespace Kleo { class KLEO_EXPORT AbstractKeyListModel : public QAbstractItemModel, public KeyListModelInterface { Q_OBJECT public: enum ItemType { // clang-format off Keys = 0x01, Groups = 0x02, All = Keys | Groups, // clang-format on }; Q_DECLARE_FLAGS(ItemTypes, ItemType) explicit AbstractKeyListModel(QObject *parent = nullptr); ~AbstractKeyListModel() override; static AbstractKeyListModel *createFlatKeyListModel(QObject *parent = nullptr); static AbstractKeyListModel *createHierarchicalKeyListModel(QObject *parent = nullptr); GpgME::Key key(const QModelIndex &idx) const override; std::vector keys(const QList &indexes) const override; KeyGroup group(const QModelIndex &idx) const override; using QAbstractItemModel::index; QModelIndex index(const GpgME::Key &key) const override; QModelIndex index(const GpgME::Key &key, int col) const; QList indexes(const std::vector &keys) const override; QModelIndex index(const KeyGroup &group) const override; QModelIndex index(const KeyGroup &group, int col) const; Q_SIGNALS: void rowAboutToBeMoved(const QModelIndex &old_parent, int old_row); void rowMoved(const QModelIndex &new_parent, int new_row); public Q_SLOTS: void setKeys(const std::vector &keys); /* Set this to set all or only secret keys from the keycache. */ void useKeyCache(bool value, Kleo::KeyList::Options options); QModelIndex addKey(const GpgME::Key &key); QList addKeys(const std::vector &keys); void removeKey(const GpgME::Key &key); void setGroups(const std::vector &groups); QModelIndex addGroup(const Kleo::KeyGroup &group); bool removeGroup(const Kleo::KeyGroup &group); void clear(Kleo::AbstractKeyListModel::ItemTypes types = All); public: int columnCount(const QModelIndex &pidx) const override; QVariant headerData(int section, Qt::Orientation o, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; /** * defines which information is displayed in tooltips * see Kleo::Formatting::ToolTipOption */ int toolTipOptions() const; void setToolTipOptions(int opts); /** * Set the keys to use for KeyListModelInterface::Remark column * to obtain remarks from this keys signature notations. * Needs at least GpgME 1.14 to work properly. Remarks are * joined by a semicolon and a space. */ void setRemarkKeys(const std::vector &remarkKeys); std::vector remarkKeys() const; protected: bool modelResetInProgress(); private: QVariant data(const GpgME::Key &key, int column, int role) const; QVariant data(const KeyGroup &group, int column, int role) const; virtual GpgME::Key doMapToKey(const QModelIndex &index) const = 0; virtual QModelIndex doMapFromKey(const GpgME::Key &key, int column) const = 0; virtual QList doAddKeys(const std::vector &keys) = 0; virtual void doRemoveKey(const GpgME::Key &key) = 0; virtual KeyGroup doMapToGroup(const QModelIndex &index) const = 0; virtual QModelIndex doMapFromGroup(const KeyGroup &group, int column) const = 0; virtual void doSetGroups(const std::vector &groups) = 0; virtual QModelIndex doAddGroup(const KeyGroup &group) = 0; virtual bool doSetGroupData(const QModelIndex &index, const KeyGroup &group) = 0; virtual bool doRemoveGroup(const KeyGroup &group) = 0; virtual void doClear(ItemTypes types) = 0; private: class Private; QScopedPointer const d; }; } Q_DECLARE_OPERATORS_FOR_FLAGS(Kleo::AbstractKeyListModel::ItemTypes) diff --git a/src/models/keylistmodelinterface.h b/src/models/keylistmodelinterface.h index 6da559f2a..05032f7e4 100644 --- a/src/models/keylistmodelinterface.h +++ b/src/models/keylistmodelinterface.h @@ -1,46 +1,46 @@ /* -*- mode: c++; c-basic-offset:4 -*- models/keylistmodelinterface.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include - #include "kleo_export.h" +#include + namespace GpgME { class Key; } class QModelIndex; template class QList; namespace Kleo { class KeyGroup; class KLEO_EXPORT KeyListModelInterface { public: virtual ~KeyListModelInterface(); virtual GpgME::Key key(const QModelIndex &idx) const = 0; virtual std::vector keys(const QList &idxs) const = 0; virtual QModelIndex index(const GpgME::Key &key) const = 0; virtual QList indexes(const std::vector &keys) const = 0; virtual KeyGroup group(const QModelIndex &idx) const = 0; virtual QModelIndex index(const KeyGroup &group) const = 0; }; } diff --git a/src/models/keylistsortfilterproxymodel.cpp b/src/models/keylistsortfilterproxymodel.cpp index 025cf52b6..a76123d2d 100644 --- a/src/models/keylistsortfilterproxymodel.cpp +++ b/src/models/keylistsortfilterproxymodel.cpp @@ -1,254 +1,254 @@ /* -*- mode: c++; c-basic-offset:4 -*- models/keylistsortfilterproxymodel.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keylistsortfilterproxymodel.h" #include "keylist.h" #include "keylistmodel.h" -#include -#include -#include +#include +#include +#include #include #include using namespace Kleo; using namespace GpgME; AbstractKeyListSortFilterProxyModel::AbstractKeyListSortFilterProxyModel(QObject *p) : QSortFilterProxyModel(p) , KeyListModelInterface() { init(); } AbstractKeyListSortFilterProxyModel::AbstractKeyListSortFilterProxyModel(const AbstractKeyListSortFilterProxyModel &other) : QSortFilterProxyModel() , KeyListModelInterface() { Q_UNUSED(other) init(); } void AbstractKeyListSortFilterProxyModel::init() { setDynamicSortFilter(true); setSortRole(Qt::EditRole); // EditRole can be expected to be in a less formatted way, better for sorting setFilterRole(Qt::DisplayRole); setFilterCaseSensitivity(Qt::CaseInsensitive); } AbstractKeyListSortFilterProxyModel::~AbstractKeyListSortFilterProxyModel() { } Key AbstractKeyListSortFilterProxyModel::key(const QModelIndex &idx) const { const KeyListModelInterface *const klmi = dynamic_cast(sourceModel()); if (!klmi) { static Key null; return null; } return klmi->key(mapToSource(idx)); } std::vector AbstractKeyListSortFilterProxyModel::keys(const QList &indexes) const { const KeyListModelInterface *const klmi = dynamic_cast(sourceModel()); if (!klmi) { return std::vector(); } QList mapped; mapped.reserve(indexes.size()); std::transform(indexes.begin(), // indexes.end(), std::back_inserter(mapped), [this](const QModelIndex &idx) { return mapToSource(idx); }); return klmi->keys(mapped); } KeyGroup AbstractKeyListSortFilterProxyModel::group(const QModelIndex &idx) const { if (const KeyListModelInterface *const klmi = dynamic_cast(sourceModel())) { return klmi->group(mapToSource(idx)); } return KeyGroup(); } QModelIndex AbstractKeyListSortFilterProxyModel::index(const Key &key) const { if (const KeyListModelInterface *const klmi = dynamic_cast(sourceModel())) { return mapFromSource(klmi->index(key)); } return {}; } QList AbstractKeyListSortFilterProxyModel::indexes(const std::vector &keys) const { if (const KeyListModelInterface *const klmi = dynamic_cast(sourceModel())) { const QList source = klmi->indexes(keys); QList mapped; mapped.reserve(source.size()); std::transform(source.begin(), // source.end(), std::back_inserter(mapped), [this](const QModelIndex &idx) { return mapFromSource(idx); }); return mapped; } return QList(); } QModelIndex AbstractKeyListSortFilterProxyModel::index(const Kleo::KeyGroup &group) const { if (const KeyListModelInterface *const klmi = dynamic_cast(sourceModel())) { return mapFromSource(klmi->index(group)); } return {}; } class KeyListSortFilterProxyModel::Private { friend class ::Kleo::KeyListSortFilterProxyModel; public: explicit Private() : keyFilter() { } ~Private() { } private: std::shared_ptr keyFilter; }; KeyListSortFilterProxyModel::KeyListSortFilterProxyModel(QObject *p) : AbstractKeyListSortFilterProxyModel(p) , d(new Private) { } KeyListSortFilterProxyModel::KeyListSortFilterProxyModel(const KeyListSortFilterProxyModel &other) : AbstractKeyListSortFilterProxyModel(other) , d(new Private(*other.d)) { } KeyListSortFilterProxyModel::~KeyListSortFilterProxyModel() { } KeyListSortFilterProxyModel *KeyListSortFilterProxyModel::clone() const { return new KeyListSortFilterProxyModel(*this); } std::shared_ptr KeyListSortFilterProxyModel::keyFilter() const { return d->keyFilter; } void KeyListSortFilterProxyModel::setKeyFilter(const std::shared_ptr &kf) { if (kf == d->keyFilter) { return; } d->keyFilter = kf; invalidate(); } bool KeyListSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { // // 0. Keep parents of matching children: // const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); for (int i = 0, end = sourceModel()->rowCount(index); i != end; ++i) { if (filterAcceptsRow(i, index)) { return true; } } // // 1. Check filterRegExp // const int role = filterRole(); const int col = filterKeyColumn(); const QRegularExpression rx = filterRegularExpression(); const QModelIndex nameIndex = sourceModel()->index(source_row, KeyList::PrettyName, source_parent); const KeyListModelInterface *const klm = dynamic_cast(sourceModel()); Q_ASSERT(klm); const Key key = klm->key(nameIndex); const KeyGroup group = klm->group(nameIndex); Q_ASSERT(!key.isNull() || !group.isNull()); if (col) { const QModelIndex colIdx = sourceModel()->index(source_row, col, source_parent); const QString content = colIdx.data(role).toString(); if (!content.contains(rx)) { return false; } } else if (!key.isNull()) { // By default match against the full uid data (name / email / comment / dn) bool match = false; for (const auto &uid : key.userIDs()) { const auto id = QString::fromUtf8(uid.id()); if (id.contains(rx)) { match = true; break; } // Also match against remarks (search tags) const auto alm = dynamic_cast(sourceModel()); if (alm) { const auto remarks = alm->data(alm->index(key, KeyList::Remarks)); if (!remarks.isNull() && remarks.toString().contains(rx)) { match = true; break; } } // Also match against fingerprints for (const auto &subkey : key.subkeys()) { const auto fpr = QString::fromLatin1(subkey.fingerprint()); if (fpr.contains(rx)) { match = true; break; } } } if (!match) { return false; } } else if (!group.isNull()) { if (!group.name().contains(rx)) { return false; } } else { return false; } // // 2. For keys check that key filters match (if any are defined) // if (d->keyFilter && !key.isNull()) { // avoid artifacts when no filters are defined return d->keyFilter->matches(key, KeyFilter::Filtering); } // 3. match by default: return true; } diff --git a/src/models/keylistsortfilterproxymodel.h b/src/models/keylistsortfilterproxymodel.h index 69d7ba102..b9da0c0c6 100644 --- a/src/models/keylistsortfilterproxymodel.h +++ b/src/models/keylistsortfilterproxymodel.h @@ -1,80 +1,79 @@ /* -*- mode: c++; c-basic-offset:4 -*- models/keylistsortfilterproxymodel.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include - #include "keylistmodelinterface.h" - #include "kleo_export.h" +#include + #include namespace GpgME { class Key; } namespace Kleo { class KeyFilter; class KLEO_EXPORT AbstractKeyListSortFilterProxyModel : public QSortFilterProxyModel, public KeyListModelInterface { Q_OBJECT protected: AbstractKeyListSortFilterProxyModel(const AbstractKeyListSortFilterProxyModel &); public: explicit AbstractKeyListSortFilterProxyModel(QObject *parent = nullptr); ~AbstractKeyListSortFilterProxyModel() override; virtual AbstractKeyListSortFilterProxyModel *clone() const = 0; GpgME::Key key(const QModelIndex &idx) const override; std::vector keys(const QList &indexes) const override; KeyGroup group(const QModelIndex &idx) const override; using QAbstractItemModel::index; QModelIndex index(const GpgME::Key &key) const override; QList indexes(const std::vector &keys) const override; QModelIndex index(const KeyGroup &group) const override; private: void init(); }; class KLEO_EXPORT KeyListSortFilterProxyModel : public AbstractKeyListSortFilterProxyModel { Q_OBJECT protected: KeyListSortFilterProxyModel(const KeyListSortFilterProxyModel &); public: explicit KeyListSortFilterProxyModel(QObject *parent = nullptr); ~KeyListSortFilterProxyModel() override; std::shared_ptr keyFilter() const; void setKeyFilter(const std::shared_ptr &kf); KeyListSortFilterProxyModel *clone() const override; protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; private: class Private; QScopedPointer const d; }; } diff --git a/src/models/keyrearrangecolumnsproxymodel.cpp b/src/models/keyrearrangecolumnsproxymodel.cpp index f53ffaa8a..db9785ae6 100644 --- a/src/models/keyrearrangecolumnsproxymodel.cpp +++ b/src/models/keyrearrangecolumnsproxymodel.cpp @@ -1,85 +1,85 @@ /* models/keyrearangecolumnsproxymodel.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keyrearrangecolumnsproxymodel.h" -#include +#include #include using namespace Kleo; using namespace GpgME; KeyRearrangeColumnsProxyModel::KeyRearrangeColumnsProxyModel(QObject *parent) : KRearrangeColumnsProxyModel(parent) , KeyListModelInterface() { } KeyListModelInterface *KeyRearrangeColumnsProxyModel::klm() const { auto *ret = dynamic_cast(sourceModel()); Q_ASSERT(ret); return ret; } Key KeyRearrangeColumnsProxyModel::key(const QModelIndex &idx) const { return klm()->key(mapToSource(idx)); } std::vector KeyRearrangeColumnsProxyModel::keys(const QList &idxs) const { QList srcIdxs; srcIdxs.reserve(idxs.count()); for (const QModelIndex &idx : idxs) { srcIdxs << mapToSource(idx); } return klm()->keys(srcIdxs); } KeyGroup KeyRearrangeColumnsProxyModel::group(const QModelIndex &idx) const { return klm()->group(mapToSource(idx)); } QModelIndex KeyRearrangeColumnsProxyModel::index(const GpgME::Key &key) const { return mapFromSource(klm()->index(key)); } QList KeyRearrangeColumnsProxyModel::indexes(const std::vector &keys) const { QList myIdxs; const QList srcIdxs = klm()->indexes(keys); myIdxs.reserve(srcIdxs.count()); for (const QModelIndex &idx : srcIdxs) { myIdxs << mapFromSource(idx); } return myIdxs; } QModelIndex KeyRearrangeColumnsProxyModel::index(const KeyGroup &group) const { return mapFromSource(klm()->index(group)); } void KeyRearrangeColumnsProxyModel::sort(int column, Qt::SortOrder order) { const auto fakeIdx = createIndex(0, column); if (!fakeIdx.isValid()) { // Empty model? KRearrangeColumnsProxyModel::sort(column, order); return; } const auto remappedIdx = mapToSource(fakeIdx); KRearrangeColumnsProxyModel::sort(remappedIdx.column(), order); } diff --git a/src/models/keyrearrangecolumnsproxymodel.h b/src/models/keyrearrangecolumnsproxymodel.h index fed250c2c..c754b5378 100644 --- a/src/models/keyrearrangecolumnsproxymodel.h +++ b/src/models/keyrearrangecolumnsproxymodel.h @@ -1,43 +1,42 @@ /* models/keyrearangecolumnsproxymodel.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "keylistmodelinterface.h" - #include "kleo_export.h" #include namespace Kleo { /** KRearrangeColumnsProxymodel that implements the KeyListModelInterface. */ class KLEO_EXPORT KeyRearrangeColumnsProxyModel : public KRearrangeColumnsProxyModel, public KeyListModelInterface { public: explicit KeyRearrangeColumnsProxyModel(QObject *parent = nullptr); GpgME::Key key(const QModelIndex &idx) const override; std::vector keys(const QList &idxs) const override; KeyGroup group(const QModelIndex &idx) const override; using KRearrangeColumnsProxyModel::index; QModelIndex index(const GpgME::Key &key) const override; QList indexes(const std::vector &keys) const override; QModelIndex index(const KeyGroup &group) const override; void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; private: KeyListModelInterface *klm() const; }; } // namespace Kleo diff --git a/src/models/subkeylistmodel.cpp b/src/models/subkeylistmodel.cpp index e5efb4b9b..c04388d62 100644 --- a/src/models/subkeylistmodel.cpp +++ b/src/models/subkeylistmodel.cpp @@ -1,215 +1,215 @@ /* -*- mode: c++; c-basic-offset:4 -*- models/subkeylistmodel.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "subkeylistmodel.h" -#include - -#include +#include #include #include #include +#include + #include #include using namespace GpgME; using namespace Kleo; class SubkeyListModel::Private { friend class ::Kleo::SubkeyListModel; SubkeyListModel *const q; public: explicit Private(SubkeyListModel *qq) : q(qq) , key() { } private: Key key; }; SubkeyListModel::SubkeyListModel(QObject *p) : QAbstractTableModel(p) , d(new Private(this)) { } SubkeyListModel::~SubkeyListModel() { } Key SubkeyListModel::key() const { return d->key; } // slot void SubkeyListModel::setKey(const Key &key) { const Key oldKey = d->key; if (qstricmp(key.primaryFingerprint(), oldKey.primaryFingerprint()) != 0) { // different key -> reset beginResetModel(); d->key = key; endResetModel(); return; } d->key = key; // ### diff them, and signal more fine-grained than this: if (key.numSubkeys() > 0 && oldKey.numSubkeys() == key.numSubkeys()) { Q_EMIT dataChanged(index(0, 0), index(key.numSubkeys() - 1, NumColumns - 1)); } else { Q_EMIT layoutAboutToBeChanged(); Q_EMIT layoutChanged(); } } Subkey SubkeyListModel::subkey(const QModelIndex &idx) const { if (idx.isValid()) { return d->key.subkey(idx.row()); } else { return Subkey(); } } std::vector SubkeyListModel::subkeys(const QList &indexes) const { std::vector result; result.reserve(indexes.size()); std::transform(indexes.begin(), // indexes.end(), std::back_inserter(result), [this](const QModelIndex &idx) { return subkey(idx); }); return result; } QModelIndex SubkeyListModel::index(const Subkey &subkey, int col) const { // O(N), but not sorted, so no better way... for (unsigned int row = 0, end = d->key.numSubkeys(); row != end; ++row) { if (qstricmp(subkey.keyID(), d->key.subkey(row).keyID()) == 0) { return index(row, col); } } return {}; } QList SubkeyListModel::indexes(const std::vector &subkeys) const { QList result; result.reserve(subkeys.size()); // O(N*M), but who cares...? std::transform(subkeys.begin(), // subkeys.end(), std::back_inserter(result), [this](const Subkey &key) { return index(key); }); return result; } void SubkeyListModel::clear() { beginResetModel(); d->key = Key::null; endResetModel(); } int SubkeyListModel::columnCount(const QModelIndex &) const { return NumColumns; } int SubkeyListModel::rowCount(const QModelIndex &pidx) const { return pidx.isValid() ? 0 : d->key.numSubkeys(); } QVariant SubkeyListModel::headerData(int section, Qt::Orientation o, int role) const { if (o == Qt::Horizontal) { if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::ToolTipRole) { switch (section) { case ID: return i18n("ID"); case Type: return i18n("Type"); case ValidFrom: return i18n("Valid From"); case ValidUntil: return i18n("Valid Until"); case Status: return i18n("Status"); case Strength: return i18n("Strength"); case Usage: return i18n("Usage"); case NumColumns:; } } } return QVariant(); } QVariant SubkeyListModel::data(const QModelIndex &idx, int role) const { if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::ToolTipRole) { return QVariant(); } const Subkey subkey = this->subkey(idx); if (subkey.isNull()) { return QVariant(); } switch (idx.column()) { case ID: return QString::fromLatin1(subkey.keyID()); case Type: return Formatting::type(subkey); case ValidFrom: if (role == Qt::EditRole) { return Formatting::creationDate(subkey); } else { return Formatting::creationDateString(subkey); } case ValidUntil: if (role == Qt::EditRole) { return Formatting::expirationDate(subkey); } else { return Formatting::expirationDateString(subkey); } case Status: return Formatting::validityShort(subkey); case Usage: return Formatting::usageString(subkey); case Strength: const QString algName = QString::fromStdString(subkey.algoName()); // For ECC keys the algo name is something like bp512 and directly // indicated the "strength" return algName.isEmpty() ? QVariant(subkey.length()) : algName; } return QVariant(); } diff --git a/src/models/subkeylistmodel.h b/src/models/subkeylistmodel.h index 6a9a86606..18dafc444 100644 --- a/src/models/subkeylistmodel.h +++ b/src/models/subkeylistmodel.h @@ -1,71 +1,71 @@ /* -*- mode: c++; c-basic-offset:4 -*- models/subkeylistmodel.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include - #include "kleo_export.h" +#include + #include namespace GpgME { class Key; class Subkey; } namespace Kleo { class KLEO_EXPORT SubkeyListModel : public QAbstractTableModel { Q_OBJECT public: explicit SubkeyListModel(QObject *parent = nullptr); ~SubkeyListModel() override; GpgME::Key key() const; enum Columns { ID, Type, ValidFrom, ValidUntil, Status, Strength, Usage, NumColumns, Icon = ID // which column shall the icon be displayed in? }; GpgME::Subkey subkey(const QModelIndex &idx) const; std::vector subkeys(const QList &indexes) const; using QAbstractTableModel::index; QModelIndex index(const GpgME::Subkey &subkey, int col = 0) const; QList indexes(const std::vector &subkeys) const; public Q_SLOTS: void setKey(const GpgME::Key &key); void clear(); public: int columnCount(const QModelIndex &pidx = QModelIndex()) const override; int rowCount(const QModelIndex &pidx = QModelIndex()) const override; QVariant headerData(int section, Qt::Orientation o, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; private: class Private; QScopedPointer const d; }; } diff --git a/src/models/useridlistmodel.cpp b/src/models/useridlistmodel.cpp index 36bc5129f..e4840e476 100644 --- a/src/models/useridlistmodel.cpp +++ b/src/models/useridlistmodel.cpp @@ -1,350 +1,350 @@ /* -*- mode: c++; c-basic-offset:4 -*- models/useridlistmodel.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Andre Heinecke SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "useridlistmodel.h" #include "keycache.h" -#include +#include #include #include #include #include using namespace GpgME; using namespace Kleo; class UIDModelItem { // A uid model item can either be a UserID::Signature or a UserID. // you can find out which it is if the uid or the signature return // null values. (Not null but isNull) // public: explicit UIDModelItem(const UserID::Signature &sig, UIDModelItem *parentItem, bool showRemarks) : mParentItem{parentItem} , mSig{sig} { mItemData << QString::fromUtf8(sig.signerKeyID()) // << Formatting::prettyName(sig) // << Formatting::prettyEMail(sig) // << Formatting::creationDateString(sig) // << Formatting::expirationDateString(sig) // << Formatting::validityShort(sig) // << (sig.isExportable() ? QStringLiteral("✓") : QString()); QString lastNotation; if (showRemarks && parentItem) { for (const auto ¬ation : sig.notations()) { if (notation.name() && !strcmp(notation.name(), "rem@gnupg.org")) { lastNotation = QString::fromUtf8(notation.value()); } } } mItemData << lastNotation; #ifdef GPGMEPP_SUPPORTS_TRUST_SIGNATURES mItemData << Formatting::trustSignatureDomain(sig); #endif } explicit UIDModelItem(const UserID &uid, UIDModelItem *parentItem) : mParentItem{parentItem} , mUid{uid} { mItemData << Formatting::prettyUserID(uid); } // The root item UIDModelItem() { mItemData << i18n("ID") // << i18n("Name") // << i18n("E-Mail") // << i18n("Valid From") // << i18n("Valid Until") // << i18n("Status") // << i18n("Exportable") // << i18n("Tags"); #ifdef GPGMEPP_SUPPORTS_TRUST_SIGNATURES mItemData << i18n("Trust Signature For"); #endif } ~UIDModelItem() { qDeleteAll(mChildItems); } void appendChild(UIDModelItem *child) { mChildItems << child; } UIDModelItem *child(int row) const { return mChildItems.value(row); } const UIDModelItem *constChild(int row) const { return mChildItems.value(row); } int childCount() const { return mChildItems.count(); } int columnCount() const { if (childCount()) { // We take the value from the first child // as we are likely a UID and our children // are UID Signatures. return constChild(0)->columnCount(); } return mItemData.count(); } QVariant data(int column) const { return mItemData.value(column); } QVariant toolTip(int column) const { if (!mSig.isNull()) { if (column == static_cast(UserIDListModel::Column::Status)) { return i18n("class %1", mSig.certClass()); } else if (column == static_cast(UserIDListModel::Column::TrustSignatureDomain)) { return Formatting::trustSignature(mSig); } } return mItemData.value(column); } QVariant icon(int column) const { if (!mSig.isNull() && column == static_cast(UserIDListModel::Column::Status)) { return Formatting::validityIcon(mSig); } return {}; } int row() const { if (mParentItem) { return mParentItem->mChildItems.indexOf(const_cast(this)); } return 0; } UIDModelItem *parentItem() const { return mParentItem; } UserID::Signature signature() const { return mSig; } UserID uid() const { return mUid; } private: QList mChildItems; QList mItemData; UIDModelItem *mParentItem = nullptr; UserID::Signature mSig; UserID mUid; }; UserIDListModel::UserIDListModel(QObject *p) : QAbstractItemModel{p} { } UserIDListModel::~UserIDListModel() = default; Key UserIDListModel::key() const { return mKey; } void UserIDListModel::setKey(const Key &key) { beginResetModel(); mKey = key; mRootItem.reset(new UIDModelItem); for (int i = 0, ids = key.numUserIDs(); i < ids; ++i) { UserID uid = key.userID(i); auto uidItem = new UIDModelItem(uid, mRootItem.get()); mRootItem->appendChild(uidItem); std::vector sigs = uid.signatures(); std::sort(sigs.begin(), sigs.end()); for (const auto &sig : sigs) { auto sigItem = new UIDModelItem(sig, uidItem, mRemarksEnabled); uidItem->appendChild(sigItem); } } endResetModel(); } int UserIDListModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) { return static_cast(parent.internalPointer())->columnCount(); } if (!mRootItem) { return 0; } return mRootItem->columnCount(); } int UserIDListModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0 || !mRootItem) { return 0; } const UIDModelItem *const parentItem = !parent.isValid() ? mRootItem.get() : static_cast(parent.internalPointer()); return parentItem->childCount(); } QModelIndex UserIDListModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return {}; } const UIDModelItem *const parentItem = !parent.isValid() ? mRootItem.get() : static_cast(parent.internalPointer()); UIDModelItem *const childItem = parentItem->child(row); if (childItem) { return createIndex(row, column, childItem); } else { return QModelIndex(); } } QModelIndex UserIDListModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return {}; } auto childItem = static_cast(index.internalPointer()); UIDModelItem *parentItem = childItem->parentItem(); if (parentItem == mRootItem.get()) { return QModelIndex(); } return createIndex(parentItem->row(), 0, parentItem); } QVariant UserIDListModel::headerData(int section, Qt::Orientation o, int role) const { if (o == Qt::Horizontal && mRootItem) { if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::ToolTipRole) { return mRootItem->data(section); } } return QVariant(); } QVariant UserIDListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::ToolTipRole && role != Qt::DecorationRole) { return QVariant(); } auto item = static_cast(index.internalPointer()); if (role == Qt::ToolTipRole) { return item->toolTip(index.column()); } if (role == Qt::DecorationRole) { return item->icon(index.column()); } return item->data(index.column()); } UserID UserIDListModel::userID(const QModelIndex &index) const { if (!index.isValid()) { return UserID(); } UIDModelItem *item = static_cast(index.internalPointer()); return item->uid(); } QVector UserIDListModel::userIDs(const QModelIndexList &indexes) const { QVector ret; for (const QModelIndex &idx : indexes) { if (!idx.isValid()) { continue; } auto item = static_cast(idx.internalPointer()); if (!item->uid().isNull()) { ret << item->uid(); } } return ret; } UserID::Signature UserIDListModel::signature(const QModelIndex &index) const { if (!index.isValid()) { return UserID::Signature(); } UIDModelItem *item = static_cast(index.internalPointer()); return item->signature(); } QVector UserIDListModel::signatures(const QModelIndexList &indexes) const { QVector ret; for (const QModelIndex &idx : indexes) { if (!idx.isValid()) { continue; } auto item = static_cast(idx.internalPointer()); if (!item->signature().isNull()) { ret << item->signature(); } } return ret; } void UserIDListModel::enableRemarks(bool value) { mRemarksEnabled = value; } diff --git a/src/ui/auditlogviewer.cpp b/src/ui/auditlogviewer.cpp index 67dba5398..4574c5326 100644 --- a/src/ui/auditlogviewer.cpp +++ b/src/ui/auditlogviewer.cpp @@ -1,155 +1,155 @@ /* SPDX-FileCopyrightText: 2015-2021 Laurent Montel SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: LGPL-2.0-or-later */ #include #include "auditlogviewer.h" +#include #include +#include +#include +#include #include #ifdef HAVE_PIMTEXTEDIT -#include "kpimtextedit/richtexteditor.h" +#include #else #include #endif -#include -#include -#include -#include + +#include #include #include #include #include +#include #include #include -#include -#include - using namespace Kleo::Private; AuditLogViewer::AuditLogViewer(const QString &log, QWidget *parent) : QDialog(parent) , m_log(/* sic */) , #ifdef HAVE_PIMTEXTEDIT m_textEdit(new KPIMTextEdit::RichTextEditorWidget(this)) #else m_textEdit(new QTextEdit(this)) #endif { setWindowTitle(i18nc("@title:window", "View GnuPG Audit Log")); QDialogButtonBox *buttonBox = new QDialogButtonBox{}; auto copyClipBtn = buttonBox->addButton(i18n("&Copy to Clipboard"), QDialogButtonBox::ActionRole); copyClipBtn->setObjectName(QStringLiteral("copyClipBtn")); copyClipBtn->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); connect(copyClipBtn, &QPushButton::clicked, this, &AuditLogViewer::slotCopyClip); auto saveAsBtn = buttonBox->addButton(i18n("&Save to Disk..."), QDialogButtonBox::ActionRole); saveAsBtn->setObjectName(QStringLiteral("saveAsBtn")); saveAsBtn->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); connect(saveAsBtn, &QPushButton::clicked, this, &AuditLogViewer::slotSaveAs); auto closeBtn = buttonBox->addButton(QString{}, QDialogButtonBox::AcceptRole); closeBtn->setObjectName(QStringLiteral("Close")); KGuiItem::assign(closeBtn, KStandardGuiItem::close()); m_textEdit->setObjectName(QStringLiteral("m_textEdit")); m_textEdit->setReadOnly(true); auto mainLayout = new QVBoxLayout(this); mainLayout->addWidget(m_textEdit); mainLayout->addWidget(buttonBox); #if 0 qDebug() << "buttonBox->style()->styleHint(QStyle::SH_DialogButtonLayout, ...):" << buttonBox->style()->styleHint(QStyle::SH_DialogButtonLayout, nullptr, buttonBox); qDebug() << __func__ << "buttonBox->focusProxy():" << buttonBox->focusProxy(); qDebug() << __func__ << "copyClipBtn->nextInFocusChain():" << copyClipBtn->nextInFocusChain(); qDebug() << __func__ << "saveAsBtn->nextInFocusChain():" << saveAsBtn->nextInFocusChain(); qDebug() << __func__ << "closeBtn->nextInFocusChain():" << closeBtn->nextInFocusChain(); #endif connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); setAuditLog(log); readConfig(); } AuditLogViewer::~AuditLogViewer() { writeConfig(); } void AuditLogViewer::setAuditLog(const QString &log) { if (log == m_log) { return; } m_log = log; m_textEdit->setHtml(QLatin1String("") + log + QLatin1String("")); } void AuditLogViewer::slotSaveAs() { const QString fileName = QFileDialog::getSaveFileName(this, i18n("Choose File to Save GnuPG Audit Log to")); if (fileName.isEmpty()) { return; } QSaveFile file(fileName); if (file.open(QIODevice::WriteOnly)) { QTextStream s(&file); s << ""; if (!windowTitle().isEmpty()) { s << "\n" << windowTitle().toHtmlEscaped() << "\n"; } s << "\n" << m_log << "\n\n"; s.flush(); file.commit(); } if (const int err = file.error()) { KMessageBox::error(this, i18n("Could not save to file \"%1\": %2", file.fileName(), QString::fromLocal8Bit(strerror(err))), i18n("File Save Error")); } } void AuditLogViewer::slotCopyClip() { #ifdef HAVE_PIMTEXTEDIT m_textEdit->editor()->selectAll(); m_textEdit->editor()->copy(); m_textEdit->editor()->textCursor().clearSelection(); #else m_textEdit->selectAll(); m_textEdit->copy(); m_textEdit->textCursor().clearSelection(); #endif } void AuditLogViewer::readConfig() { KConfigGroup group(KSharedConfig::openConfig(), "AuditLogViewer"); const QSize size = group.readEntry("Size", QSize()); if (size.isValid()) { resize(size); } else { resize(600, 400); } } void AuditLogViewer::writeConfig() { KConfigGroup group(KSharedConfig::openConfig(), "AuditLogViewer"); group.writeEntry("Size", size()); group.sync(); } diff --git a/src/ui/auditlogviewer.h b/src/ui/auditlogviewer.h index c8e77bf2a..fe1d0aa4c 100644 --- a/src/ui/auditlogviewer.h +++ b/src/ui/auditlogviewer.h @@ -1,57 +1,57 @@ /* SPDX-FileCopyrightText: 2015-2021 Laurent Montel SPDX-License-Identifier: LGPL-2.0-or-later */ #pragma once -#include - #include "kleo_export.h" +#include + #ifdef HAVE_PIMTEXTEDIT #include namespace KPIMTextEdit { class RichTextEditorWidget; } #else class QTextEdit; #endif // HAVE_PIMTEXTEDIT namespace Kleo { namespace Private { class KLEO_EXPORT AuditLogViewer : public QDialog { Q_OBJECT public: explicit AuditLogViewer(const QString &log, QWidget *parent = nullptr); ~AuditLogViewer() override; void setAuditLog(const QString &log); private Q_SLOTS: void slotSaveAs(); void slotCopyClip(); private: void writeConfig(); void readConfig(); QString m_log; #ifdef HAVE_PIMTEXTEDIT KPIMTextEdit::RichTextEditorWidget *m_textEdit = nullptr; #else QTextEdit *m_textEdit = nullptr; #endif }; } } diff --git a/src/ui/cryptoconfigentryreaderport.cpp b/src/ui/cryptoconfigentryreaderport.cpp index 0862e49ac..d9f41e4ac 100644 --- a/src/ui/cryptoconfigentryreaderport.cpp +++ b/src/ui/cryptoconfigentryreaderport.cpp @@ -1,73 +1,73 @@ /* ui/cryptoconfigentryreaderport.cpp This file is part of libkleopatra SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "cryptoconfigentryreaderport_p.h" #include "cryptoconfigmodule.h" #include "readerportselection.h" -#include +#include #include #include #include #if __has_include() #include #endif #include #include #include namespace Kleo { CryptoConfigEntryReaderPort::CryptoConfigEntryReaderPort(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *layout, QWidget *parent) : CryptoConfigEntryGUI{module, entry, entryName} , mReaderPort{new ReaderPortSelection{parent}} { auto const label = new QLabel{i18nc("@label:listbox Reader for smart cards", "Reader to connect to"), parent}; label->setBuddy(mReaderPort); if (entry->isReadOnly()) { label->setEnabled(false); mReaderPort->setEnabled(false); } else { connect(mReaderPort, &ReaderPortSelection::valueChanged, this, &CryptoConfigEntryReaderPort::slotChanged); } const int row = layout->rowCount(); layout->addWidget(label, row, 1); layout->addWidget(mReaderPort, row, 2); } void CryptoConfigEntryReaderPort::doSave() { if (mEntry->isReadOnly()) { return; } mEntry->setStringValue(mReaderPort->value()); } void CryptoConfigEntryReaderPort::doLoad() { mReaderPort->setValue(mEntry->stringValue()); } } // namespace Kleo diff --git a/src/ui/cryptoconfigmodule.cpp b/src/ui/cryptoconfigmodule.cpp index 96dab7584..36c5fb6b5 100644 --- a/src/ui/cryptoconfigmodule.cpp +++ b/src/ui/cryptoconfigmodule.cpp @@ -1,1041 +1,1041 @@ /* cryptoconfigmodule.cpp This file is part of kgpgcertmanager SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "cryptoconfigmodule.h" #include "cryptoconfigentryreaderport_p.h" #include "cryptoconfigmodule_p.h" #include "directoryserviceswidget.h" #include "filenamerequester.h" -#include -#include -#include +#include +#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; namespace { class ScrollArea : public QScrollArea { public: explicit ScrollArea(QWidget *p) : QScrollArea(p) { } QSize sizeHint() const override { const QSize wsz = widget() ? widget()->sizeHint() : QSize(); return {wsz.width() + style()->pixelMetric(QStyle::PM_ScrollBarExtent), QScrollArea::sizeHint().height()}; } }; } inline QIcon loadIcon(const QString &s) { QString ss = s; const static QRegularExpression reg(QRegularExpression(QLatin1String("[^a-zA-Z0-9_]"))); return QIcon::fromTheme(ss.replace(reg, QStringLiteral("-"))); } static unsigned int num_components_with_options(const QGpgME::CryptoConfig *config) { if (!config) { return 0; } const QStringList components = config->componentList(); unsigned int result = 0; for (QStringList::const_iterator it = components.begin(); it != components.end(); ++it) { if (const QGpgME::CryptoConfigComponent *const comp = config->component(*it)) { if (!comp->groupList().empty()) { ++result; } } } return result; } static KPageView::FaceType determineJanusFace(const QGpgME::CryptoConfig *config, Kleo::CryptoConfigModule::Layout layout, bool &ok) { ok = true; if (num_components_with_options(config) < 2) { ok = false; return KPageView::Plain; } switch (layout) { case CryptoConfigModule::LinearizedLayout: return KPageView::Plain; case CryptoConfigModule::TabbedLayout: return KPageView::Tabbed; case CryptoConfigModule::IconListLayout: return KPageView::List; } Q_ASSERT(!"we should never get here"); return KPageView::List; } Kleo::CryptoConfigModule::CryptoConfigModule(QGpgME::CryptoConfig *config, QWidget *parent) : KPageWidget(parent) , mConfig(config) { init(IconListLayout); } Kleo::CryptoConfigModule::CryptoConfigModule(QGpgME::CryptoConfig *config, Layout layout, QWidget *parent) : KPageWidget(parent) , mConfig(config) { init(layout); } void Kleo::CryptoConfigModule::init(Layout layout) { if (QLayout *l = this->layout()) { l->setContentsMargins(0, 0, 0, 0); } QGpgME::CryptoConfig *const config = mConfig; bool configOK = false; const KPageView::FaceType type = determineJanusFace(config, layout, configOK); setFaceType(type); QVBoxLayout *vlay = nullptr; QWidget *vbox = nullptr; if (type == Plain) { QWidget *w = new QWidget(this); auto l = new QVBoxLayout(w); l->setContentsMargins(0, 0, 0, 0); auto s = new QScrollArea(w); s->setFrameStyle(QFrame::NoFrame); s->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); s->setWidgetResizable(true); l->addWidget(s); vbox = new QWidget(s->viewport()); vlay = new QVBoxLayout(vbox); vlay->setContentsMargins(0, 0, 0, 0); s->setWidget(vbox); addPage(w, configOK ? QString() : i18n("GpgConf Error")); } const QStringList components = sortComponentList(config->componentList()); for (QStringList::const_iterator it = components.begin(); it != components.end(); ++it) { // qCDebug(KLEO_UI_LOG) <<"Component" << (*it).toLocal8Bit() <<":"; QGpgME::CryptoConfigComponent *comp = config->component(*it); Q_ASSERT(comp); if (comp->groupList().empty()) { continue; } std::unique_ptr compGUI(new CryptoConfigComponentGUI(this, comp)); compGUI->setObjectName(*it); // KJanusWidget doesn't seem to have iterators, so we store a copy... mComponentGUIs.append(compGUI.get()); if (type == Plain) { QGroupBox *gb = new QGroupBox(comp->description(), vbox); (new QVBoxLayout(gb))->addWidget(compGUI.release()); vlay->addWidget(gb); } else { vbox = new QWidget(this); vlay = new QVBoxLayout(vbox); vlay->setContentsMargins(0, 0, 0, 0); KPageWidgetItem *pageItem = new KPageWidgetItem(vbox, comp->description()); if (type != Tabbed) { pageItem->setIcon(loadIcon(comp->iconName())); } addPage(pageItem); QScrollArea *scrollArea = type == Tabbed ? new QScrollArea(vbox) : new ScrollArea(vbox); scrollArea->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); scrollArea->setWidgetResizable(true); vlay->addWidget(scrollArea); const QSize compGUISize = compGUI->sizeHint(); scrollArea->setWidget(compGUI.release()); // Set a nice startup size const int deskHeight = screen()->size().height(); int dialogHeight; if (deskHeight > 1000) { // very big desktop ? dialogHeight = 800; } else if (deskHeight > 650) { // big desktop ? dialogHeight = 500; } else { // small (800x600, 640x480) desktop dialogHeight = 400; } Q_ASSERT(scrollArea->widget()); if (type != Tabbed) { scrollArea->setMinimumHeight(qMin(compGUISize.height(), dialogHeight)); } } } if (mComponentGUIs.empty()) { const QString msg = i18n( "The gpgconf tool used to provide the information " "for this dialog does not seem to be installed " "properly. It did not return any components. " "Try running \"%1\" on the command line for more " "information.", components.empty() ? QLatin1String("gpgconf --list-components") : QLatin1String("gpgconf --list-options gpg")); QLabel *label = new QLabel(msg, vbox); label->setWordWrap(true); label->setMinimumHeight(fontMetrics().lineSpacing() * 5); vlay->addWidget(label); } } namespace { template QStringList sortConfigEntries(const Iterator orderBegin, const Iterator orderEnd, const QStringList &entries) { // components sorting algorithm: // 1. components with predefined order (provided via orderBegin / orderEnd) // 2. other components sorted alphabetically QStringList result; QStringList others; for (auto it = orderBegin; it != orderEnd; ++it) { if (entries.contains(*it)) { result.append(*it); } } for (const auto &item : entries) { if (!result.contains(item)) { others.append(item); } } others.sort(); result.append(others); return result; } } // namespace QStringList Kleo::CryptoConfigModule::sortComponentList(const QStringList &components) { static const std::array order = { QStringLiteral("gpg"), QStringLiteral("gpgsm"), QStringLiteral("gpg-agent"), QStringLiteral("dirmngr"), QStringLiteral("pinentry"), QStringLiteral("scdaemon"), }; return sortConfigEntries(order.begin(), order.end(), components); } QStringList Kleo::CryptoConfigModule::sortGroupList(const QString &moduleName, const QStringList &groups) { if (moduleName == QStringLiteral("gpg")) { static const std::array order = { QStringLiteral("Keyserver"), QStringLiteral("Configuration"), QStringLiteral("Monitor"), QStringLiteral("Debug"), }; return sortConfigEntries(order.begin(), order.end(), groups); } else if (moduleName == QStringLiteral("gpgsm")) { static const std::array order = { QStringLiteral("Security"), QStringLiteral("Configuration"), QStringLiteral("Monitor"), QStringLiteral("Debug"), }; return sortConfigEntries(order.begin(), order.end(), groups); } else if (moduleName == QStringLiteral("gpg-agent")) { static const std::array order = { QStringLiteral("Security"), QStringLiteral("Passphrase policy"), QStringLiteral("Configuration"), QStringLiteral("Monitor"), QStringLiteral("Debug"), }; return sortConfigEntries(order.begin(), order.end(), groups); } else if (moduleName == QStringLiteral("dirmngr")) { static const std::array order = { QStringLiteral("Keyserver"), QStringLiteral("HTTP"), QStringLiteral("LDAP"), QStringLiteral("OCSP"), QStringLiteral("Tor"), QStringLiteral("Enforcement"), QStringLiteral("Configuration"), QStringLiteral("Format"), QStringLiteral("Monitor"), QStringLiteral("Debug"), }; return sortConfigEntries(order.begin(), order.end(), groups); } else if (moduleName == QStringLiteral("scdaemon")) { static const std::array order = { QStringLiteral("Monitor"), QStringLiteral("Configuration"), QStringLiteral("Security"), QStringLiteral("Debug"), }; return sortConfigEntries(order.begin(), order.end(), groups); } else { qCDebug(KLEO_UI_LOG) << "Configuration groups order is not defined for " << moduleName; QStringList result(groups); result.sort(); return result; } } bool Kleo::CryptoConfigModule::hasError() const { return mComponentGUIs.empty(); } void Kleo::CryptoConfigModule::save() { bool changed = false; QList::Iterator it = mComponentGUIs.begin(); for (; it != mComponentGUIs.end(); ++it) { if ((*it)->save()) { changed = true; } } if (changed) { mConfig->sync(true /*runtime*/); } } void Kleo::CryptoConfigModule::reset() { QList::Iterator it = mComponentGUIs.begin(); for (; it != mComponentGUIs.end(); ++it) { (*it)->load(); } } void Kleo::CryptoConfigModule::defaults() { QList::Iterator it = mComponentGUIs.begin(); for (; it != mComponentGUIs.end(); ++it) { (*it)->defaults(); } } void Kleo::CryptoConfigModule::cancel() { mConfig->clear(); } //// namespace { bool offerEntryForConfiguration(QGpgME::CryptoConfigEntry *entry) { static const QRegularExpression entryPathGroupSegmentRegexp{QStringLiteral("/.*/")}; static std::set entriesToExclude; if (entriesToExclude.empty()) { entriesToExclude.insert(QStringLiteral("gpg/keyserver")); if (engineIsVersion(2, 3, 5, GpgME::GpgConfEngine) || (engineIsVersion(2, 2, 34, GpgME::GpgConfEngine) && !engineIsVersion(2, 3, 0, GpgME::GpgConfEngine))) { // exclude for 2.2.{34,...} and 2.3.5+ entriesToExclude.insert(QStringLiteral("gpgsm/keyserver")); } } const bool de_vs = Kleo::gnupgUsesDeVsCompliance(); // Skip "dangerous" expert options if we are running in CO_DE_VS. // Otherwise, skip any options beyond "invisible" (== expert + 1) level. const auto maxEntryLevel = de_vs ? QGpgME::CryptoConfigEntry::Level_Advanced // : QGpgME::CryptoConfigEntry::Level_Expert + 1; // we ignore the group when looking up entries to exclude because entries // are uniquely identified by their name and their component const auto entryId = entry->path().replace(entryPathGroupSegmentRegexp, QLatin1String{"/"}).toLower(); return (entry->level() <= maxEntryLevel) && (entriesToExclude.find(entryId) == entriesToExclude.end()); } auto getGroupEntriesToOfferForConfiguration(QGpgME::CryptoConfigGroup *group) { std::vector result; const auto entryNames = group->entryList(); for (const auto &entryName : entryNames) { auto *const entry = group->entry(entryName); Q_ASSERT(entry); if (offerEntryForConfiguration(entry)) { result.push_back(entry); } else { qCDebug(KLEO_UI_LOG) << "entry" << entry->path() << "too advanced or excluded explicitly, skipping"; } } return result; } } Kleo::CryptoConfigComponentGUI::CryptoConfigComponentGUI(CryptoConfigModule *module, QGpgME::CryptoConfigComponent *component, QWidget *parent) : QWidget(parent) , mComponent(component) { auto glay = new QGridLayout(this); const QStringList groups = module->sortGroupList(mComponent->name(), mComponent->groupList()); if (groups.size() > 1) { glay->setColumnMinimumWidth(0, 30); for (QStringList::const_iterator it = groups.begin(), end = groups.end(); it != end; ++it) { QGpgME::CryptoConfigGroup *group = mComponent->group(*it); Q_ASSERT(group); if (!group) { continue; } auto groupEntries = getGroupEntriesToOfferForConfiguration(group); if (groupEntries.size() == 0) { // skip groups without entries to be offered in the UI continue; } const QString title = group->description(); auto hbox = new QHBoxLayout; hbox->addWidget(new QLabel{title.isEmpty() ? *it : title, this}); hbox->addWidget(new KSeparator{Qt::Horizontal, this}, 1); const int row = glay->rowCount(); glay->addLayout(hbox, row, 0, 1, 3); mGroupGUIs.append(new CryptoConfigGroupGUI(module, group, groupEntries, glay, this)); } } else if (!groups.empty()) { auto *const group = mComponent->group(groups.front()); auto groupEntries = getGroupEntriesToOfferForConfiguration(group); if (groupEntries.size() > 0) { mGroupGUIs.append(new CryptoConfigGroupGUI(module, group, groupEntries, glay, this)); } } glay->setRowStretch(glay->rowCount(), 1); } bool Kleo::CryptoConfigComponentGUI::save() { bool changed = false; QList::Iterator it = mGroupGUIs.begin(); for (; it != mGroupGUIs.end(); ++it) { if ((*it)->save()) { changed = true; } } return changed; } void Kleo::CryptoConfigComponentGUI::load() { QList::Iterator it = mGroupGUIs.begin(); for (; it != mGroupGUIs.end(); ++it) { (*it)->load(); } } void Kleo::CryptoConfigComponentGUI::defaults() { QList::Iterator it = mGroupGUIs.begin(); for (; it != mGroupGUIs.end(); ++it) { (*it)->defaults(); } } //// Kleo::CryptoConfigGroupGUI::CryptoConfigGroupGUI(CryptoConfigModule *module, QGpgME::CryptoConfigGroup *group, const std::vector &entries, QGridLayout *glay, QWidget *widget) : QObject(module) { const int startRow = glay->rowCount(); for (auto entry : entries) { CryptoConfigEntryGUI *entryGUI = CryptoConfigEntryGUIFactory::createEntryGUI(module, entry, entry->name(), glay, widget); if (entryGUI) { mEntryGUIs.append(entryGUI); entryGUI->load(); } } const int endRow = glay->rowCount() - 1; if (endRow < startRow) { return; } const QString iconName = group->iconName(); if (iconName.isEmpty()) { return; } QLabel *l = new QLabel(widget); l->setPixmap(loadIcon(iconName).pixmap(32, 32)); glay->addWidget(l, startRow, 0, endRow - startRow + 1, 1, Qt::AlignTop); } bool Kleo::CryptoConfigGroupGUI::save() { bool changed = false; QList::Iterator it = mEntryGUIs.begin(); for (; it != mEntryGUIs.end(); ++it) { if ((*it)->isChanged()) { (*it)->save(); changed = true; } } return changed; } void Kleo::CryptoConfigGroupGUI::load() { QList::Iterator it = mEntryGUIs.begin(); for (; it != mEntryGUIs.end(); ++it) { (*it)->load(); } } void Kleo::CryptoConfigGroupGUI::defaults() { QList::Iterator it = mEntryGUIs.begin(); for (; it != mEntryGUIs.end(); ++it) { (*it)->resetToDefault(); } } //// using constructor = CryptoConfigEntryGUI *(*)(CryptoConfigModule *, QGpgME::CryptoConfigEntry *, const QString &, QGridLayout *, QWidget *); namespace { template CryptoConfigEntryGUI *_create(CryptoConfigModule *m, QGpgME::CryptoConfigEntry *e, const QString &n, QGridLayout *l, QWidget *p) { return new T_Widget(m, e, n, l, p); } } static const struct WidgetsByEntryName { const char *entryGlob; constructor create; } widgetsByEntryName[] = { {"*/*/debug-level", &_create}, {"scdaemon/*/reader-port", &_create}, }; static const unsigned int numWidgetsByEntryName = sizeof widgetsByEntryName / sizeof *widgetsByEntryName; static const constructor listWidgets[QGpgME::CryptoConfigEntry::NumArgType] = { // None: A list of options with no arguments (e.g. -v -v -v) is shown as a spinbox &_create, nullptr, // String // Int/UInt: Let people type list of numbers (1,2,3....). Untested. &_create, &_create, nullptr, // Path nullptr, // Formerly URL &_create, nullptr, // DirPath }; static const constructor scalarWidgets[QGpgME::CryptoConfigEntry::NumArgType] = { // clang-format off &_create, // None &_create, // String &_create, // Int &_create, // UInt &_create, // Path nullptr, // Formerly URL nullptr, // LDAPURL &_create, // DirPath // clang-format on }; CryptoConfigEntryGUI *Kleo::CryptoConfigEntryGUIFactory::createEntryGUI(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) { Q_ASSERT(entry); // try to lookup by path: const QString path = entry->path(); for (unsigned int i = 0; i < numWidgetsByEntryName; ++i) { if (QRegExp(QLatin1String(widgetsByEntryName[i].entryGlob), Qt::CaseSensitive, QRegExp::Wildcard).exactMatch(path)) { return widgetsByEntryName[i].create(module, entry, entryName, glay, widget); } } // none found, so look up by type: const unsigned int argType = entry->argType(); Q_ASSERT(argType < QGpgME::CryptoConfigEntry::NumArgType); if (entry->isList()) { if (const constructor create = listWidgets[argType]) { return create(module, entry, entryName, glay, widget); } else { qCWarning(KLEO_UI_LOG) << "No widget implemented for list of type" << entry->argType(); } } else if (const constructor create = scalarWidgets[argType]) { return create(module, entry, entryName, glay, widget); } else { qCWarning(KLEO_UI_LOG) << "No widget implemented for type" << entry->argType(); } return nullptr; } //// Kleo::CryptoConfigEntryGUI::CryptoConfigEntryGUI(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName) : QObject(module) , mEntry(entry) , mName(entryName) , mChanged(false) { connect(this, &CryptoConfigEntryGUI::changed, module, &CryptoConfigModule::changed); } QString Kleo::CryptoConfigEntryGUI::description() const { QString descr = mEntry->description(); if (descr.isEmpty()) { // happens for expert options // String does not need to be translated because the options itself // are also not translated return QStringLiteral("\"%1\"").arg(mName); } if (i18nc("Translate this to 'yes' or 'no' (use the English words!) " "depending on whether your language uses " "Sentence style capitalization in GUI labels (yes) or not (no). " "Context: We get some backend strings in that have the wrong " "capitalization (in English, at least) so we need to force the " "first character to upper-case. It is this behaviour you can " "control for your language with this translation.", "yes") == QLatin1String("yes")) { descr[0] = descr[0].toUpper(); } return descr; } void Kleo::CryptoConfigEntryGUI::resetToDefault() { mEntry->resetToDefault(); load(); } //// Kleo::CryptoConfigEntryLineEdit::CryptoConfigEntryLineEdit(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) { const int row = glay->rowCount(); mLineEdit = new KLineEdit(widget); QLabel *label = new QLabel(description(), widget); label->setBuddy(mLineEdit); glay->addWidget(label, row, 1); glay->addWidget(mLineEdit, row, 2); if (entry->isReadOnly()) { label->setEnabled(false); mLineEdit->setEnabled(false); } else { connect(mLineEdit, &KLineEdit::textChanged, this, &CryptoConfigEntryLineEdit::slotChanged); } } void Kleo::CryptoConfigEntryLineEdit::doSave() { mEntry->setStringValue(mLineEdit->text()); } void Kleo::CryptoConfigEntryLineEdit::doLoad() { mLineEdit->setText(mEntry->stringValue()); } //// /* Note: Do not use "guru" as debug level but use the value 10. The former also enables the creation of hash dump files and thus leaves traces of plaintext on the disk. */ static const struct { const KLazyLocalizedString label; const char *name; } debugLevels[] = { {kli18n("0 - None"), "none"}, {kli18n("1 - Basic"), "basic"}, {kli18n("2 - Verbose"), "advanced"}, {kli18n("3 - More Verbose"), "expert"}, {kli18n("4 - All"), "10"}, }; static const unsigned int numDebugLevels = sizeof debugLevels / sizeof *debugLevels; Kleo::CryptoConfigEntryDebugLevel::CryptoConfigEntryDebugLevel(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) , mComboBox(new QComboBox(widget)) { QLabel *label = new QLabel(i18n("Set the debugging level to"), widget); label->setBuddy(mComboBox); for (unsigned int i = 0; i < numDebugLevels; ++i) { mComboBox->addItem(KLocalizedString(debugLevels[i].label).toString()); } if (entry->isReadOnly()) { label->setEnabled(false); mComboBox->setEnabled(false); } else { connect(mComboBox, qOverload(&QComboBox::currentIndexChanged), this, &CryptoConfigEntryDebugLevel::slotChanged); } const int row = glay->rowCount(); glay->addWidget(label, row, 1); glay->addWidget(mComboBox, row, 2); } void Kleo::CryptoConfigEntryDebugLevel::doSave() { const unsigned int idx = mComboBox->currentIndex(); if (idx < numDebugLevels) { mEntry->setStringValue(QLatin1String(debugLevels[idx].name)); } else { mEntry->setStringValue(QString()); } } void Kleo::CryptoConfigEntryDebugLevel::doLoad() { const QString str = mEntry->stringValue(); for (unsigned int i = 0; i < numDebugLevels; ++i) { if (str == QLatin1String(debugLevels[i].name)) { mComboBox->setCurrentIndex(i); return; } } mComboBox->setCurrentIndex(0); } //// Kleo::CryptoConfigEntryPath::CryptoConfigEntryPath(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) , mFileNameRequester(nullptr) { const int row = glay->rowCount(); mFileNameRequester = new FileNameRequester(widget); mFileNameRequester->setExistingOnly(false); mFileNameRequester->setFilter(QDir::Files); QLabel *label = new QLabel(description(), widget); label->setBuddy(mFileNameRequester); glay->addWidget(label, row, 1); glay->addWidget(mFileNameRequester, row, 2); if (entry->isReadOnly()) { label->setEnabled(false); mFileNameRequester->setEnabled(false); } else { connect(mFileNameRequester, &FileNameRequester::fileNameChanged, this, &CryptoConfigEntryPath::slotChanged); } } void Kleo::CryptoConfigEntryPath::doSave() { mEntry->setURLValue(QUrl::fromLocalFile(mFileNameRequester->fileName())); } void Kleo::CryptoConfigEntryPath::doLoad() { if (mEntry->urlValue().isLocalFile()) { mFileNameRequester->setFileName(mEntry->urlValue().toLocalFile()); } else { mFileNameRequester->setFileName(mEntry->urlValue().toString()); } } //// Kleo::CryptoConfigEntryDirPath::CryptoConfigEntryDirPath(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) , mFileNameRequester(nullptr) { const int row = glay->rowCount(); mFileNameRequester = new FileNameRequester(widget); mFileNameRequester->setExistingOnly(false); mFileNameRequester->setFilter(QDir::Dirs); QLabel *label = new QLabel(description(), widget); label->setBuddy(mFileNameRequester); glay->addWidget(label, row, 1); glay->addWidget(mFileNameRequester, row, 2); if (entry->isReadOnly()) { label->setEnabled(false); mFileNameRequester->setEnabled(false); } else { connect(mFileNameRequester, &FileNameRequester::fileNameChanged, this, &CryptoConfigEntryDirPath::slotChanged); } } void Kleo::CryptoConfigEntryDirPath::doSave() { mEntry->setURLValue(QUrl::fromLocalFile(mFileNameRequester->fileName())); } void Kleo::CryptoConfigEntryDirPath::doLoad() { mFileNameRequester->setFileName(mEntry->urlValue().toLocalFile()); } //// Kleo::CryptoConfigEntrySpinBox::CryptoConfigEntrySpinBox(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) { if (entry->argType() == QGpgME::CryptoConfigEntry::ArgType_None && entry->isList()) { mKind = ListOfNone; } else if (entry->argType() == QGpgME::CryptoConfigEntry::ArgType_UInt) { mKind = UInt; } else { Q_ASSERT(entry->argType() == QGpgME::CryptoConfigEntry::ArgType_Int); mKind = Int; } const int row = glay->rowCount(); mNumInput = new QSpinBox(widget); QLabel *label = new QLabel(description(), widget); label->setBuddy(mNumInput); glay->addWidget(label, row, 1); glay->addWidget(mNumInput, row, 2); if (entry->isReadOnly()) { label->setEnabled(false); mNumInput->setEnabled(false); } else { mNumInput->setMinimum(mKind == Int ? std::numeric_limits::min() : 0); mNumInput->setMaximum(std::numeric_limits::max()); connect(mNumInput, qOverload(&QSpinBox::valueChanged), this, &CryptoConfigEntrySpinBox::slotChanged); } } void Kleo::CryptoConfigEntrySpinBox::doSave() { int value = mNumInput->value(); switch (mKind) { case ListOfNone: mEntry->setNumberOfTimesSet(value); break; case UInt: mEntry->setUIntValue(value); break; case Int: mEntry->setIntValue(value); break; } } void Kleo::CryptoConfigEntrySpinBox::doLoad() { int value = 0; switch (mKind) { case ListOfNone: value = mEntry->numberOfTimesSet(); break; case UInt: value = mEntry->uintValue(); break; case Int: value = mEntry->intValue(); break; } mNumInput->setValue(value); } //// Kleo::CryptoConfigEntryCheckBox::CryptoConfigEntryCheckBox(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) { const int row = glay->rowCount(); mCheckBox = new QCheckBox(widget); glay->addWidget(mCheckBox, row, 1, 1, 2); mCheckBox->setText(description()); if (entry->isReadOnly()) { mCheckBox->setEnabled(false); } else { connect(mCheckBox, &QCheckBox::toggled, this, &CryptoConfigEntryCheckBox::slotChanged); } } void Kleo::CryptoConfigEntryCheckBox::doSave() { mEntry->setBoolValue(mCheckBox->isChecked()); } void Kleo::CryptoConfigEntryCheckBox::doLoad() { mCheckBox->setChecked(mEntry->boolValue()); } Kleo::CryptoConfigEntryLDAPURL::CryptoConfigEntryLDAPURL(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) { mLabel = new QLabel(widget); mPushButton = new QPushButton(entry->isReadOnly() ? i18n("Show...") : i18n("Edit..."), widget); const int row = glay->rowCount(); QLabel *label = new QLabel(description(), widget); label->setBuddy(mPushButton); glay->addWidget(label, row, 1); auto hlay = new QHBoxLayout; glay->addLayout(hlay, row, 2); hlay->addWidget(mLabel, 1); hlay->addWidget(mPushButton); if (entry->isReadOnly()) { mLabel->setEnabled(false); } connect(mPushButton, &QPushButton::clicked, this, &CryptoConfigEntryLDAPURL::slotOpenDialog); } void Kleo::CryptoConfigEntryLDAPURL::doLoad() { setURLList(mEntry->urlValueList()); } void Kleo::CryptoConfigEntryLDAPURL::doSave() { mEntry->setURLValueList(mURLList); } void prepareURLCfgDialog(QDialog *dialog, DirectoryServicesWidget *dirserv, bool readOnly) { QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok, dialog); if (!readOnly) { buttonBox->addButton(QDialogButtonBox::Cancel); buttonBox->addButton(QDialogButtonBox::RestoreDefaults); QPushButton *defaultsBtn = buttonBox->button(QDialogButtonBox::RestoreDefaults); QObject::connect(defaultsBtn, &QPushButton::clicked, dirserv, &DirectoryServicesWidget::clear); QObject::connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); } QObject::connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); auto layout = new QVBoxLayout; layout->addWidget(dirserv); layout->addWidget(buttonBox); dialog->setLayout(layout); } void Kleo::CryptoConfigEntryLDAPURL::slotOpenDialog() { if (!gpgme_check_version("1.16.0")) { KMessageBox::sorry(mPushButton->parentWidget(), i18n("Configuration of directory services is not possible " "because the used gpgme libraries are too old."), i18n("Sorry")); return; } // I'm a bad boy and I do it all on the stack. Enough classes already :) // This is just a simple dialog around the directory-services-widget QDialog dialog(mPushButton->parentWidget()); dialog.setWindowTitle(i18nc("@title:window", "Configure Directory Services")); auto dirserv = new DirectoryServicesWidget(&dialog); prepareURLCfgDialog(&dialog, dirserv, mEntry->isReadOnly()); dirserv->setReadOnly(mEntry->isReadOnly()); std::vector servers; std::transform(std::cbegin(mURLList), std::cend(mURLList), std::back_inserter(servers), [](const auto &url) { return KeyserverConfig::fromUrl(url); }); dirserv->setKeyservers(servers); if (dialog.exec()) { QList urls; const auto servers = dirserv->keyservers(); std::transform(std::begin(servers), std::end(servers), std::back_inserter(urls), [](const auto &server) { return server.toUrl(); }); setURLList(urls); slotChanged(); } } void Kleo::CryptoConfigEntryLDAPURL::setURLList(const QList &urlList) { mURLList = urlList; if (mURLList.isEmpty()) { mLabel->setText(i18n("None configured")); } else { mLabel->setText(i18np("1 server configured", "%1 servers configured", mURLList.count())); } } #include "moc_cryptoconfigmodule_p.cpp" diff --git a/src/ui/cryptoconfigmodule.h b/src/ui/cryptoconfigmodule.h index fedd97f6d..82db94ed1 100644 --- a/src/ui/cryptoconfigmodule.h +++ b/src/ui/cryptoconfigmodule.h @@ -1,59 +1,61 @@ /* cryptoconfigmodule.h This file is part of libkleopatra SPDX-FileCopyrightText: 2004, 2005 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" + +#include + #include -#include namespace QGpgME { class CryptoConfig; } // namespace QGpgME namespace Kleo { class CryptoConfigComponentGUI; /** * Crypto Config Module widget, dynamically generated from CryptoConfig * It's a simple QWidget so that it can be embedded into a dialog or into a KCModule. */ class KLEO_EXPORT CryptoConfigModule : public KPageWidget { Q_OBJECT public: enum Layout { TabbedLayout, IconListLayout, LinearizedLayout }; explicit CryptoConfigModule(QGpgME::CryptoConfig *config, QWidget *parent = nullptr); explicit CryptoConfigModule(QGpgME::CryptoConfig *config, Layout layout, QWidget *parent = nullptr); bool hasError() const; void save(); void reset(); // i.e. reload current settings, discarding user input void defaults(); void cancel(); Q_SIGNALS: void changed(); private: void init(Layout layout); static QStringList sortComponentList(const QStringList &components); public: static QStringList sortGroupList(const QString &moduleName, const QStringList &groups); private: QGpgME::CryptoConfig *mConfig = nullptr; QList mComponentGUIs; }; } diff --git a/src/ui/cryptoconfigmodule_p.h b/src/ui/cryptoconfigmodule_p.h index d566d2345..bb89708b8 100644 --- a/src/ui/cryptoconfigmodule_p.h +++ b/src/ui/cryptoconfigmodule_p.h @@ -1,293 +1,292 @@ /* cryptoconfigmodule_p.h This file is part of libkleopatra SPDX-FileCopyrightText: 2004, 2005 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include - #include +#include class KLineEdit; class QSpinBox; class QPushButton; class QGridLayout; class QLabel; class QCheckBox; class QComboBox; namespace Kleo { class FileNameRequester; } namespace QGpgME { class CryptoConfig; class CryptoConfigComponent; class CryptoConfigGroup; class CryptoConfigEntry; } // namespace QGpgME namespace Kleo { class CryptoConfigModule; class CryptoConfigComponentGUI; class CryptoConfigGroupGUI; class CryptoConfigEntryGUI; /** * A widget corresponding to a component in the crypto config */ class CryptoConfigComponentGUI : public QWidget { Q_OBJECT public: CryptoConfigComponentGUI(CryptoConfigModule *module, QGpgME::CryptoConfigComponent *component, QWidget *parent = nullptr); bool save(); void load(); void defaults(); private: QGpgME::CryptoConfigComponent *mComponent = nullptr; QList mGroupGUIs; }; /** * A class managing widgets corresponding to a group in the crypto config */ class CryptoConfigGroupGUI : public QObject { Q_OBJECT public: CryptoConfigGroupGUI(CryptoConfigModule *module, QGpgME::CryptoConfigGroup *group, const std::vector &entries, QGridLayout *layout, QWidget *parent = nullptr); bool save(); void load(); void defaults(); private: QList mEntryGUIs; }; /** * Factory for CryptoConfigEntryGUI instances * Not a real factory, but can become one later. */ class CryptoConfigEntryGUIFactory { public: static CryptoConfigEntryGUI * createEntryGUI(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *layout, QWidget *widget); }; /** * Base class for the widget managers tied to an entry in the crypto config */ class CryptoConfigEntryGUI : public QObject { Q_OBJECT public: CryptoConfigEntryGUI(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName); void load() { doLoad(); mChanged = false; } void save() { Q_ASSERT(mChanged); doSave(); mChanged = false; } void resetToDefault(); QString description() const; bool isChanged() const { return mChanged; } Q_SIGNALS: void changed(); protected Q_SLOTS: void slotChanged() { mChanged = true; Q_EMIT changed(); } protected: virtual void doSave() = 0; virtual void doLoad() = 0; QGpgME::CryptoConfigEntry *mEntry = nullptr; QString mName; bool mChanged = false; }; /** * A widget manager for a string entry in the crypto config */ class CryptoConfigEntryLineEdit : public CryptoConfigEntryGUI { Q_OBJECT public: CryptoConfigEntryLineEdit(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *layout, QWidget *parent = nullptr); void doSave() override; void doLoad() override; private: KLineEdit *mLineEdit = nullptr; }; /** * A widget manager for a debug-level entry in the crypto config */ class CryptoConfigEntryDebugLevel : public CryptoConfigEntryGUI { Q_OBJECT public: CryptoConfigEntryDebugLevel(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *layout, QWidget *parent = nullptr); void doSave() override; void doLoad() override; private: QComboBox *mComboBox = nullptr; }; /** * A widget manager for a path entry in the crypto config */ class CryptoConfigEntryPath : public CryptoConfigEntryGUI { Q_OBJECT public: CryptoConfigEntryPath(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *layout, QWidget *parent = nullptr); void doSave() override; void doLoad() override; private: Kleo::FileNameRequester *mFileNameRequester = nullptr; }; /** * A widget manager for a directory path entry in the crypto config */ class CryptoConfigEntryDirPath : public CryptoConfigEntryGUI { Q_OBJECT public: CryptoConfigEntryDirPath(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *layout, QWidget *parent = nullptr); void doSave() override; void doLoad() override; private: Kleo::FileNameRequester *mFileNameRequester = nullptr; }; /** * A widget manager for an int/uint entry in the crypto config */ class CryptoConfigEntrySpinBox : public CryptoConfigEntryGUI { Q_OBJECT public: CryptoConfigEntrySpinBox(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *layout, QWidget *parent = nullptr); void doSave() override; void doLoad() override; private: enum { Int, UInt, ListOfNone } mKind; QSpinBox *mNumInput = nullptr; }; /** * A widget manager for a bool entry in the crypto config */ class CryptoConfigEntryCheckBox : public CryptoConfigEntryGUI { Q_OBJECT public: CryptoConfigEntryCheckBox(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *layout, QWidget *parent = nullptr); void doSave() override; void doLoad() override; private: QCheckBox *mCheckBox = nullptr; }; /** * A widget manager for an LDAP list entry in the crypto config */ class CryptoConfigEntryLDAPURL : public CryptoConfigEntryGUI { Q_OBJECT public: CryptoConfigEntryLDAPURL(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *layout, QWidget *parent = nullptr); void doSave() override; void doLoad() override; private Q_SLOTS: void slotOpenDialog(); private: void setURLList(const QList &urlList); QLabel *mLabel = nullptr; QPushButton *mPushButton = nullptr; QList mURLList; }; } diff --git a/src/ui/directoryserviceswidget.cpp b/src/ui/directoryserviceswidget.cpp index b2c31c622..e456e6d40 100644 --- a/src/ui/directoryserviceswidget.cpp +++ b/src/ui/directoryserviceswidget.cpp @@ -1,412 +1,412 @@ /* ui/directoryserviceswidget.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2001, 2002, 2004 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2017 Bundesamnt für Sicherheit in der Informationstechnik SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "directoryserviceswidget.h" #include "editdirectoryservicedialog.h" -#include -#include +#include +#include #include #include #include #include #include #include #include #include #include using namespace Kleo; namespace { bool activeDirectoryIsSupported() { return engineIsVersion(2, 2, 28, GpgME::GpgSMEngine); } bool isStandardActiveDirectory(const KeyserverConfig &keyserver) { return (keyserver.authentication() == KeyserverAuthentication::ActiveDirectory) && keyserver.host().isEmpty(); } bool keyserverIsEditable(const KeyserverConfig &keyserver) { // standard AD is not editable return !isStandardActiveDirectory(keyserver); } class KeyserverModel : public QAbstractListModel { Q_OBJECT public: explicit KeyserverModel(QObject *parent = nullptr) : QAbstractListModel{parent} { } void setKeyservers(const std::vector &servers) { clear(); beginInsertRows(QModelIndex(), 0, servers.size() - 1); m_items = servers; endInsertRows(); } void addKeyserver(const KeyserverConfig &keyserver) { const auto row = m_items.size(); beginInsertRows(QModelIndex(), row, row); m_items.push_back(keyserver); endInsertRows(); } KeyserverConfig getKeyserver(unsigned int id) { if (id >= m_items.size()) { qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id; return {}; } return m_items[id]; } void updateKeyserver(unsigned int id, const KeyserverConfig &keyserver) { if (id >= m_items.size()) { qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id; return; } m_items[id] = keyserver; Q_EMIT dataChanged(index(id), index(id)); } void deleteKeyserver(unsigned int id) { if (id >= m_items.size()) { qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id; return; } beginRemoveRows(QModelIndex(), id, id); m_items.erase(m_items.begin() + id); endRemoveRows(); } void clear() { if (m_items.empty()) { return; } beginRemoveRows(QModelIndex(), 0, m_items.size() - 1); m_items.clear(); endRemoveRows(); } int rowCount(const QModelIndex & = QModelIndex()) const override { return m_items.size(); } QVariant data(const QModelIndex &index, int role) const override { if (!index.isValid()) { return {}; } switch (role) { case Qt::DisplayRole: case Qt::EditRole: { const auto keyserver = m_items[index.row()]; return isStandardActiveDirectory(keyserver) ? i18n("Active Directory") : keyserver.host(); } } return {}; } bool hasActiveDirectory() { // check whether any of the model items represents an Active Directory keyserver return std::any_of(std::cbegin(m_items), std::cend(m_items), isStandardActiveDirectory); } private: using QAbstractListModel::setData; private: std::vector m_items; }; } class DirectoryServicesWidget::Private { DirectoryServicesWidget *const q; struct { QListView *keyserverList = nullptr; QToolButton *newButton = nullptr; QAction *addActiveDirectoryAction = nullptr; QAction *addLdapServerAction = nullptr; QPushButton *editButton = nullptr; QPushButton *deleteButton = nullptr; } ui; KeyserverModel *keyserverModel = nullptr; bool readOnly = false; public: Private(DirectoryServicesWidget *qq) : q(qq) { auto mainLayout = new QVBoxLayout{q}; auto gridLayout = new QGridLayout{}; gridLayout->setColumnStretch(0, 1); gridLayout->setRowStretch(1, 1); keyserverModel = new KeyserverModel{q}; ui.keyserverList = new QListView(); ui.keyserverList->setModel(keyserverModel); ui.keyserverList->setModelColumn(0); ui.keyserverList->setSelectionBehavior(QAbstractItemView::SelectRows); ui.keyserverList->setSelectionMode(QAbstractItemView::SingleSelection); ui.keyserverList->setWhatsThis(i18nc("@info:whatsthis", "This is a list of all directory services that are configured for use with X.509.")); gridLayout->addWidget(ui.keyserverList, 1, 0); auto groupsButtonLayout = new QVBoxLayout(); auto menu = new QMenu{q}; ui.addActiveDirectoryAction = menu->addAction(i18n("Active Directory"), [this]() { addActiveDirectory(); }); ui.addActiveDirectoryAction->setToolTip(i18nc("@info:tooltip", "Click to use a directory service running on your Active Directory. " "This works only on Windows and requires GnuPG 2.2.28 or later.")); ui.addActiveDirectoryAction->setEnabled(activeDirectoryIsSupported()); ui.addLdapServerAction = menu->addAction(i18n("LDAP Server"), [this]() { addLdapServer(); }); ui.addLdapServerAction->setToolTip(i18nc("@info:tooltip", "Click to add a directory service provided by an LDAP server.")); ui.newButton = new QToolButton{q}; ui.newButton->setText(i18n("Add")); ui.newButton->setToolTip(i18nc("@info:tooltip", "Click to add a directory service.")); ui.newButton->setWhatsThis(i18nc("@info:whatsthis", "Click this button to add a directory service to the list of services. " "The change will only take effect once you acknowledge the configuration dialog.")); ui.newButton->setToolButtonStyle(Qt::ToolButtonTextOnly); ui.newButton->setPopupMode(QToolButton::InstantPopup); ui.newButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); // expand horizontally like the QPushButtons ui.newButton->setMenu(menu); groupsButtonLayout->addWidget(ui.newButton); ui.editButton = new QPushButton(i18n("Edit")); ui.editButton->setToolTip(i18nc("@info:tooltip", "Click to edit the selected service.")); ui.editButton->setWhatsThis(i18nc("@info:whatsthis", "Click this button to edit the settings of the currently selected directory service. " "The changes will only take effect once you acknowledge the configuration dialog.")); ui.editButton->setEnabled(false); groupsButtonLayout->addWidget(ui.editButton); ui.deleteButton = new QPushButton(i18n("Delete")); ui.deleteButton->setToolTip(i18nc("@info:tooltip", "Click to remove the selected service.")); ui.deleteButton->setWhatsThis(i18nc("@info:whatsthis", "Click this button to remove the currently selected directory service. " "The change will only take effect once you acknowledge the configuration dialog.")); ui.deleteButton->setEnabled(false); groupsButtonLayout->addWidget(ui.deleteButton); groupsButtonLayout->addStretch(1); gridLayout->addLayout(groupsButtonLayout, 1, 1); mainLayout->addLayout(gridLayout, /*stretch=*/1); connect(keyserverModel, &QAbstractItemModel::dataChanged, q, [this]() { modelChanged(); }); connect(keyserverModel, &QAbstractItemModel::rowsInserted, q, [this]() { modelChanged(); }); connect(keyserverModel, &QAbstractItemModel::rowsRemoved, q, [this]() { modelChanged(); }); connect(ui.keyserverList->selectionModel(), &QItemSelectionModel::selectionChanged, q, [this]() { selectionChanged(); }); connect(ui.keyserverList, &QListView::doubleClicked, q, [this](const QModelIndex &index) { if (!readOnly) { editKeyserver(index); } }); connect(ui.editButton, &QPushButton::clicked, q, [this]() { editKeyserver(); }); connect(ui.deleteButton, &QPushButton::clicked, q, [this]() { deleteKeyserver(); }); } void setReadOnly(bool ro) { readOnly = ro; updateActions(); } void setKeyservers(const std::vector &servers) { keyserverModel->setKeyservers(servers); } std::vector keyservers() const { std::vector result; result.reserve(keyserverModel->rowCount()); for (int row = 0; row < keyserverModel->rowCount(); ++row) { result.push_back(keyserverModel->getKeyserver(row)); } return result; } void clear() { if (keyserverModel->rowCount() == 0) { return; } keyserverModel->clear(); } private: auto selectedIndex() { const auto indexes = ui.keyserverList->selectionModel()->selectedRows(); return indexes.empty() ? QModelIndex() : indexes[0]; } void modelChanged() { updateActions(); Q_EMIT q->changed(); } void selectionChanged() { updateActions(); } void updateActions() { const auto index = selectedIndex(); ui.newButton->setEnabled(!readOnly); ui.addActiveDirectoryAction->setEnabled(activeDirectoryIsSupported() && !keyserverModel->hasActiveDirectory()); ui.editButton->setEnabled(!readOnly && index.isValid() && keyserverIsEditable(keyserverModel->getKeyserver(index.row()))); ui.deleteButton->setEnabled(!readOnly && index.isValid()); } void handleEditKeyserverDialogResult(const int id, const EditDirectoryServiceDialog *dialog) { if (id >= 0) { keyserverModel->updateKeyserver(id, dialog->keyserver()); } else { keyserverModel->addKeyserver(dialog->keyserver()); } } void showEditKeyserverDialog(const int id, const KeyserverConfig &keyserver, const QString &windowTitle) { QPointer dialog{new EditDirectoryServiceDialog{q}}; dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowModality(Qt::WindowModal); dialog->setWindowTitle(windowTitle); dialog->setKeyserver(keyserver); connect(dialog, &QDialog::accepted, q, [dialog, id, this] { handleEditKeyserverDialogResult(id, dialog); }); dialog->show(); } void addActiveDirectory() { KeyserverConfig keyserver; keyserver.setAuthentication(KeyserverAuthentication::ActiveDirectory); keyserverModel->addKeyserver(keyserver); } void addLdapServer() { showEditKeyserverDialog(-1, {}, i18nc("@title:window", "LDAP Directory Service")); } void editKeyserver(const QModelIndex &index = {}) { const auto serverIndex = index.isValid() ? index : selectedIndex(); if (!serverIndex.isValid()) { qCDebug(KLEO_UI_LOG) << __func__ << "selection is empty"; return; } const auto id = serverIndex.row(); const KeyserverConfig keyserver = keyserverModel->getKeyserver(id); if (!keyserverIsEditable(keyserver)) { qCDebug(KLEO_UI_LOG) << __func__ << "selected keyserver (id:" << id << ") cannot be modified"; return; } showEditKeyserverDialog(id, keyserver, i18nc("@title:window", "LDAP Directory Service")); } void deleteKeyserver() { const QModelIndex serverIndex = selectedIndex(); if (!serverIndex.isValid()) { qCDebug(KLEO_UI_LOG) << __func__ << "selection is empty"; return; } keyserverModel->deleteKeyserver(serverIndex.row()); } }; DirectoryServicesWidget::DirectoryServicesWidget(QWidget *parent) : QWidget{parent} , d{std::make_unique(this)} { } DirectoryServicesWidget::~DirectoryServicesWidget() = default; void DirectoryServicesWidget::setKeyservers(const std::vector &servers) { d->setKeyservers(servers); } std::vector DirectoryServicesWidget::keyservers() const { return d->keyservers(); } void DirectoryServicesWidget::setReadOnly(bool readOnly) { d->setReadOnly(readOnly); } void DirectoryServicesWidget::clear() { d->clear(); } #include "directoryserviceswidget.moc" diff --git a/src/ui/dnattributeorderconfigwidget.cpp b/src/ui/dnattributeorderconfigwidget.cpp index e371edabb..3a9722a83 100644 --- a/src/ui/dnattributeorderconfigwidget.cpp +++ b/src/ui/dnattributeorderconfigwidget.cpp @@ -1,343 +1,342 @@ /* -*- c++ -*- dnattributeorderconfigwidget.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 #include "dnattributeorderconfigwidget.h" -#include "libkleo/dn.h" +#include #include +#include #include -#include #include +#include +#include #include #include - -#include -#include #include class Kleo::DNAttributeOrderConfigWidget::DNAttributeOrderConfigWidgetPrivate { public: enum { UUp = 0, Up = 1, Left = 2, Right = 3, Down = 4, DDown = 5 }; #ifndef QT_NO_TREEWIDGET QTreeWidget *availableLV = nullptr; QTreeWidget *currentLV = nullptr; #endif QToolButton *navTB[6]; #ifndef QT_NO_TREEWIDGET QTreeWidgetItem *placeHolderItem = nullptr; #endif }; #ifndef QT_NO_TREEWIDGET static void prepare(QTreeWidget *lv) { lv->setAllColumnsShowFocus(true); lv->header()->setStretchLastSection(true); lv->setHeaderLabels(QStringList() << QString() << i18n("Description")); } #endif Kleo::DNAttributeOrderConfigWidget::DNAttributeOrderConfigWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) , d(new DNAttributeOrderConfigWidgetPrivate) { auto glay = new QGridLayout(this); glay->setContentsMargins(0, 0, 0, 0); glay->setColumnStretch(0, 1); glay->setColumnStretch(2, 1); int row = -1; ++row; glay->addWidget(new QLabel(i18n("Available attributes:"), this), row, 0); glay->addWidget(new QLabel(i18n("Current attribute order:"), this), row, 2); ++row; glay->setRowStretch(row, 1); #ifndef QT_NO_TREEWIDGET d->availableLV = new QTreeWidget(this); prepare(d->availableLV); d->availableLV->sortItems(0, Qt::AscendingOrder); glay->addWidget(d->availableLV, row, 0); d->currentLV = new QTreeWidget(this); prepare(d->currentLV); glay->addWidget(d->currentLV, row, 2); connect(d->availableLV, &QTreeWidget::itemClicked, this, &DNAttributeOrderConfigWidget::slotAvailableSelectionChanged); connect(d->currentLV, &QTreeWidget::itemClicked, this, &DNAttributeOrderConfigWidget::slotCurrentOrderSelectionChanged); d->placeHolderItem = new QTreeWidgetItem(d->availableLV); d->placeHolderItem->setText(0, QStringLiteral("_X_")); d->placeHolderItem->setText(1, i18n("All others")); #endif // the up/down/left/right arrow cross: auto xlay = new QGridLayout(); xlay->setSpacing(0); xlay->setObjectName(QStringLiteral("xlay")); xlay->setAlignment(Qt::AlignCenter); static const struct { const char *icon; int row, col; const KLazyLocalizedString tooltip; void (DNAttributeOrderConfigWidget::*slot)(); bool autorepeat; } navButtons[] = { {"go-top", 0, 1, kli18n("Move to top"), &DNAttributeOrderConfigWidget::slotDoubleUpButtonClicked, false}, {"go-up", 1, 1, kli18n("Move one up"), &DNAttributeOrderConfigWidget::slotUpButtonClicked, true}, {"go-previous", 2, 0, kli18n("Remove from current attribute order"), &DNAttributeOrderConfigWidget::slotLeftButtonClicked, false}, {"go-next", 2, 2, kli18n("Add to current attribute order"), &DNAttributeOrderConfigWidget::slotRightButtonClicked, false}, {"go-down", 3, 1, kli18n("Move one down"), &DNAttributeOrderConfigWidget::slotDownButtonClicked, true}, {"go-bottom", 4, 1, kli18n("Move to bottom"), &DNAttributeOrderConfigWidget::slotDoubleDownButtonClicked, false}, }; for (unsigned int i = 0; i < sizeof navButtons / sizeof *navButtons; ++i) { QToolButton *tb = d->navTB[i] = new QToolButton(this); tb->setIcon(QIcon::fromTheme(QLatin1String(navButtons[i].icon))); tb->setEnabled(false); tb->setToolTip(KLocalizedString(navButtons[i].tooltip).toString()); xlay->addWidget(tb, navButtons[i].row, navButtons[i].col); tb->setAutoRepeat(navButtons[i].autorepeat); connect(tb, &QToolButton::clicked, this, navButtons[i].slot); } glay->addLayout(xlay, row, 1); } Kleo::DNAttributeOrderConfigWidget::~DNAttributeOrderConfigWidget() = default; void Kleo::DNAttributeOrderConfigWidget::setAttributeOrder(const QStringList &order) { #ifndef QT_NO_TREEWIDGET // save the _X_ item: takePlaceHolderItem(); // clear the rest: d->availableLV->clear(); d->currentLV->clear(); // fill the RHS listview: QTreeWidgetItem *last = nullptr; for (QStringList::const_iterator it = order.begin(); it != order.end(); ++it) { const QString attr = (*it).toUpper(); if (attr == QLatin1String("_X_")) { takePlaceHolderItem(); d->currentLV->insertTopLevelItem(d->currentLV->topLevelItemCount(), d->placeHolderItem); last = d->placeHolderItem; } else { last = new QTreeWidgetItem(d->currentLV, last); last->setText(0, attr); last->setText(1, DN::attributeNameToLabel(attr)); } } // fill the LHS listview with what's left: const QStringList all = DN::attributeNames(); const QStringList::const_iterator end(all.end()); for (QStringList::const_iterator it = all.begin(); it != end; ++it) { if (!order.contains(*it)) { auto item = new QTreeWidgetItem(d->availableLV); item->setText(0, *it); item->setText(1, DN::attributeNameToLabel(*it)); } } if (!d->placeHolderItem->treeWidget()) { d->availableLV->addTopLevelItem(d->placeHolderItem); } #endif } void Kleo::DNAttributeOrderConfigWidget::takePlaceHolderItem() { #ifndef QT_NO_TREEWIDGET if (QTreeWidget *lv = d->placeHolderItem->treeWidget()) { lv->takeTopLevelItem(lv->indexOfTopLevelItem(d->placeHolderItem)); } #endif } QStringList Kleo::DNAttributeOrderConfigWidget::attributeOrder() const { QStringList order; #ifndef QT_NO_TREEWIDGET for (QTreeWidgetItemIterator it(d->currentLV); (*it); ++it) { order.push_back((*it)->text(0)); } #endif return order; } void Kleo::DNAttributeOrderConfigWidget::slotAvailableSelectionChanged(QTreeWidgetItem *item) { d->navTB[DNAttributeOrderConfigWidgetPrivate::Right]->setEnabled(item); } void Kleo::DNAttributeOrderConfigWidget::slotCurrentOrderSelectionChanged(QTreeWidgetItem *item) { enableDisableButtons(item); } void Kleo::DNAttributeOrderConfigWidget::enableDisableButtons(QTreeWidgetItem *item) { #ifndef QT_NO_TREEWIDGET d->navTB[DNAttributeOrderConfigWidgetPrivate::UUp]->setEnabled(item && d->currentLV->itemAbove(item)); d->navTB[DNAttributeOrderConfigWidgetPrivate::Up]->setEnabled(item && d->currentLV->itemAbove(item)); d->navTB[DNAttributeOrderConfigWidgetPrivate::Left]->setEnabled(item); d->navTB[DNAttributeOrderConfigWidgetPrivate::Down]->setEnabled(item && d->currentLV->itemBelow(item)); d->navTB[DNAttributeOrderConfigWidgetPrivate::DDown]->setEnabled(item && d->currentLV->itemBelow(item)); #endif } void Kleo::DNAttributeOrderConfigWidget::slotUpButtonClicked() { #ifndef QT_NO_TREEWIDGET if (d->currentLV->selectedItems().isEmpty()) { return; } QTreeWidgetItem *item = d->currentLV->selectedItems().first(); int itemIndex = d->currentLV->indexOfTopLevelItem(item); if (itemIndex <= 0) { return; } d->currentLV->takeTopLevelItem(itemIndex); d->currentLV->insertTopLevelItem(itemIndex - 1, item); d->currentLV->clearSelection(); item->setSelected(true); enableDisableButtons(item); Q_EMIT changed(); #endif } void Kleo::DNAttributeOrderConfigWidget::slotDoubleUpButtonClicked() { #ifndef QT_NO_TREEWIDGET if (d->currentLV->selectedItems().isEmpty()) { return; } QTreeWidgetItem *item = d->currentLV->selectedItems().first(); int itemIndex = d->currentLV->indexOfTopLevelItem(item); if (itemIndex == 0) { return; } d->currentLV->takeTopLevelItem(itemIndex); d->currentLV->insertTopLevelItem(0, item); d->currentLV->clearSelection(); item->setSelected(true); enableDisableButtons(item); Q_EMIT changed(); #endif } void Kleo::DNAttributeOrderConfigWidget::slotDownButtonClicked() { #ifndef QT_NO_TREEWIDGET if (d->currentLV->selectedItems().isEmpty()) { return; } QTreeWidgetItem *item = d->currentLV->selectedItems().first(); int itemIndex = d->currentLV->indexOfTopLevelItem(item); if (itemIndex + 1 >= d->currentLV->topLevelItemCount()) { return; } d->currentLV->takeTopLevelItem(itemIndex); d->currentLV->insertTopLevelItem(itemIndex + 1, item); d->currentLV->clearSelection(); item->setSelected(true); enableDisableButtons(item); Q_EMIT changed(); #endif } void Kleo::DNAttributeOrderConfigWidget::slotDoubleDownButtonClicked() { #ifndef QT_NO_TREEWIDGET if (d->currentLV->selectedItems().isEmpty()) { return; } QTreeWidgetItem *item = d->currentLV->selectedItems().first(); const int itemIndex = d->currentLV->indexOfTopLevelItem(item); if (itemIndex + 1 >= d->currentLV->topLevelItemCount()) { return; } d->currentLV->takeTopLevelItem(itemIndex); d->currentLV->addTopLevelItem(item); d->currentLV->clearSelection(); item->setSelected(true); enableDisableButtons(item); Q_EMIT changed(); #endif } void Kleo::DNAttributeOrderConfigWidget::slotLeftButtonClicked() { #ifndef QT_NO_TREEWIDGET if (d->currentLV->selectedItems().isEmpty()) { return; } QTreeWidgetItem *right = d->currentLV->selectedItems().first(); QTreeWidgetItem *next = d->currentLV->itemBelow(right); if (!next) { next = d->currentLV->itemAbove(right); } d->currentLV->takeTopLevelItem(d->currentLV->indexOfTopLevelItem(right)); d->availableLV->addTopLevelItem(right); d->availableLV->sortItems(0, Qt::AscendingOrder); if (next) { next->setSelected(true); } enableDisableButtons(next); Q_EMIT changed(); #endif } void Kleo::DNAttributeOrderConfigWidget::slotRightButtonClicked() { #ifndef QT_NO_TREEWIDGET if (d->availableLV->selectedItems().isEmpty()) { return; } QTreeWidgetItem *left = d->availableLV->selectedItems().first(); QTreeWidgetItem *next = d->availableLV->itemBelow(left); if (!next) { next = d->availableLV->itemAbove(left); } d->availableLV->takeTopLevelItem(d->availableLV->indexOfTopLevelItem(left)); int newRightIndex = d->currentLV->topLevelItemCount(); if (!d->currentLV->selectedItems().isEmpty()) { QTreeWidgetItem *right = d->currentLV->selectedItems().first(); newRightIndex = d->currentLV->indexOfTopLevelItem(right); right->setSelected(false); } d->currentLV->insertTopLevelItem(newRightIndex, left); left->setSelected(true); enableDisableButtons(left); d->navTB[DNAttributeOrderConfigWidgetPrivate::Right]->setEnabled(next); if (next) { next->setSelected(true); } Q_EMIT changed(); #endif } void Kleo::DNAttributeOrderConfigWidget::virtual_hook(int, void *) { } diff --git a/src/ui/editdirectoryservicedialog.cpp b/src/ui/editdirectoryservicedialog.cpp index 245012624..56d841067 100644 --- a/src/ui/editdirectoryservicedialog.cpp +++ b/src/ui/editdirectoryservicedialog.cpp @@ -1,409 +1,409 @@ /* ui/editdirectoryservicedialog.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "editdirectoryservicedialog.h" -#include -#include -#include +#include +#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; namespace { int defaultPort(KeyserverConnection connection) { return connection == KeyserverConnection::TunnelThroughTLS ? 636 : 389; } } class EditDirectoryServiceDialog::Private { EditDirectoryServiceDialog *const q; struct Ui { QLineEdit *hostEdit = nullptr; QSpinBox *portSpinBox = nullptr; QCheckBox *useDefaultPortCheckBox = nullptr; QButtonGroup *authenticationGroup = nullptr; QLineEdit *userEdit = nullptr; KPasswordLineEdit *passwordEdit = nullptr; QButtonGroup *connectionGroup = nullptr; KCollapsibleGroupBox *advancedSettings = nullptr; QLineEdit *baseDnEdit = nullptr; QLineEdit *additionalFlagsEdit = nullptr; QDialogButtonBox *buttonBox = nullptr; Ui(QWidget *parent) : hostEdit{new QLineEdit{parent}} , portSpinBox{new QSpinBox{parent}} , useDefaultPortCheckBox{new QCheckBox{parent}} , authenticationGroup{new QButtonGroup{parent}} , userEdit{new QLineEdit{parent}} , passwordEdit{new KPasswordLineEdit{parent}} , connectionGroup{new QButtonGroup{parent}} , advancedSettings{new KCollapsibleGroupBox{parent}} , baseDnEdit{new QLineEdit{parent}} , additionalFlagsEdit{new QLineEdit{parent}} , buttonBox{new QDialogButtonBox{parent}} { #define SET_OBJECT_NAME(x) x->setObjectName(QStringLiteral(#x)); SET_OBJECT_NAME(hostEdit) SET_OBJECT_NAME(portSpinBox) SET_OBJECT_NAME(useDefaultPortCheckBox) SET_OBJECT_NAME(authenticationGroup) SET_OBJECT_NAME(userEdit) SET_OBJECT_NAME(passwordEdit) SET_OBJECT_NAME(connectionGroup) SET_OBJECT_NAME(advancedSettings) SET_OBJECT_NAME(baseDnEdit) SET_OBJECT_NAME(additionalFlagsEdit) SET_OBJECT_NAME(buttonBox) #undef SET_OBJECT_NAME auto mainLayout = new QVBoxLayout{parent}; auto serverWidget = new QWidget{parent}; { auto layout = new QGridLayout{serverWidget}; layout->setColumnStretch(2, 1); int row = 0; layout->addWidget(new QLabel{i18n("Host:")}, row, 0); hostEdit->setToolTip(i18nc("@info:tooltip", "Enter the name or IP address of the server hosting the directory service.")); hostEdit->setClearButtonEnabled(true); layout->addWidget(hostEdit, row, 1, 1, -1); ++row; layout->addWidget(new QLabel{i18n("Port:")}, row, 0); portSpinBox->setRange(1, USHRT_MAX); portSpinBox->setToolTip(i18nc("@info:tooltip", "(Optional, the default is fine in most cases) " "Pick the port number the directory service is listening on.")); layout->addWidget(portSpinBox, row, 1); useDefaultPortCheckBox->setText(i18n("Use default")); useDefaultPortCheckBox->setChecked(true); layout->addWidget(useDefaultPortCheckBox, row, 2); } mainLayout->addWidget(serverWidget); auto authenticationWidget = new QGroupBox{i18n("Authentication"), parent}; { auto layout = new QVBoxLayout{authenticationWidget}; { auto radioButton = new QRadioButton{i18n("Anonymous")}; radioButton->setToolTip(i18nc("@info:tooltip", "Use an anonymous LDAP server that does not require authentication.")); radioButton->setChecked(true); authenticationGroup->addButton(radioButton, static_cast(KeyserverAuthentication::Anonymous)); layout->addWidget(radioButton); } { auto radioButton = new QRadioButton{i18n("Authenticate via Active Directory")}; if (!engineIsVersion(2, 2, 28, GpgME::GpgSMEngine)) { radioButton->setText(i18n("Authenticate via Active Directory (requires GnuPG 2.2.28 or later)")); } radioButton->setToolTip( i18nc("@info:tooltip", "On Windows, authenticate to the LDAP server using the Active Directory with the current user.")); authenticationGroup->addButton(radioButton, static_cast(KeyserverAuthentication::ActiveDirectory)); layout->addWidget(radioButton); } { auto radioButton = new QRadioButton{i18n("Authenticate with user and password")}; radioButton->setToolTip(i18nc("@info:tooltip", "Authenticate to the LDAP server with your LDAP credentials.")); authenticationGroup->addButton(radioButton, static_cast(KeyserverAuthentication::Password)); layout->addWidget(radioButton); } auto credentialsWidget = new QWidget{parent}; { auto layout = new QGridLayout{credentialsWidget}; layout->setColumnStretch(1, 1); int row = 0; layout->addWidget(new QLabel{i18n("User:")}, row, 0); userEdit->setToolTip(i18nc("@info:tooltip", "Enter your LDAP user resp. Bind DN for authenticating to the LDAP server.")); userEdit->setClearButtonEnabled(true); layout->addWidget(userEdit, row, 1); ++row; layout->addWidget(new QLabel{i18n("Password:")}, row, 0); passwordEdit->setToolTip(xi18nc("@info:tooltip", "Enter your password for authenticating to the LDAP server." "The password will be saved in the clear " "in a configuration file in your home directory.")); passwordEdit->setClearButtonEnabled(true); layout->addWidget(passwordEdit, row, 1); } layout->addWidget(credentialsWidget); } mainLayout->addWidget(authenticationWidget); auto securityWidget = new QGroupBox{i18n("Connection Security"), parent}; if (!engineIsVersion(2, 2, 28, GpgME::GpgSMEngine)) { securityWidget->setTitle(i18n("Connection Security (requires GnuPG 2.2.28 or later)")); } { auto layout = new QVBoxLayout{securityWidget}; { auto radioButton = new QRadioButton{i18n("Use default connection (probably not TLS secured)")}; radioButton->setToolTip(i18nc("@info:tooltip", "Use GnuPG's default to connect to the LDAP server. " "By default, GnuPG 2.3 and earlier use a plain, not TLS secured connection. " "(Not recommended)")); radioButton->setChecked(true); connectionGroup->addButton(radioButton, static_cast(KeyserverConnection::Default)); layout->addWidget(radioButton); } { auto radioButton = new QRadioButton{i18n("Do not use a TLS secured connection")}; radioButton->setToolTip(i18nc("@info:tooltip", "Use a plain, not TLS secured connection to connect to the LDAP server. " "(Not recommended)")); connectionGroup->addButton(radioButton, static_cast(KeyserverConnection::Plain)); layout->addWidget(radioButton); } { auto radioButton = new QRadioButton{i18n("Use TLS secured connection")}; radioButton->setToolTip(i18nc("@info:tooltip", "Use a standard TLS secured connection (initiated with STARTTLS) " "to connect to the LDAP server. " "(Recommended)")); connectionGroup->addButton(radioButton, static_cast(KeyserverConnection::UseSTARTTLS)); layout->addWidget(radioButton); } { auto radioButton = new QRadioButton{i18n("Tunnel LDAP through a TLS connection")}; radioButton->setToolTip(i18nc("@info:tooltip", "Use a TLS secured connection through which the connection to the " "LDAP server is tunneled. " "(Not recommended)")); connectionGroup->addButton(radioButton, static_cast(KeyserverConnection::TunnelThroughTLS)); layout->addWidget(radioButton); } } mainLayout->addWidget(securityWidget); advancedSettings->setTitle(i18n("Advanced Settings")); { auto layout = new QGridLayout{advancedSettings}; layout->setColumnStretch(1, 1); int row = 0; layout->addWidget(new QLabel{i18n("Base DN:")}, row, 0); baseDnEdit->setToolTip(i18nc("@info:tooltip", "(Optional, can usually be left empty) " "Enter the base DN for this LDAP server to limit searches " "to only that subtree of the directory.")); baseDnEdit->setClearButtonEnabled(true); layout->addWidget(baseDnEdit, row, 1); ++row; layout->addWidget(new QLabel{i18n("Additional flags:")}, row, 0); additionalFlagsEdit->setToolTip(i18nc("@info:tooltip", "Here you can enter additional flags that are not yet (or no longer) " "supported by Kleopatra. For example, older versions of GnuPG use " "ldaps to request a TLS secured connection.")); additionalFlagsEdit->setClearButtonEnabled(true); layout->addWidget(additionalFlagsEdit, row, 1); } mainLayout->addWidget(advancedSettings); mainLayout->addStretch(1); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); KGuiItem::assign(okButton, KStandardGuiItem::ok()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel()); mainLayout->addWidget(buttonBox); }; } ui; QString host() const { return ui.hostEdit->text().trimmed(); } int port() const { return ui.useDefaultPortCheckBox->isChecked() ? -1 : ui.portSpinBox->value(); } KeyserverAuthentication authentication() const { return KeyserverAuthentication{ui.authenticationGroup->checkedId()}; } QString user() const { return ui.userEdit->text().trimmed(); } QString password() const { return ui.passwordEdit->password(); // not trimmed } KeyserverConnection connection() const { return KeyserverConnection{ui.connectionGroup->checkedId()}; } QString baseDn() const { return ui.baseDnEdit->text().trimmed(); } QStringList additionalFlags() const { return transformInPlace(ui.additionalFlagsEdit->text().split(QLatin1Char{','}, Qt::SkipEmptyParts), [](const auto &flag) { return flag.trimmed(); }); } bool inputIsAcceptable() const { const bool hostIsSet = !host().isEmpty(); const bool requiredCredentialsAreSet = authentication() != KeyserverAuthentication::Password || (!user().isEmpty() && !password().isEmpty()); return hostIsSet && requiredCredentialsAreSet; } void updateWidgets() { ui.portSpinBox->setEnabled(!ui.useDefaultPortCheckBox->isChecked()); if (ui.useDefaultPortCheckBox->isChecked()) { ui.portSpinBox->setValue(defaultPort(connection())); } ui.userEdit->setEnabled(authentication() == KeyserverAuthentication::Password); ui.passwordEdit->setEnabled(authentication() == KeyserverAuthentication::Password); ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(inputIsAcceptable()); } public: Private(EditDirectoryServiceDialog *q) : q{q} , ui{q} { connect(ui.hostEdit, &QLineEdit::textEdited, q, [this]() { updateWidgets(); }); connect(ui.useDefaultPortCheckBox, &QCheckBox::toggled, q, [this]() { updateWidgets(); }); connect(ui.authenticationGroup, &QButtonGroup::idToggled, q, [this]() { updateWidgets(); }); connect(ui.userEdit, &QLineEdit::textEdited, q, [this]() { updateWidgets(); }); connect(ui.passwordEdit, &KPasswordLineEdit::passwordChanged, q, [this]() { updateWidgets(); }); connect(ui.connectionGroup, &QButtonGroup::idToggled, q, [this]() { updateWidgets(); }); connect(ui.buttonBox, &QDialogButtonBox::accepted, q, &EditDirectoryServiceDialog::accept); connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &EditDirectoryServiceDialog::reject); updateWidgets(); restoreLayout(); } ~Private() { saveLayout(); } void setKeyserver(const KeyserverConfig &keyserver) { ui.hostEdit->setText(keyserver.host()); ui.useDefaultPortCheckBox->setChecked(keyserver.port() == -1); ui.portSpinBox->setValue(keyserver.port() == -1 ? defaultPort(keyserver.connection()) : keyserver.port()); ui.authenticationGroup->button(static_cast(keyserver.authentication()))->setChecked(true); ui.userEdit->setText(keyserver.user()); ui.passwordEdit->setPassword(keyserver.password()); ui.connectionGroup->button(static_cast(keyserver.connection()))->setChecked(true); ui.baseDnEdit->setText(keyserver.ldapBaseDn()); ui.additionalFlagsEdit->setText(keyserver.additionalFlags().join(QLatin1Char{','})); ui.advancedSettings->setExpanded(!keyserver.ldapBaseDn().isEmpty() || !keyserver.additionalFlags().empty()); updateWidgets(); } KeyserverConfig keyserver() const { KeyserverConfig keyserver; keyserver.setHost(host()); keyserver.setPort(port()); keyserver.setAuthentication(authentication()); keyserver.setUser(user()); keyserver.setPassword(password()); keyserver.setConnection(connection()); keyserver.setLdapBaseDn(baseDn()); keyserver.setAdditionalFlags(additionalFlags()); return keyserver; } private: void saveLayout() { KConfigGroup configGroup{KSharedConfig::openStateConfig(), "EditDirectoryServiceDialog"}; configGroup.writeEntry("Size", q->size()); configGroup.sync(); } void restoreLayout() { const KConfigGroup configGroup{KSharedConfig::openStateConfig(), "EditDirectoryServiceDialog"}; const auto size = configGroup.readEntry("Size", QSize{}); if (size.isValid()) { q->resize(size); } } }; EditDirectoryServiceDialog::EditDirectoryServiceDialog(QWidget *parent, Qt::WindowFlags f) : QDialog{parent, f} , d{std::make_unique(this)} { setWindowTitle(i18nc("@title:window", "Edit Directory Service")); } EditDirectoryServiceDialog::~EditDirectoryServiceDialog() = default; void EditDirectoryServiceDialog::setKeyserver(const KeyserverConfig &keyserver) { d->setKeyserver(keyserver); } KeyserverConfig EditDirectoryServiceDialog::keyserver() const { return d->keyserver(); } diff --git a/src/ui/editdirectoryservicedialog.h b/src/ui/editdirectoryservicedialog.h index 1092f1984..22dced571 100644 --- a/src/ui/editdirectoryservicedialog.h +++ b/src/ui/editdirectoryservicedialog.h @@ -1,38 +1,38 @@ /* ui/editdirectoryservicedialog.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include "kleo_export.h" + #include #include -#include "kleo_export.h" - namespace Kleo { class KeyserverConfig; class KLEO_EXPORT EditDirectoryServiceDialog : public QDialog { Q_OBJECT public: explicit EditDirectoryServiceDialog(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); ~EditDirectoryServiceDialog() override; void setKeyserver(const KeyserverConfig &keyserver); KeyserverConfig keyserver() const; private: class Private; const std::unique_ptr d; }; } diff --git a/src/ui/filenamerequester.h b/src/ui/filenamerequester.h index 3ac92dfa1..2c42e660a 100644 --- a/src/ui/filenamerequester.h +++ b/src/ui/filenamerequester.h @@ -1,59 +1,58 @@ /* -*- mode: c++; c-basic-offset:4 -*- ui/filenamerequester.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" -#include - #include +#include namespace Kleo { class KLEO_EXPORT FileNameRequester : public QWidget { Q_OBJECT Q_PROPERTY(QString fileName READ fileName WRITE setFileName) Q_PROPERTY(bool existingOnly READ existingOnly WRITE setExistingOnly) public: explicit FileNameRequester(QWidget *parent = nullptr); explicit FileNameRequester(QDir::Filters filter, QWidget *parent = nullptr); ~FileNameRequester() override; void setFileName(const QString &name); QString fileName() const; void setExistingOnly(bool on); bool existingOnly() const; void setFilter(QDir::Filters f); QDir::Filters filter() const; void setNameFilter(const QString &nameFilter); QString nameFilter() const; void setAccessibleNameOfLineEdit(const QString &name); Q_SIGNALS: void fileNameChanged(const QString &filename); protected: bool event(QEvent *event) override; private: virtual QString requestFileName(); private: class FileNameRequesterPrivate; std::unique_ptr const d; }; } diff --git a/src/ui/keyapprovaldialog.cpp b/src/ui/keyapprovaldialog.cpp index c5660bd64..9cc175fa8 100644 --- a/src/ui/keyapprovaldialog.cpp +++ b/src/ui/keyapprovaldialog.cpp @@ -1,207 +1,208 @@ /* -*- c++ -*- keyapprovaldialog.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB Based on kpgpui.h SPDX-FileCopyrightText: 2001, 2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keyapprovaldialog.h" #include "keyrequester.h" #include #include +#include + #include #include #include #include #include #include #include #include #include #include #include #include -#include static Kleo::EncryptionPreference cb2pref(int i) { switch (i) { default: case 0: return Kleo::UnknownPreference; case 1: return Kleo::NeverEncrypt; case 2: return Kleo::AlwaysEncrypt; case 3: return Kleo::AlwaysEncryptIfPossible; case 4: return Kleo::AlwaysAskForEncryption; case 5: return Kleo::AskWheneverPossible; } } static int pref2cb(Kleo::EncryptionPreference p) { switch (p) { default: return 0; case Kleo::NeverEncrypt: return 1; case Kleo::AlwaysEncrypt: return 2; case Kleo::AlwaysEncryptIfPossible: return 3; case Kleo::AlwaysAskForEncryption: return 4; case Kleo::AskWheneverPossible: return 5; } } static QStringList preferencesStrings() { return QStringList() << xi18n("none") // << i18n("Never Encrypt with This Key") // << i18n("Always Encrypt with This Key") // << i18n("Encrypt Whenever Encryption is Possible") // << i18n("Always Ask") // << i18n("Ask Whenever Encryption is Possible"); } class Q_DECL_HIDDEN Kleo::KeyApprovalDialog::KeyApprovalDialogPrivate { public: KeyApprovalDialogPrivate() { } Kleo::KeyRequester *selfRequester = nullptr; QStringList addresses; std::vector requesters; std::vector preferences; bool prefsChanged = false; }; Kleo::KeyApprovalDialog::KeyApprovalDialog(const std::vector &recipients, const std::vector &sender, QWidget *parent) : QDialog(parent) , d(new KeyApprovalDialogPrivate()) { setWindowTitle(i18nc("@title:window", "Encryption Key Approval")); auto mainLayout = new QVBoxLayout(this); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &KeyApprovalDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &KeyApprovalDialog::reject); okButton->setDefault(true); Q_ASSERT(!recipients.empty()); QFrame *page = new QFrame(this); mainLayout->addWidget(page); mainLayout->addWidget(buttonBox); auto vlay = new QVBoxLayout(page); vlay->setContentsMargins(0, 0, 0, 0); vlay->addWidget(new QLabel(i18n("The following keys will be used for encryption:"), page)); auto sv = new QScrollArea(page); sv->setWidgetResizable(true); vlay->addWidget(sv); QWidget *view = new QWidget(sv->viewport()); auto glay = new QGridLayout(view); glay->setColumnStretch(1, 1); sv->setWidget(view); int row = -1; if (!sender.empty()) { ++row; glay->addWidget(new QLabel(i18n("Your keys:"), view), row, 0); d->selfRequester = new EncryptionKeyRequester(true, EncryptionKeyRequester::AllProtocols, view); d->selfRequester->setKeys(sender); glay->addWidget(d->selfRequester, row, 1); ++row; glay->addWidget(new KSeparator(Qt::Horizontal, view), row, 0, 1, 2); } const QStringList prefs = preferencesStrings(); for (auto it = recipients.begin(); it != recipients.end(); ++it) { ++row; glay->addWidget(new QLabel(i18n("Recipient:"), view), row, 0); glay->addWidget(new QLabel(it->address, view), row, 1); d->addresses.push_back(it->address); ++row; glay->addWidget(new QLabel(i18n("Encryption keys:"), view), row, 0); KeyRequester *req = new EncryptionKeyRequester(true, EncryptionKeyRequester::AllProtocols, view); req->setKeys(it->keys); glay->addWidget(req, row, 1); d->requesters.push_back(req); ++row; glay->addWidget(new QLabel(i18n("Encryption preference:"), view), row, 0); auto cb = new QComboBox(view); cb->setEditable(false); cb->addItems(prefs); glay->addWidget(cb, row, 1); cb->setCurrentIndex(pref2cb(it->pref)); connect(cb, qOverload(&QComboBox::activated), this, &KeyApprovalDialog::slotPrefsChanged); d->preferences.push_back(cb); } QSize size = sizeHint(); // don't make the dialog too large const QSize desk = screen()->size(); resize(QSize(qMin(size.width(), 3 * desk.width() / 4), qMin(size.height(), 7 * desk.height() / 8))); } Kleo::KeyApprovalDialog::~KeyApprovalDialog() = default; std::vector Kleo::KeyApprovalDialog::senderKeys() const { return d->selfRequester ? d->selfRequester->keys() : std::vector(); } std::vector Kleo::KeyApprovalDialog::items() const { Q_ASSERT(d->requesters.size() == static_cast(d->addresses.size())); Q_ASSERT(d->requesters.size() == d->preferences.size()); std::vector result; result.reserve(d->requesters.size()); QStringList::const_iterator ait = d->addresses.constBegin(); auto rit = d->requesters.begin(); auto cit = d->preferences.begin(); while (ait != d->addresses.constEnd()) { result.push_back(Item(*ait++, (*rit++)->keys(), cb2pref((*cit++)->currentIndex()))); } return result; } bool Kleo::KeyApprovalDialog::preferencesChanged() const { return d->prefsChanged; } void Kleo::KeyApprovalDialog::slotPrefsChanged() { d->prefsChanged = true; } diff --git a/src/ui/keyapprovaldialog.h b/src/ui/keyapprovaldialog.h index e8d33e840..686f457a7 100644 --- a/src/ui/keyapprovaldialog.h +++ b/src/ui/keyapprovaldialog.h @@ -1,69 +1,70 @@ /* -*- c++ -*- keyapprovaldialog.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB Based on kpgpui.h SPDX-FileCopyrightText: 2001, 2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" -#include "libkleo/enum.h" + +#include #include #include #include namespace GpgME { class Key; } namespace Kleo { class KLEO_EXPORT KeyApprovalDialog : public QDialog { Q_OBJECT public: struct Item { Item() : pref(UnknownPreference) { } Item(const QString &a, const std::vector &k, EncryptionPreference p = UnknownPreference) : address(a) , keys(k) , pref(p) { } QString address; std::vector keys; EncryptionPreference pref; }; KeyApprovalDialog(const std::vector &recipients, const std::vector &sender, QWidget *parent = nullptr); ~KeyApprovalDialog() override; std::vector items() const; std::vector senderKeys() const; bool preferencesChanged() const; private Q_SLOTS: void slotPrefsChanged(); private: class KeyApprovalDialogPrivate; std::unique_ptr const d; }; } // namespace Kleo diff --git a/src/ui/keylistview.cpp b/src/ui/keylistview.cpp index b595ec5a9..0676f8863 100644 --- a/src/ui/keylistview.cpp +++ b/src/ui/keylistview.cpp @@ -1,558 +1,558 @@ /* keylistview.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 #include "keylistview.h" #include #include #include #include +#include #include #include #include + #include #include #include -#include - static const int updateDelayMilliSecs = 500; class Q_DECL_HIDDEN Kleo::KeyListView::KeyListViewPrivate { public: KeyListViewPrivate() : updateTimer(nullptr) { } std::vector keyBuffer; QTimer *updateTimer = nullptr; std::map itemMap; }; // a list of signals where we want to replace QListViewItem with // Kleo:KeyListViewItem: static const struct { const char *source; const char *target; } signalReplacements[] = { { SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), SLOT(slotEmitDoubleClicked(QTreeWidgetItem *, int)), }, { SIGNAL(itemSelectionChanged()), SLOT(slotEmitSelectionChanged()), }, { SIGNAL(customContextMenuRequested(QPoint)), SLOT(slotEmitContextMenu(QPoint)), }, }; static const int numSignalReplacements = sizeof signalReplacements / sizeof *signalReplacements; Kleo::KeyListView::KeyListView(const ColumnStrategy *columnStrategy, const DisplayStrategy *displayStrategy, QWidget *parent, Qt::WindowFlags f) : QTreeWidget(parent) , mColumnStrategy(columnStrategy) , mDisplayStrategy(displayStrategy) , mHierarchical(false) , d(new KeyListViewPrivate()) { setWindowFlags(f); setContextMenuPolicy(Qt::CustomContextMenu); d->updateTimer = new QTimer(this); d->updateTimer->setSingleShot(true); connect(d->updateTimer, &QTimer::timeout, this, &KeyListView::slotUpdateTimeout); if (!columnStrategy) { qCWarning(KLEO_UI_LOG) << "Kleo::KeyListView: need a column strategy to work with!"; return; } const QFontMetrics fm = fontMetrics(); for (int col = 0; !columnStrategy->title(col).isEmpty(); ++col) { headerItem()->setText(col, columnStrategy->title(col)); header()->resizeSection(col, columnStrategy->width(col, fm)); header()->setSectionResizeMode(col, columnStrategy->resizeMode(col)); } setAllColumnsShowFocus(true); for (int i = 0; i < numSignalReplacements; ++i) { connect(this, signalReplacements[i].source, signalReplacements[i].target); } this->setToolTip(QString()); viewport()->setToolTip(QString()); // make double sure :) } Kleo::KeyListView::~KeyListView() { d->updateTimer->stop(); // need to clear here, since in ~QListView, our children won't have // a valid listView() pointing to us anymore, and their dtors try to // unregister from us. clear(); Q_ASSERT(d->itemMap.size() == 0); // need to delete the tooltip ourselves, as ~QToolTip isn't virtual :o delete mColumnStrategy; mColumnStrategy = nullptr; delete mDisplayStrategy; mDisplayStrategy = nullptr; } void Kleo::KeyListView::takeItem(QTreeWidgetItem *qlvi) { // qCDebug(KLEO_UI_LOG) <<"Kleo::KeyListView::takeItem(" << qlvi <<" )"; if (auto *item = lvi_cast(qlvi)) { deregisterItem(item); } takeTopLevelItem(indexOfTopLevelItem(qlvi)); } void Kleo::KeyListView::setHierarchical(bool hier) { if (hier == mHierarchical) { return; } mHierarchical = hier; if (hier) { gatherScattered(); } else { scatterGathered(firstChild()); } } void Kleo::KeyListView::slotAddKey(const GpgME::Key &key) { if (key.isNull()) { return; } d->keyBuffer.push_back(key); if (!d->updateTimer->isActive()) { d->updateTimer->start(updateDelayMilliSecs); } } void Kleo::KeyListView::slotUpdateTimeout() { if (d->keyBuffer.empty()) { return; } const bool wasUpdatesEnabled = viewport()->updatesEnabled(); if (wasUpdatesEnabled) { viewport()->setUpdatesEnabled(false); } qCDebug(KLEO_UI_LOG) << "Kleo::KeyListView::slotUpdateTimeout(): processing" << d->keyBuffer.size() << "items en block"; if (hierarchical()) { for (std::vector::const_iterator it = d->keyBuffer.begin(); it != d->keyBuffer.end(); ++it) { doHierarchicalInsert(*it); } gatherScattered(); } else { for (std::vector::const_iterator it = d->keyBuffer.begin(); it != d->keyBuffer.end(); ++it) { (void)new KeyListViewItem(this, *it); } } if (wasUpdatesEnabled) { viewport()->setUpdatesEnabled(true); } d->keyBuffer.clear(); } void Kleo::KeyListView::clear() { d->updateTimer->stop(); d->keyBuffer.clear(); while (QTreeWidgetItem *item = topLevelItem(0)) { delete item; } QTreeWidget::clear(); } void Kleo::KeyListView::registerItem(KeyListViewItem *item) { // qCDebug(KLEO_UI_LOG) <<"registerItem(" << item <<" )"; if (!item) { return; } const QByteArray fpr = item->key().primaryFingerprint(); if (!fpr.isEmpty()) { d->itemMap.insert(std::make_pair(fpr, item)); } } void Kleo::KeyListView::deregisterItem(const KeyListViewItem *item) { // qCDebug(KLEO_UI_LOG) <<"deregisterItem( KeyLVI:" << item <<" )"; if (!item) { return; } auto it = d->itemMap.find(item->key().primaryFingerprint()); if (it == d->itemMap.end()) { return; } // This Q_ASSERT triggers, though it shouldn't. Print some more // information when it happens. // Q_ASSERT( it->second == item ); if (it->second != item) { qCWarning(KLEO_UI_LOG) << "deregisterItem:" << "item " << item->key().primaryFingerprint() // << "it->second" << (it->second ? it->second->key().primaryFingerprint() : "is null"); return; } d->itemMap.erase(it); } void Kleo::KeyListView::doHierarchicalInsert(const GpgME::Key &key) { const QByteArray fpr = key.primaryFingerprint(); if (fpr.isEmpty()) { return; } KeyListViewItem *item = nullptr; if (!key.isRoot()) { if (KeyListViewItem *parent = itemByFingerprint(key.chainID())) { item = new KeyListViewItem(parent, key); parent->setExpanded(true); } } if (!item) { item = new KeyListViewItem(this, key); // top-level (for now) } d->itemMap.insert(std::make_pair(fpr, item)); } void Kleo::KeyListView::gatherScattered() { KeyListViewItem *item = firstChild(); while (item) { KeyListViewItem *cur = item; item = item->nextSibling(); if (cur->key().isRoot()) { continue; } if (KeyListViewItem *parent = itemByFingerprint(cur->key().chainID())) { // found a new parent... // ### todo: optimize by suppressing removing/adding the item to the itemMap... takeTopLevelItem(indexOfTopLevelItem(cur)); parent->addChild(cur); parent->setExpanded(true); } } } void Kleo::KeyListView::scatterGathered(KeyListViewItem *start) { KeyListViewItem *item = start; while (item) { KeyListViewItem *cur = item; item = item->nextSibling(); scatterGathered(Kleo::lvi_cast(cur->child(0))); Q_ASSERT(cur->childCount() == 0); // ### todo: optimize by suppressing removing/adding the item to the itemMap... if (cur->parent()) { static_cast(cur->parent())->takeItem(cur); } else { takeItem(cur); } addTopLevelItem(cur); } } Kleo::KeyListViewItem *Kleo::KeyListView::itemByFingerprint(const QByteArray &s) const { if (s.isEmpty()) { return nullptr; } const std::map::const_iterator it = d->itemMap.find(s); if (it == d->itemMap.end()) { return nullptr; } return it->second; } void Kleo::KeyListView::slotRefreshKey(const GpgME::Key &key) { const char *fpr = key.primaryFingerprint(); if (!fpr) { return; } if (KeyListViewItem *item = itemByFingerprint(fpr)) { item->setKey(key); } else { // none found -> add it slotAddKey(key); } } // slots for the emission of covariant Q_SIGNALS: void Kleo::KeyListView::slotEmitDoubleClicked(QTreeWidgetItem *item, int col) { if (!item || lvi_cast(item)) { Q_EMIT doubleClicked(static_cast(item), col); } } void Kleo::KeyListView::slotEmitReturnPressed(QTreeWidgetItem *item) { if (!item || lvi_cast(item)) { Q_EMIT returnPressed(static_cast(item)); } } void Kleo::KeyListView::slotEmitSelectionChanged() { Q_EMIT selectionChanged(selectedItem()); } void Kleo::KeyListView::slotEmitContextMenu(const QPoint &pos) { QTreeWidgetItem *item = itemAt(pos); if (!item || lvi_cast(item)) { Q_EMIT contextMenu(static_cast(item), viewport()->mapToGlobal(pos)); } } // // // KeyListViewItem // // Kleo::KeyListViewItem::KeyListViewItem(KeyListView *parent, const GpgME::Key &key) : QTreeWidgetItem(parent, RTTI) { Q_ASSERT(parent); setKey(key); } Kleo::KeyListViewItem::KeyListViewItem(KeyListView *parent, KeyListViewItem *after, const GpgME::Key &key) : QTreeWidgetItem(parent, after, RTTI) { Q_ASSERT(parent); setKey(key); } Kleo::KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, const GpgME::Key &key) : QTreeWidgetItem(parent, RTTI) { Q_ASSERT(parent && parent->listView()); setKey(key); } Kleo::KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, KeyListViewItem *after, const GpgME::Key &key) : QTreeWidgetItem(parent, after, RTTI) { Q_ASSERT(parent && parent->listView()); setKey(key); } Kleo::KeyListViewItem::~KeyListViewItem() { // delete the children first... When children are deleted in the // QLVI dtor, they don't have listView() anymore, thus they don't // call deregister( this ), leading to stale entries in the // itemMap... while (QTreeWidgetItem *item = child(0)) { delete item; } // better do this here, too, since deletion is top-down and thus // we're deleted when our parent item is no longer a // KeyListViewItem, but a mere QListViewItem, so our takeItem() // overload is gone by that time... if (KeyListView *lv = listView()) { lv->deregisterItem(this); } } void Kleo::KeyListViewItem::setKey(const GpgME::Key &key) { KeyListView *lv = listView(); if (lv) { lv->deregisterItem(this); } mKey = key; if (lv) { lv->registerItem(this); } // the ColumnStrategy operations might be very slow, so cache their // result here, where we're non-const :) const Kleo::KeyListView::ColumnStrategy *cs = lv ? lv->columnStrategy() : nullptr; if (!cs) { return; } const KeyListView::DisplayStrategy *ds = lv->displayStrategy(); const int numCols = lv ? lv->columnCount() : 0; for (int i = 0; i < numCols; ++i) { setText(i, cs->text(key, i)); setToolTip(i, cs->toolTip(key, i)); const QIcon icon = cs->icon(key, i); if (!icon.isNull()) { setIcon(i, icon); } if (ds) { setForeground(i, QBrush(ds->keyForeground(key, foreground(i).color()))); setBackground(i, QBrush(ds->keyBackground(key, background(i).color()))); setFont(i, ds->keyFont(key, font(i))); } } } QString Kleo::KeyListViewItem::toolTip(int col) const { return listView() && listView()->columnStrategy() ? listView()->columnStrategy()->toolTip(key(), col) : QString(); } bool Kleo::KeyListViewItem::operator<(const QTreeWidgetItem &other) const { if (other.type() != RTTI || !listView() || !listView()->columnStrategy()) { return QTreeWidgetItem::operator<(other); } const auto that = static_cast(&other); return listView()->columnStrategy()->compare(this->key(), that->key(), treeWidget()->sortColumn()) < 0; } void Kleo::KeyListViewItem::takeItem(QTreeWidgetItem *qlvi) { // qCDebug(KLEO_UI_LOG) <<"Kleo::KeyListViewItem::takeItem(" << qlvi <<" )"; if (auto *item = lvi_cast(qlvi)) { listView()->deregisterItem(item); } takeChild(indexOfChild(qlvi)); } // // // ColumnStrategy // // Kleo::KeyListView::ColumnStrategy::~ColumnStrategy() { } int Kleo::KeyListView::ColumnStrategy::compare(const GpgME::Key &key1, const GpgME::Key &key2, const int col) const { return QString::localeAwareCompare(text(key1, col), text(key2, col)); } int Kleo::KeyListView::ColumnStrategy::width(int col, const QFontMetrics &fm) const { return fm.horizontalAdvance(title(col)) * 2; } QString Kleo::KeyListView::ColumnStrategy::toolTip(const GpgME::Key &key, int col) const { return text(key, col); } // // // DisplayStrategy // // Kleo::KeyListView::DisplayStrategy::~DisplayStrategy() { } // font QFont Kleo::KeyListView::DisplayStrategy::keyFont(const GpgME::Key &, const QFont &font) const { return font; } // foreground QColor Kleo::KeyListView::DisplayStrategy::keyForeground(const GpgME::Key &, const QColor &fg) const { return fg; } // background QColor Kleo::KeyListView::DisplayStrategy::keyBackground(const GpgME::Key &, const QColor &bg) const { return bg; } // // // Collection of covariant return reimplementations of QListView(Item) // members: // // Kleo::KeyListView *Kleo::KeyListViewItem::listView() const { return static_cast(QTreeWidgetItem::treeWidget()); } Kleo::KeyListViewItem *Kleo::KeyListViewItem::nextSibling() const { if (parent()) { const int myIndex = parent()->indexOfChild(const_cast(this)); return static_cast(parent()->child(myIndex + 1)); } const int myIndex = treeWidget()->indexOfTopLevelItem(const_cast(this)); return static_cast(treeWidget()->topLevelItem(myIndex + 1)); } Kleo::KeyListViewItem *Kleo::KeyListView::firstChild() const { return static_cast(topLevelItem(0)); } Kleo::KeyListViewItem *Kleo::KeyListView::selectedItem() const { QList selection = selectedItems(); if (selection.isEmpty()) { return nullptr; } return selection.first(); } QList Kleo::KeyListView::selectedItems() const { QList result; const auto selectedItems = QTreeWidget::selectedItems(); for (QTreeWidgetItem *selectedItem : selectedItems) { if (auto *i = Kleo::lvi_cast(selectedItem)) { result.append(i); } } return result; } bool Kleo::KeyListView::isMultiSelection() const { return selectionMode() == ExtendedSelection || selectionMode() == MultiSelection; } void Kleo::KeyListView::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { if (selectedItem()) { slotEmitReturnPressed(selectedItem()); } } QTreeView::keyPressEvent(event); } diff --git a/src/ui/keylistview.h b/src/ui/keylistview.h index ef0db4da8..5728ae528 100644 --- a/src/ui/keylistview.h +++ b/src/ui/keylistview.h @@ -1,197 +1,197 @@ /* keylistview.h 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 */ #pragma once #include "kleo_export.h" -#include - #include #include #include #include +#include + class QFont; class QColor; namespace Kleo { // work around moc parser bug... #define TEMPLATE_TYPENAME(T) template TEMPLATE_TYPENAME(T) inline T *lvi_cast(QTreeWidgetItem *item) { return item && (item->type() == T::RTTI) ? static_cast(item) : nullptr; } TEMPLATE_TYPENAME(T) inline const T *lvi_cast(const QTreeWidgetItem *item) { return item && (item->type() == T::RTTI) ? static_cast(item) : nullptr; } #undef TEMPLATE_TYPENAME class KeyListView; class KeyListViewItem : public QTreeWidgetItem { public: KeyListViewItem(KeyListView *parent, const GpgME::Key &key); KeyListViewItem(KeyListView *parent, KeyListViewItem *after, const GpgME::Key &key); KeyListViewItem(KeyListViewItem *parent, const GpgME::Key &key); KeyListViewItem(KeyListViewItem *parent, KeyListViewItem *after, const GpgME::Key &key); ~KeyListViewItem() override; void setKey(const GpgME::Key &key); const GpgME::Key &key() const { return mKey; } enum { RTTI = QTreeWidgetItem::UserType + 1 }; // // only boring stuff below: // virtual QString toolTip(int column) const; /*! \reimp for covariant return */ KeyListView *listView() const; /*! \reimp for covariant return */ KeyListViewItem *nextSibling() const; /*! \reimp */ bool operator<(const QTreeWidgetItem &other) const override; /*! \reimp */ void takeItem(QTreeWidgetItem *item); private: GpgME::Key mKey; }; class KLEO_EXPORT KeyListView : public QTreeWidget { Q_OBJECT friend class KeyListViewItem; public: class KLEO_EXPORT ColumnStrategy { public: virtual ~ColumnStrategy(); virtual QString title(int column) const = 0; virtual int width(int column, const QFontMetrics &fm) const; virtual QHeaderView::ResizeMode resizeMode(int) const { return QHeaderView::Interactive; } virtual QString text(const GpgME::Key &key, int column) const = 0; virtual QString toolTip(const GpgME::Key &key, int column) const; virtual QIcon icon(const GpgME::Key &, int) const { return QIcon(); } virtual int compare(const GpgME::Key &key1, const GpgME::Key &key2, const int column) const; }; class KLEO_EXPORT DisplayStrategy { public: virtual ~DisplayStrategy(); // font virtual QFont keyFont(const GpgME::Key &, const QFont &) const; // foreground virtual QColor keyForeground(const GpgME::Key &, const QColor &) const; // background virtual QColor keyBackground(const GpgME::Key &, const QColor &) const; }; explicit KeyListView(const ColumnStrategy *strategy, const DisplayStrategy *display = nullptr, QWidget *parent = nullptr, Qt::WindowFlags f = {}); ~KeyListView() override; const ColumnStrategy *columnStrategy() const { return mColumnStrategy; } const DisplayStrategy *displayStrategy() const { return mDisplayStrategy; } bool hierarchical() const { return mHierarchical; } virtual void setHierarchical(bool hier); void flushKeys() { slotUpdateTimeout(); } bool isMultiSelection() const; KeyListViewItem *itemByFingerprint(const QByteArray &) const; public: using QTreeWidget::selectionChanged; // for below, but moc doesn't like it to be in the Q_SIGNALS: section Q_SIGNALS: void doubleClicked(Kleo::KeyListViewItem *, int); void returnPressed(Kleo::KeyListViewItem *); void selectionChanged(Kleo::KeyListViewItem *); void contextMenu(Kleo::KeyListViewItem *, const QPoint &); protected: void keyPressEvent(QKeyEvent *event) override; public Q_SLOTS: virtual void slotAddKey(const GpgME::Key &key); virtual void slotRefreshKey(const GpgME::Key &key); // // Only boring stuff below: // private Q_SLOTS: void slotEmitDoubleClicked(QTreeWidgetItem *, int); void slotEmitReturnPressed(QTreeWidgetItem *); void slotEmitSelectionChanged(); void slotEmitContextMenu(const QPoint &pos); void slotUpdateTimeout(); public: /*! \reimp for covariant return */ KeyListViewItem *selectedItem() const; /*! \reimp */ QList selectedItems() const; /*! \reimp for covariant return */ KeyListViewItem *firstChild() const; /*! \reimp */ void clear(); /*! \reimp */ void takeItem(QTreeWidgetItem *); private: void doHierarchicalInsert(const GpgME::Key &); void gatherScattered(); void scatterGathered(KeyListViewItem *); void registerItem(KeyListViewItem *); void deregisterItem(const KeyListViewItem *); private: const ColumnStrategy *mColumnStrategy = nullptr; const DisplayStrategy *mDisplayStrategy = nullptr; bool mHierarchical = false; class KeyListViewPrivate; std::unique_ptr const d; }; } diff --git a/src/ui/keyrequester.cpp b/src/ui/keyrequester.cpp index 792ae993e..d2d8e9a7b 100644 --- a/src/ui/keyrequester.cpp +++ b/src/ui/keyrequester.cpp @@ -1,492 +1,488 @@ /* -*- c++ -*- keyrequester.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB Based on kpgpui.cpp SPDX-FileCopyrightText: 2001, 2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details This file is part of KPGP, the KDE PGP/GnuPG support library. SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keyrequester.h" #include "keyselectiondialog.h" -#include "libkleo/dn.h" +#include -// gpgme++ -#include -#include -#include - -// KDE #include #include -#include -#include -// Qt -#include +#include +#include +#include +#include +#include #include -#include +#include +#include using namespace QGpgME; Kleo::KeyRequester::KeyRequester(unsigned int allowedKeys, bool multipleKeys, QWidget *parent) : QWidget(parent) , mOpenPGPBackend(nullptr) , mSMIMEBackend(nullptr) , mMulti(multipleKeys) , mKeyUsage(allowedKeys) , mJobs(0) , d(nullptr) { init(); } Kleo::KeyRequester::KeyRequester(QWidget *parent) : QWidget(parent) , mOpenPGPBackend(nullptr) , mSMIMEBackend(nullptr) , mMulti(false) , mKeyUsage(0) , mJobs(0) , d(nullptr) { init(); } void Kleo::KeyRequester::init() { auto hlay = new QHBoxLayout(this); hlay->setContentsMargins(0, 0, 0, 0); // the label where the key id is to be displayed: mLabel = new QLabel(this); mLabel->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); // the button to unset any key: mEraseButton = new QPushButton(this); mEraseButton->setAutoDefault(false); mEraseButton->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); mEraseButton->setIcon( QIcon::fromTheme(QApplication::isRightToLeft() ? QStringLiteral("edit-clear-locationbar-ltr") : QStringLiteral("edit-clear-locationbar-rtl"))); mEraseButton->setToolTip(i18n("Clear")); // the button to call the KeySelectionDialog: mDialogButton = new QPushButton(i18n("Change..."), this); mDialogButton->setAutoDefault(false); hlay->addWidget(mLabel, 1); hlay->addWidget(mEraseButton); hlay->addWidget(mDialogButton); connect(mEraseButton, &QPushButton::clicked, this, &SigningKeyRequester::slotEraseButtonClicked); connect(mDialogButton, &QPushButton::clicked, this, &SigningKeyRequester::slotDialogButtonClicked); setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed)); setAllowedKeys(mKeyUsage); } Kleo::KeyRequester::~KeyRequester() { } const std::vector &Kleo::KeyRequester::keys() const { return mKeys; } const GpgME::Key &Kleo::KeyRequester::key() const { static const GpgME::Key null = GpgME::Key::null; if (mKeys.empty()) { return null; } else { return mKeys.front(); } } void Kleo::KeyRequester::setKeys(const std::vector &keys) { mKeys.clear(); for (auto it = keys.begin(); it != keys.end(); ++it) { if (!it->isNull()) { mKeys.push_back(*it); } } updateKeys(); } void Kleo::KeyRequester::setKey(const GpgME::Key &key) { mKeys.clear(); if (!key.isNull()) { mKeys.push_back(key); } updateKeys(); } QString Kleo::KeyRequester::fingerprint() const { if (mKeys.empty()) { return QString(); } else { return QLatin1String(mKeys.front().primaryFingerprint()); } } QStringList Kleo::KeyRequester::fingerprints() const { QStringList result; for (auto it = mKeys.begin(); it != mKeys.end(); ++it) { if (!it->isNull()) { if (const char *fpr = it->primaryFingerprint()) { result.push_back(QLatin1String(fpr)); } } } return result; } void Kleo::KeyRequester::setFingerprint(const QString &fingerprint) { startKeyListJob(QStringList(fingerprint)); } void Kleo::KeyRequester::setFingerprints(const QStringList &fingerprints) { startKeyListJob(fingerprints); } void Kleo::KeyRequester::updateKeys() { if (mKeys.empty()) { mLabel->clear(); return; } if (mKeys.size() > 1) { setMultipleKeysEnabled(true); } QStringList labelTexts; QString toolTipText; for (std::vector::const_iterator it = mKeys.begin(); it != mKeys.end(); ++it) { if (it->isNull()) { continue; } const QString fpr = QLatin1String(it->primaryFingerprint()); labelTexts.push_back(fpr.right(8)); toolTipText += fpr.right(8) + QLatin1String(": "); if (const char *uid = it->userID(0).id()) { if (it->protocol() == GpgME::OpenPGP) { toolTipText += QString::fromUtf8(uid); } else { toolTipText += Kleo::DN(uid).prettyDN(); } } else { toolTipText += xi18n("unknown"); } toolTipText += QLatin1Char('\n'); } mLabel->setText(labelTexts.join(QLatin1String(", "))); mLabel->setToolTip(toolTipText); } #ifndef __KLEO_UI_SHOW_KEY_LIST_ERROR_H__ #define __KLEO_UI_SHOW_KEY_LIST_ERROR_H__ static void showKeyListError(QWidget *parent, const GpgME::Error &err) { Q_ASSERT(err); const QString msg = i18n( "

An error occurred while fetching " "the keys from the backend:

" "

%1

", QString::fromLocal8Bit(err.asString())); KMessageBox::error(parent, msg, i18n("Key Listing Failed")); } #endif // __KLEO_UI_SHOW_KEY_LIST_ERROR_H__ void Kleo::KeyRequester::startKeyListJob(const QStringList &fingerprints) { if (!mSMIMEBackend && !mOpenPGPBackend) { return; } mTmpKeys.clear(); mJobs = 0; unsigned int count = 0; for (QStringList::const_iterator it = fingerprints.begin(); it != fingerprints.end(); ++it) { if (!(*it).trimmed().isEmpty()) { ++count; } } if (!count) { // don't fall into the trap that an empty pattern means // "return all keys" :) setKey(GpgME::Key::null); return; } if (mOpenPGPBackend) { KeyListJob *job = mOpenPGPBackend->keyListJob(false); // local, no sigs if (!job) { KMessageBox::error(this, i18n("The OpenPGP backend does not support listing keys. " "Check your installation."), i18n("Key Listing Failed")); } else { connect(job, &KeyListJob::result, this, &SigningKeyRequester::slotKeyListResult); connect(job, &KeyListJob::nextKey, this, &SigningKeyRequester::slotNextKey); const GpgME::Error err = job->start(fingerprints, mKeyUsage & Kleo::KeySelectionDialog::SecretKeys && !(mKeyUsage & Kleo::KeySelectionDialog::PublicKeys)); if (err) { showKeyListError(this, err); } else { ++mJobs; } } } if (mSMIMEBackend) { KeyListJob *job = mSMIMEBackend->keyListJob(false); // local, no sigs if (!job) { KMessageBox::error(this, i18n("The S/MIME backend does not support listing keys. " "Check your installation."), i18n("Key Listing Failed")); } else { connect(job, &KeyListJob::result, this, &SigningKeyRequester::slotKeyListResult); connect(job, &KeyListJob::nextKey, this, &SigningKeyRequester::slotNextKey); const GpgME::Error err = job->start(fingerprints, mKeyUsage & Kleo::KeySelectionDialog::SecretKeys && !(mKeyUsage & Kleo::KeySelectionDialog::PublicKeys)); if (err) { showKeyListError(this, err); } else { ++mJobs; } } } if (mJobs > 0) { mEraseButton->setEnabled(false); mDialogButton->setEnabled(false); } } void Kleo::KeyRequester::slotNextKey(const GpgME::Key &key) { if (!key.isNull()) { mTmpKeys.push_back(key); } } void Kleo::KeyRequester::slotKeyListResult(const GpgME::KeyListResult &res) { if (res.error()) { showKeyListError(this, res.error()); } if (--mJobs <= 0) { mEraseButton->setEnabled(true); mDialogButton->setEnabled(true); setKeys(mTmpKeys); mTmpKeys.clear(); } } void Kleo::KeyRequester::slotDialogButtonClicked() { KeySelectionDialog *dlg = mKeys.empty() ? new KeySelectionDialog(mDialogCaption, mDialogMessage, mInitialQuery, mKeyUsage, mMulti, false, this) : new KeySelectionDialog(mDialogCaption, mDialogCaption, mKeys, mKeyUsage, mMulti, false, this); if (dlg->exec() == QDialog::Accepted) { if (mMulti) { setKeys(dlg->selectedKeys()); } else { setKey(dlg->selectedKey()); } Q_EMIT changed(); } delete dlg; } void Kleo::KeyRequester::slotEraseButtonClicked() { if (!mKeys.empty()) { Q_EMIT changed(); } mKeys.clear(); updateKeys(); } void Kleo::KeyRequester::setDialogCaption(const QString &caption) { mDialogCaption = caption; } void Kleo::KeyRequester::setDialogMessage(const QString &msg) { mDialogMessage = msg; } bool Kleo::KeyRequester::isMultipleKeysEnabled() const { return mMulti; } void Kleo::KeyRequester::setMultipleKeysEnabled(bool multi) { if (multi == mMulti) { return; } if (!multi && !mKeys.empty()) { mKeys.erase(mKeys.begin() + 1, mKeys.end()); } mMulti = multi; updateKeys(); } unsigned int Kleo::KeyRequester::allowedKeys() const { return mKeyUsage; } void Kleo::KeyRequester::setAllowedKeys(unsigned int keyUsage) { mKeyUsage = keyUsage; mOpenPGPBackend = nullptr; mSMIMEBackend = nullptr; if (mKeyUsage & KeySelectionDialog::OpenPGPKeys) { mOpenPGPBackend = openpgp(); } if (mKeyUsage & KeySelectionDialog::SMIMEKeys) { mSMIMEBackend = smime(); } if (mOpenPGPBackend && !mSMIMEBackend) { mDialogCaption = i18n("OpenPGP Key Selection"); mDialogMessage = i18n("Please select an OpenPGP key to use."); } else if (!mOpenPGPBackend && mSMIMEBackend) { mDialogCaption = i18n("S/MIME Key Selection"); mDialogMessage = i18n("Please select an S/MIME key to use."); } else { mDialogCaption = i18n("Key Selection"); mDialogMessage = i18n("Please select an (OpenPGP or S/MIME) key to use."); } } QPushButton *Kleo::KeyRequester::dialogButton() { return mDialogButton; } QPushButton *Kleo::KeyRequester::eraseButton() { return mEraseButton; } static inline unsigned int foo(bool openpgp, bool smime, bool trusted, bool valid) { unsigned int result = 0; if (openpgp) { result |= Kleo::KeySelectionDialog::OpenPGPKeys; } if (smime) { result |= Kleo::KeySelectionDialog::SMIMEKeys; } if (trusted) { result |= Kleo::KeySelectionDialog::TrustedKeys; } if (valid) { result |= Kleo::KeySelectionDialog::ValidKeys; } return result; } static inline unsigned int encryptionKeyUsage(bool openpgp, bool smime, bool trusted, bool valid) { return foo(openpgp, smime, trusted, valid) | Kleo::KeySelectionDialog::EncryptionKeys | Kleo::KeySelectionDialog::PublicKeys; } static inline unsigned int signingKeyUsage(bool openpgp, bool smime, bool trusted, bool valid) { return foo(openpgp, smime, trusted, valid) | Kleo::KeySelectionDialog::SigningKeys | Kleo::KeySelectionDialog::SecretKeys; } Kleo::EncryptionKeyRequester::EncryptionKeyRequester(bool multi, unsigned int proto, QWidget *parent, bool onlyTrusted, bool onlyValid) : KeyRequester(encryptionKeyUsage(proto & OpenPGP, proto & SMIME, onlyTrusted, onlyValid), multi, parent) , d(nullptr) { } Kleo::EncryptionKeyRequester::EncryptionKeyRequester(QWidget *parent) : KeyRequester(0, false, parent) , d(nullptr) { } Kleo::EncryptionKeyRequester::~EncryptionKeyRequester() { } void Kleo::EncryptionKeyRequester::setAllowedKeys(unsigned int proto, bool onlyTrusted, bool onlyValid) { KeyRequester::setAllowedKeys(encryptionKeyUsage(proto & OpenPGP, proto & SMIME, onlyTrusted, onlyValid)); } Kleo::SigningKeyRequester::SigningKeyRequester(bool multi, unsigned int proto, QWidget *parent, bool onlyTrusted, bool onlyValid) : KeyRequester(signingKeyUsage(proto & OpenPGP, proto & SMIME, onlyTrusted, onlyValid), multi, parent) , d(nullptr) { } Kleo::SigningKeyRequester::SigningKeyRequester(QWidget *parent) : KeyRequester(0, false, parent) , d(nullptr) { } Kleo::SigningKeyRequester::~SigningKeyRequester() { } void Kleo::SigningKeyRequester::setAllowedKeys(unsigned int proto, bool onlyTrusted, bool onlyValid) { KeyRequester::setAllowedKeys(signingKeyUsage(proto & OpenPGP, proto & SMIME, onlyTrusted, onlyValid)); } void Kleo::KeyRequester::virtual_hook(int, void *) { } void Kleo::EncryptionKeyRequester::virtual_hook(int id, void *data) { KeyRequester::virtual_hook(id, data); } void Kleo::SigningKeyRequester::virtual_hook(int id, void *data) { KeyRequester::virtual_hook(id, data); } diff --git a/src/ui/keyrequester.h b/src/ui/keyrequester.h index ce00d2f4a..26d902825 100644 --- a/src/ui/keyrequester.h +++ b/src/ui/keyrequester.h @@ -1,203 +1,203 @@ /* -*- c++ -*- keyrequester.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB Based on kpgpui.h SPDX-FileCopyrightText: 2001, 2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details This file is part of KPGP, the KDE PGP/GnuPG support library. SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" -#include +#include #include +#include #include #include namespace GpgME { class Key; class KeyListResult; } -#include class QString; class QPushButton; namespace Kleo { /// Base class for SigningKeyRequester and EncryptionKeyRequester class KLEO_EXPORT KeyRequester : public QWidget { Q_OBJECT public: explicit KeyRequester(unsigned int allowedKeys, bool multipleKeys = false, QWidget *parent = nullptr); // Constructor for Qt Designer explicit KeyRequester(QWidget *parent = nullptr); ~KeyRequester() override; const GpgME::Key &key() const; /** Preferred method to set a key for non-multi-KeyRequesters. Doesn't start a backend KeyListJob. */ void setKey(const GpgME::Key &key); const std::vector &keys() const; /** Preferred method to set a key for multi-KeyRequesters. Doesn't start a backend KeyListJob. */ void setKeys(const std::vector &keys); QString fingerprint() const; /** Set the key by fingerprint. Starts a background KeyListJob to retrieve the complete GpgME::Key object */ void setFingerprint(const QString &fingerprint); QStringList fingerprints() const; /** Set the keys by fingerprint. Starts a background KeyListJob to retrieve the complete GpgME::Key objects */ void setFingerprints(const QStringList &fingerprints); QPushButton *eraseButton(); QPushButton *dialogButton(); void setDialogCaption(const QString &caption); void setDialogMessage(const QString &message); bool isMultipleKeysEnabled() const; void setMultipleKeysEnabled(bool enable); unsigned int allowedKeys() const; void setAllowedKeys(unsigned int allowed); void setInitialQuery(const QString &s) { mInitialQuery = s; } const QString &initialQuery() const { return mInitialQuery; } Q_SIGNALS: void changed(); private: void init(); void startKeyListJob(const QStringList &fingerprints); void updateKeys(); private Q_SLOTS: void slotNextKey(const GpgME::Key &key); void slotKeyListResult(const GpgME::KeyListResult &result); void slotDialogButtonClicked(); void slotEraseButtonClicked(); private: const QGpgME::Protocol *mOpenPGPBackend = nullptr; const QGpgME::Protocol *mSMIMEBackend = nullptr; QLabel *mLabel = nullptr; QPushButton *mEraseButton = nullptr; QPushButton *mDialogButton = nullptr; QString mDialogCaption, mDialogMessage, mInitialQuery; bool mMulti; unsigned int mKeyUsage; int mJobs; std::vector mKeys; std::vector mTmpKeys; private: class Private; Private *const d; protected: virtual void virtual_hook(int, void *); }; class KLEO_EXPORT EncryptionKeyRequester : public KeyRequester { Q_OBJECT public: enum { OpenPGP = 1, SMIME = 2, AllProtocols = OpenPGP | SMIME }; /** * Preferred constructor */ explicit EncryptionKeyRequester(bool multipleKeys = false, unsigned int proto = AllProtocols, QWidget *parent = nullptr, bool onlyTrusted = true, bool onlyValid = true); /** * Constructor for Qt designer */ explicit EncryptionKeyRequester(QWidget *parent); ~EncryptionKeyRequester() override; void setAllowedKeys(unsigned int proto, bool onlyTrusted = true, bool onlyValid = true); private: class Private; Private *const d; protected: void virtual_hook(int, void *) override; }; class KLEO_EXPORT SigningKeyRequester : public KeyRequester { Q_OBJECT public: enum { OpenPGP = 1, SMIME = 2, AllProtocols = OpenPGP | SMIME }; /** * Preferred constructor * @param multipleKeys whether multiple keys can be selected * * @param proto the allowed protocols, OpenPGP and/or SMIME * @param parent the parent widget * @param onlyTrusted only show trusted keys * @param onlyValid only show valid keys */ explicit SigningKeyRequester(bool multipleKeys = false, unsigned int proto = AllProtocols, QWidget *parent = nullptr, bool onlyTrusted = true, bool onlyValid = true); /** * Constructor for Qt designer */ explicit SigningKeyRequester(QWidget *parent); ~SigningKeyRequester() override; /* * Those parameters affect the parameters given to the key selection dialog. * @param proto the allowed protocols, OpenPGP and/or SMIME * @param onlyTrusted only show trusted keys * @param onlyValid only show valid keys */ void setAllowedKeys(unsigned int proto, bool onlyTrusted = true, bool onlyValid = true); private: class Private; Private *const d; protected: void virtual_hook(int, void *) override; }; } diff --git a/src/ui/keyselectioncombo.cpp b/src/ui/keyselectioncombo.cpp index 60fbd669b..550ef3edb 100644 --- a/src/ui/keyselectioncombo.cpp +++ b/src/ui/keyselectioncombo.cpp @@ -1,655 +1,655 @@ /* This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keyselectioncombo.h" #include "progressbar.h" -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include #include #include #include -#include +#include using namespace Kleo; Q_DECLARE_METATYPE(GpgME::Key) namespace { class SortFilterProxyModel : public KeyListSortFilterProxyModel { Q_OBJECT public: using KeyListSortFilterProxyModel::KeyListSortFilterProxyModel; void setAlwaysAcceptedKey(const QString &fingerprint) { if (fingerprint == mFingerprint) { return; } mFingerprint = fingerprint; invalidate(); } protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { if (!mFingerprint.isEmpty()) { const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); const auto fingerprint = sourceModel()->data(index, KeyList::FingerprintRole).toString(); if (fingerprint == mFingerprint) { return true; } } return KeyListSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } private: QString mFingerprint; }; class ProxyModel : public QSortFilterProxyModel { Q_OBJECT private: struct CustomItem { QIcon icon; QString text; QVariant data; QString toolTip; }; public: ProxyModel(QObject *parent = nullptr) : QSortFilterProxyModel(parent) { } ~ProxyModel() override { qDeleteAll(mFrontItems); qDeleteAll(mBackItems); } bool lessThan(const QModelIndex &left, const QModelIndex &right) const override { const auto leftKey = sourceModel()->data(left, KeyList::KeyRole).value(); const auto rightKey = sourceModel()->data(right, KeyList::KeyRole).value(); if (leftKey.isNull()) { return false; } if (rightKey.isNull()) { return true; } // As we display UID(0) this is ok. We probably need a get Best UID at some point. const auto lUid = leftKey.userID(0); const auto rUid = rightKey.userID(0); if (lUid.isNull()) { return false; } if (rUid.isNull()) { return true; } int cmp = strcmp(lUid.id(), rUid.id()); if (cmp) { return cmp < 0; } if (lUid.validity() == rUid.validity()) { /* Both are the same check which one is newer. */ time_t oldTime = 0; for (const GpgME::Subkey &s : leftKey.subkeys()) { if (s.isRevoked() || s.isInvalid() || s.isDisabled()) { continue; } if (s.creationTime() > oldTime) { oldTime = s.creationTime(); } } time_t newTime = 0; for (const GpgME::Subkey &s : rightKey.subkeys()) { if (s.isRevoked() || s.isInvalid() || s.isDisabled()) { continue; } if (s.creationTime() > newTime) { newTime = s.creationTime(); } } return newTime < oldTime; } return lUid.validity() > rUid.validity(); } bool isCustomItem(const int row) const { return row < mFrontItems.count() || row >= mFrontItems.count() + QSortFilterProxyModel::rowCount(); } void prependItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip) { beginInsertRows(QModelIndex(), 0, 0); mFrontItems.push_front(new CustomItem{icon, text, data, toolTip}); endInsertRows(); } void appendItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); mBackItems.push_back(new CustomItem{icon, text, data, toolTip}); endInsertRows(); } void removeCustomItem(const QVariant &data) { for (int i = 0; i < mFrontItems.count(); ++i) { if (mFrontItems[i]->data == data) { beginRemoveRows(QModelIndex(), i, i); delete mFrontItems.takeAt(i); endRemoveRows(); return; } } for (int i = 0; i < mBackItems.count(); ++i) { if (mBackItems[i]->data == data) { const int index = mFrontItems.count() + QSortFilterProxyModel::rowCount() + i; beginRemoveRows(QModelIndex(), index, index); delete mBackItems.takeAt(i); endRemoveRows(); return; } } } int rowCount(const QModelIndex &parent = QModelIndex()) const override { return mFrontItems.count() + QSortFilterProxyModel::rowCount(parent) + mBackItems.count(); } int columnCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent) // pretend that there is only one column to workaround a bug in // QAccessibleTable which provides the accessibility interface for the // pop-up of the combo box return 1; } QModelIndex mapToSource(const QModelIndex &index) const override { if (!isCustomItem(index.row())) { const int row = index.row() - mFrontItems.count(); return sourceModel()->index(row, index.column()); } else { return {}; } } QModelIndex mapFromSource(const QModelIndex &source_index) const override { const QModelIndex idx = QSortFilterProxyModel::mapFromSource(source_index); return createIndex(mFrontItems.count() + idx.row(), idx.column(), idx.internalPointer()); } QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { if (row < 0 || row >= rowCount()) { return {}; } if (row < mFrontItems.count()) { return createIndex(row, column, mFrontItems[row]); } else if (row >= mFrontItems.count() + QSortFilterProxyModel::rowCount()) { return createIndex(row, column, mBackItems[row - mFrontItems.count() - QSortFilterProxyModel::rowCount()]); } else { const QModelIndex mi = QSortFilterProxyModel::index(row - mFrontItems.count(), column, parent); return createIndex(row, column, mi.internalPointer()); } } Qt::ItemFlags flags(const QModelIndex &index) const override { Q_UNUSED(index) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemNeverHasChildren; } QModelIndex parent(const QModelIndex &) const override { // Flat list return {}; } QVariant data(const QModelIndex &index, int role) const override { if (!index.isValid()) { return QVariant(); } if (isCustomItem(index.row())) { Q_ASSERT(!mFrontItems.isEmpty() || !mBackItems.isEmpty()); auto ci = static_cast(index.internalPointer()); switch (role) { case Qt::DisplayRole: return ci->text; case Qt::DecorationRole: return ci->icon; case Qt::UserRole: return ci->data; case Qt::ToolTipRole: return ci->toolTip; default: return QVariant(); } } const auto key = QSortFilterProxyModel::data(index, KeyList::KeyRole).value(); Q_ASSERT(!key.isNull()); if (key.isNull()) { return QVariant(); } switch (role) { case Qt::DisplayRole: { const auto userID = key.userID(0); QString name; QString email; if (key.protocol() == GpgME::OpenPGP) { name = QString::fromUtf8(userID.name()); email = QString::fromUtf8(userID.email()); } else { const Kleo::DN dn(userID.id()); name = dn[QStringLiteral("CN")]; email = dn[QStringLiteral("EMAIL")]; } const auto nameAndEmail = email.isEmpty() ? name : name.isEmpty() ? email : i18nc("Name ", "%1 <%2>", name, email); if (Kleo::KeyCache::instance()->pgpOnly()) { return i18nc("Name (validity, created: date)", "%1 (%2, created: %3)", nameAndEmail, Kleo::Formatting::complianceStringShort(key), Kleo::Formatting::creationDateString(key)); } else { return i18nc("Name (validity, type, created: date)", "%1 (%2, %3, created: %4)", nameAndEmail, Kleo::Formatting::complianceStringShort(key), Formatting::displayName(key.protocol()), Kleo::Formatting::creationDateString(key)); } } case Qt::ToolTipRole: { using namespace Kleo::Formatting; return Kleo::Formatting::toolTip(key, Validity | Issuer | Subject | Fingerprint | ExpiryDates | UserIDs); } case Qt::DecorationRole: return Kleo::Formatting::iconForUid(key.userID(0)); default: return QSortFilterProxyModel::data(index, role); } } private: QVector mFrontItems; QVector mBackItems; }; } // anonymous namespace namespace Kleo { class KeySelectionComboPrivate { public: KeySelectionComboPrivate(KeySelectionCombo *parent) : wasEnabled(true) , q(parent) { } /* Selects the first key with a UID addrSpec that matches * the mPerfectMatchMbox variable. * * The idea here is that if there are keys like: * * tom-store@abc.com * susi-store@abc.com * store@abc.com * * And the user wants to send a mail to "store@abc.com" * the filter should still show tom and susi (because they * both are part of store) but the key for "store" should * be preselected. * * Returns true if one was selected. False otherwise. */ bool selectPerfectIdMatch() const { if (mPerfectMatchMbox.isEmpty()) { return false; } for (int i = 0; i < proxyModel->rowCount(); ++i) { const auto idx = proxyModel->index(i, 0, QModelIndex()); const auto key = proxyModel->data(idx, KeyList::KeyRole).value(); if (key.isNull()) { // WTF? continue; } for (const auto &uid : key.userIDs()) { if (QString::fromStdString(uid.addrSpec()) == mPerfectMatchMbox) { q->setCurrentIndex(i); return true; } } } return false; } /* Updates the current key with the default key if the key matches * the current key filter. */ void updateWithDefaultKey() { GpgME::Protocol filterProto = GpgME::UnknownProtocol; const auto filter = dynamic_cast(sortFilterProxy->keyFilter().get()); if (filter && filter->isOpenPGP() == DefaultKeyFilter::Set) { filterProto = GpgME::OpenPGP; } else if (filter && filter->isOpenPGP() == DefaultKeyFilter::NotSet) { filterProto = GpgME::CMS; } QString defaultKey = defaultKeys.value(filterProto); if (defaultKey.isEmpty()) { // Fallback to unknown protocol defaultKey = defaultKeys.value(GpgME::UnknownProtocol); } // make sure that the default key is not filtered out unless it has the wrong protocol if (filterProto == GpgME::UnknownProtocol) { sortFilterProxy->setAlwaysAcceptedKey(defaultKey); } else { const auto key = KeyCache::instance()->findByFingerprint(defaultKey.toLatin1().constData()); if (!key.isNull() && key.protocol() == filterProto) { sortFilterProxy->setAlwaysAcceptedKey(defaultKey); } else { sortFilterProxy->setAlwaysAcceptedKey({}); } } q->setCurrentKey(defaultKey); } void storeCurrentSelectionBeforeModelChange() { keyBeforeModelChange = q->currentKey(); customItemBeforeModelChange = q->currentData(); } void restoreCurrentSelectionAfterModelChange() { if (!keyBeforeModelChange.isNull()) { q->setCurrentKey(keyBeforeModelChange); } else if (customItemBeforeModelChange.isValid()) { const auto index = q->findData(customItemBeforeModelChange); if (index != -1) { q->setCurrentIndex(index); } else { updateWithDefaultKey(); } } } Kleo::AbstractKeyListModel *model = nullptr; SortFilterProxyModel *sortFilterProxy = nullptr; ProxyModel *proxyModel = nullptr; std::shared_ptr cache; QMap defaultKeys; bool wasEnabled = false; bool useWasEnabled = false; bool secretOnly = false; bool initialKeyListingDone = false; QString mPerfectMatchMbox; GpgME::Key keyBeforeModelChange; QVariant customItemBeforeModelChange; private: KeySelectionCombo *const q; }; } using namespace Kleo; KeySelectionCombo::KeySelectionCombo(QWidget *parent) : KeySelectionCombo(true, parent) { } KeySelectionCombo::KeySelectionCombo(bool secretOnly, QWidget *parent) : QComboBox(parent) , d(new KeySelectionComboPrivate(this)) { d->model = Kleo::AbstractKeyListModel::createFlatKeyListModel(this); d->secretOnly = secretOnly; d->sortFilterProxy = new SortFilterProxyModel(this); d->sortFilterProxy->setSourceModel(d->model); d->proxyModel = new ProxyModel(this); d->proxyModel->setSourceModel(d->sortFilterProxy); setModel(d->proxyModel); connect(this, qOverload(&QComboBox::currentIndexChanged), this, [this](int row) { if (row >= 0 && row < d->proxyModel->rowCount()) { if (d->proxyModel->isCustomItem(row)) { Q_EMIT customItemSelected(currentData(Qt::UserRole)); } else { Q_EMIT currentKeyChanged(currentKey()); } } }); d->cache = Kleo::KeyCache::mutableInstance(); connect(model(), &QAbstractItemModel::rowsAboutToBeInserted, this, [this]() { d->storeCurrentSelectionBeforeModelChange(); }); connect(model(), &QAbstractItemModel::rowsInserted, this, [this]() { d->restoreCurrentSelectionAfterModelChange(); }); connect(model(), &QAbstractItemModel::rowsAboutToBeRemoved, this, [this]() { d->storeCurrentSelectionBeforeModelChange(); }); connect(model(), &QAbstractItemModel::rowsRemoved, this, [this]() { d->restoreCurrentSelectionAfterModelChange(); }); connect(model(), &QAbstractItemModel::modelAboutToBeReset, this, [this]() { d->storeCurrentSelectionBeforeModelChange(); }); connect(model(), &QAbstractItemModel::modelReset, this, [this]() { d->restoreCurrentSelectionAfterModelChange(); }); QTimer::singleShot(0, this, &KeySelectionCombo::init); } KeySelectionCombo::~KeySelectionCombo() = default; void KeySelectionCombo::init() { connect(d->cache.get(), &Kleo::KeyCache::keyListingDone, this, [this]() { // Set useKeyCache ensures that the cache is populated // so this can be a blocking call if the cache is not initialized if (!d->initialKeyListingDone) { d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys); d->proxyModel->removeCustomItem(QStringLiteral("-libkleo-loading-keys")); } // We use the useWasEnabled state variable to decide if we should // change the enable / disable state based on the keylist done signal. // If we triggered the refresh useWasEnabled is true and we want to // enable / disable again after our refresh, as the refresh disabled it. // // But if a keyListingDone signal comes from just a generic refresh // triggered by someone else we don't want to change the enable / disable // state. if (d->useWasEnabled) { setEnabled(d->wasEnabled); d->useWasEnabled = false; } Q_EMIT keyListingFinished(); }); connect(this, &KeySelectionCombo::keyListingFinished, this, [this]() { if (!d->initialKeyListingDone) { d->updateWithDefaultKey(); d->initialKeyListingDone = true; } }); if (!d->cache->initialized()) { refreshKeys(); } else { d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys); Q_EMIT keyListingFinished(); } connect(this, qOverload(&QComboBox::currentIndexChanged), this, [this]() { setToolTip(currentData(Qt::ToolTipRole).toString()); }); } void KeySelectionCombo::setKeyFilter(const std::shared_ptr &kf) { d->sortFilterProxy->setKeyFilter(kf); d->proxyModel->sort(0); d->updateWithDefaultKey(); } std::shared_ptr KeySelectionCombo::keyFilter() const { return d->sortFilterProxy->keyFilter(); } void KeySelectionCombo::setIdFilter(const QString &id) { d->sortFilterProxy->setFilterRegularExpression(id); d->mPerfectMatchMbox = id; d->updateWithDefaultKey(); } QString KeySelectionCombo::idFilter() const { return d->sortFilterProxy->filterRegularExpression().pattern(); } GpgME::Key Kleo::KeySelectionCombo::currentKey() const { return currentData(KeyList::KeyRole).value(); } void Kleo::KeySelectionCombo::setCurrentKey(const GpgME::Key &key) { const int idx = findData(QString::fromLatin1(key.primaryFingerprint()), KeyList::FingerprintRole, Qt::MatchExactly); if (idx > -1) { setCurrentIndex(idx); } else if (!d->selectPerfectIdMatch()) { d->updateWithDefaultKey(); } setToolTip(currentData(Qt::ToolTipRole).toString()); } void Kleo::KeySelectionCombo::setCurrentKey(const QString &fingerprint) { const auto cur = currentKey(); if (!cur.isNull() && !fingerprint.isEmpty() && fingerprint == QLatin1String(cur.primaryFingerprint())) { // already set; still emit a changed signal because the current key may // have become the item at the current index by changes in the underlying model Q_EMIT currentKeyChanged(cur); return; } const int idx = findData(fingerprint, KeyList::FingerprintRole, Qt::MatchExactly); if (idx > -1) { setCurrentIndex(idx); } else if (!d->selectPerfectIdMatch()) { setCurrentIndex(0); } setToolTip(currentData(Qt::ToolTipRole).toString()); } void KeySelectionCombo::refreshKeys() { d->wasEnabled = isEnabled(); d->useWasEnabled = true; setEnabled(false); const bool wasBlocked = blockSignals(true); prependCustomItem(QIcon(), i18n("Loading keys ..."), QStringLiteral("-libkleo-loading-keys")); setCurrentIndex(0); blockSignals(wasBlocked); d->cache->startKeyListing(); } void KeySelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip) { d->proxyModel->appendItem(icon, text, data, toolTip); } void KeySelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data) { appendCustomItem(icon, text, data, QString()); } void KeySelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip) { d->proxyModel->prependItem(icon, text, data, toolTip); } void KeySelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data) { prependCustomItem(icon, text, data, QString()); } void KeySelectionCombo::removeCustomItem(const QVariant &data) { d->proxyModel->removeCustomItem(data); } void Kleo::KeySelectionCombo::setDefaultKey(const QString &fingerprint, GpgME::Protocol proto) { d->defaultKeys.insert(proto, fingerprint); d->updateWithDefaultKey(); } void Kleo::KeySelectionCombo::setDefaultKey(const QString &fingerprint) { setDefaultKey(fingerprint, GpgME::UnknownProtocol); } QString Kleo::KeySelectionCombo::defaultKey(GpgME::Protocol proto) const { return d->defaultKeys.value(proto); } QString Kleo::KeySelectionCombo::defaultKey() const { return defaultKey(GpgME::UnknownProtocol); } #include "keyselectioncombo.moc" diff --git a/src/ui/keyselectioncombo.h b/src/ui/keyselectioncombo.h index 4c52a1dc4..7cd4e230f 100644 --- a/src/ui/keyselectioncombo.h +++ b/src/ui/keyselectioncombo.h @@ -1,73 +1,73 @@ /* This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include - -#include - #include "kleo_export.h" #include +#include + +#include + #include namespace GpgME { class Key; } namespace Kleo { class KeyFilter; class KeySelectionComboPrivate; class KLEO_EXPORT KeySelectionCombo : public QComboBox { Q_OBJECT public: explicit KeySelectionCombo(QWidget *parent = nullptr); explicit KeySelectionCombo(bool secretOnly, QWidget *parent = nullptr); ~KeySelectionCombo() override; void setKeyFilter(const std::shared_ptr &kf); std::shared_ptr keyFilter() const; void setIdFilter(const QString &id); QString idFilter() const; void refreshKeys(); GpgME::Key currentKey() const; void setCurrentKey(const GpgME::Key &key); void setCurrentKey(const QString &fingerprint); void setDefaultKey(const QString &fingerprint); void setDefaultKey(const QString &fingerprint, GpgME::Protocol proto); QString defaultKey() const; QString defaultKey(GpgME::Protocol proto) const; void prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data); void appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data); void prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip); void appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip); void removeCustomItem(const QVariant &data); Q_SIGNALS: void customItemSelected(const QVariant &data); void currentKeyChanged(const GpgME::Key &key); void keyListingFinished(); protected: virtual void init(); private: std::unique_ptr const d; }; } diff --git a/src/ui/keyselectiondialog.cpp b/src/ui/keyselectiondialog.cpp index 88dbf599d..9a4086d13 100644 --- a/src/ui/keyselectiondialog.cpp +++ b/src/ui/keyselectiondialog.cpp @@ -1,1001 +1,998 @@ /* -*- c++ -*- keyselectiondialog.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB Based on kpgpui.cpp SPDX-FileCopyrightText: 2001, 2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keyselectiondialog.h" #include "keylistview.h" #include "progressdialog.h" -#include "libkleo/dn.h" +#include #include -// gpgme++ -#include -#include - -#include - -// KDE #include #include #include #include #include -// Qt +#include + #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include +#include + #include #include #include static bool checkKeyUsage(const GpgME::Key &key, unsigned int keyUsage, QString *statusString = nullptr) { auto setStatusString = [statusString](const QString &status) { if (statusString) { *statusString = status; } }; if (keyUsage & Kleo::KeySelectionDialog::ValidKeys) { if (key.isInvalid()) { if (key.keyListMode() & GpgME::Validate) { qCDebug(KLEO_UI_LOG) << "key is invalid"; setStatusString(i18n("The key is not valid.")); return false; } else { qCDebug(KLEO_UI_LOG) << "key is invalid - ignoring"; } } if (key.isExpired()) { qCDebug(KLEO_UI_LOG) << "key is expired"; setStatusString(i18n("The key is expired.")); return false; } else if (key.isRevoked()) { qCDebug(KLEO_UI_LOG) << "key is revoked"; setStatusString(i18n("The key is revoked.")); return false; } else if (key.isDisabled()) { qCDebug(KLEO_UI_LOG) << "key is disabled"; setStatusString(i18n("The key is disabled.")); return false; } } if (keyUsage & Kleo::KeySelectionDialog::EncryptionKeys && !key.canEncrypt()) { qCDebug(KLEO_UI_LOG) << "key can't encrypt"; setStatusString(i18n("The key is not designated for encryption.")); return false; } if (keyUsage & Kleo::KeySelectionDialog::SigningKeys && !key.canSign()) { qCDebug(KLEO_UI_LOG) << "key can't sign"; setStatusString(i18n("The key is not designated for signing.")); return false; } if (keyUsage & Kleo::KeySelectionDialog::CertificationKeys && !key.canCertify()) { qCDebug(KLEO_UI_LOG) << "key can't certify"; setStatusString(i18n("The key is not designated for certifying.")); return false; } if (keyUsage & Kleo::KeySelectionDialog::AuthenticationKeys && !key.canAuthenticate()) { qCDebug(KLEO_UI_LOG) << "key can't authenticate"; setStatusString(i18n("The key is not designated for authentication.")); return false; } if (keyUsage & Kleo::KeySelectionDialog::SecretKeys && !(keyUsage & Kleo::KeySelectionDialog::PublicKeys) && !key.hasSecret()) { qCDebug(KLEO_UI_LOG) << "key isn't secret"; setStatusString(i18n("The key is not secret.")); return false; } if (keyUsage & Kleo::KeySelectionDialog::TrustedKeys && key.protocol() == GpgME::OpenPGP && // only check this for secret keys for now. // Seems validity isn't checked for secret keylistings... !key.hasSecret()) { std::vector uids = key.userIDs(); for (std::vector::const_iterator it = uids.begin(); it != uids.end(); ++it) { if (!it->isRevoked() && it->validity() >= GpgME::UserID::Marginal) { return true; } } qCDebug(KLEO_UI_LOG) << "key has no UIDs with validity >= Marginal"; setStatusString(i18n("The key is not trusted enough.")); return false; } // X.509 keys are always trusted, else they won't be the keybox. // PENDING(marc) check that this ^ is correct setStatusString(i18n("The key can be used.")); return true; } static bool checkKeyUsage(const std::vector &keys, unsigned int keyUsage) { for (auto it = keys.begin(); it != keys.end(); ++it) { if (!checkKeyUsage(*it, keyUsage)) { return false; } } return true; } static inline QString time_t2string(time_t t) { return QDateTime::fromSecsSinceEpoch(t).toString(); } namespace { class ColumnStrategy : public Kleo::KeyListView::ColumnStrategy { public: ColumnStrategy(unsigned int keyUsage); QString title(int col) const override; int width(int col, const QFontMetrics &fm) const override; QString text(const GpgME::Key &key, int col) const override; QString toolTip(const GpgME::Key &key, int col) const override; QIcon icon(const GpgME::Key &key, int col) const override; private: const QIcon mKeyGoodPix, mKeyBadPix, mKeyUnknownPix, mKeyValidPix; const unsigned int mKeyUsage; }; static QString iconPath(const QString &name) { return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("libkleopatra/pics/") + name + QStringLiteral(".png")); } ColumnStrategy::ColumnStrategy(unsigned int keyUsage) : Kleo::KeyListView::ColumnStrategy() , mKeyGoodPix(iconPath(QStringLiteral("key_ok"))) , mKeyBadPix(iconPath(QStringLiteral("key_bad"))) , mKeyUnknownPix(iconPath(QStringLiteral("key_unknown"))) , mKeyValidPix(iconPath(QStringLiteral("key"))) , mKeyUsage(keyUsage) { if (keyUsage == 0) { qCWarning(KLEO_UI_LOG) << "KeySelectionDialog: keyUsage == 0. You want to use AllKeys instead."; } } QString ColumnStrategy::title(int col) const { switch (col) { case 0: return i18n("Key ID"); case 1: return i18n("User ID"); default: return QString(); } } int ColumnStrategy::width(int col, const QFontMetrics &fm) const { if (col == 0) { static const char hexchars[] = "0123456789ABCDEF"; int maxWidth = 0; for (unsigned int i = 0; i < 16; ++i) { maxWidth = qMax(fm.boundingRect(QLatin1Char(hexchars[i])).width(), maxWidth); } return 8 * maxWidth + 2 * 16 /* KIconLoader::SizeSmall */; } return Kleo::KeyListView::ColumnStrategy::width(col, fm); } QString ColumnStrategy::text(const GpgME::Key &key, int col) const { switch (col) { case 0: { if (key.shortKeyID()) { return QString::fromUtf8(key.shortKeyID()); } else { return xi18n("unknown"); } } case 1: { const char *uid = key.userID(0).id(); if (key.protocol() == GpgME::OpenPGP) { return uid && *uid ? QString::fromUtf8(uid) : QString(); } else { // CMS return Kleo::DN(uid).prettyDN(); } } default: return QString(); } } QString ColumnStrategy::toolTip(const GpgME::Key &key, int) const { const char *uid = key.userID(0).id(); const char *fpr = key.primaryFingerprint(); const char *issuer = key.issuerName(); const GpgME::Subkey subkey = key.subkey(0); const QString expiry = subkey.neverExpires() ? i18n("never") : time_t2string(subkey.expirationTime()); const QString creation = time_t2string(subkey.creationTime()); QString keyStatusString; if (!checkKeyUsage(key, mKeyUsage, &keyStatusString)) { // Show the status in bold if there is a problem keyStatusString = QLatin1String("") % keyStatusString % QLatin1String(""); } QString html = QStringLiteral("

"); if (key.protocol() == GpgME::OpenPGP) { html += i18n("OpenPGP key for %1", uid ? QString::fromUtf8(uid) : i18n("unknown")); } else { html += i18n("S/MIME key for %1", uid ? Kleo::DN(uid).prettyDN() : i18n("unknown")); } html += QStringLiteral("

"); const auto addRow = [&html](const QString &name, const QString &value) { html += QStringLiteral("").arg(name, value); }; addRow(i18nc("Key creation date", "Created"), creation); addRow(i18nc("Key Expiration date", "Expiry"), expiry); addRow(i18nc("Key fingerprint", "Fingerprint"), fpr ? QString::fromLatin1(fpr) : i18n("unknown")); if (key.protocol() != GpgME::OpenPGP) { addRow(i18nc("Key issuer", "Issuer"), issuer ? Kleo::DN(issuer).prettyDN() : i18n("unknown")); } addRow(i18nc("Key status", "Status"), keyStatusString); html += QStringLiteral("
%1: %2
"); return html; } QIcon ColumnStrategy::icon(const GpgME::Key &key, int col) const { if (col != 0) { return QIcon(); } // this key did not undergo a validating keylisting yet: if (!(key.keyListMode() & GpgME::Validate)) { return mKeyUnknownPix; } if (!checkKeyUsage(key, mKeyUsage)) { return mKeyBadPix; } if (key.protocol() == GpgME::CMS) { return mKeyGoodPix; } switch (key.userID(0).validity()) { default: case GpgME::UserID::Unknown: case GpgME::UserID::Undefined: return mKeyUnknownPix; case GpgME::UserID::Never: return mKeyValidPix; case GpgME::UserID::Marginal: case GpgME::UserID::Full: case GpgME::UserID::Ultimate: return mKeyGoodPix; } } } static const int sCheckSelectionDelay = 250; Kleo::KeySelectionDialog::KeySelectionDialog(QWidget *parent, Options options) : QDialog(parent) , mOpenPGPBackend(QGpgME::openpgp()) , mSMIMEBackend(QGpgME::smime()) , mKeyUsage(AllKeys) { qCDebug(KLEO_UI_LOG) << "mTruncated:" << mTruncated << "mSavedOffsetY:" << mSavedOffsetY; setUpUI(options, QString()); } Kleo::KeySelectionDialog::KeySelectionDialog(const QString &title, const QString &text, const std::vector &selectedKeys, unsigned int keyUsage, bool extendedSelection, bool rememberChoice, QWidget *parent, bool modal) : QDialog(parent) , mSelectedKeys(selectedKeys) , mKeyUsage(keyUsage) { setWindowTitle(title); setModal(modal); init(rememberChoice, extendedSelection, text, QString()); } Kleo::KeySelectionDialog::KeySelectionDialog(const QString &title, const QString &text, const QString &initialQuery, const std::vector &selectedKeys, unsigned int keyUsage, bool extendedSelection, bool rememberChoice, QWidget *parent, bool modal) : QDialog(parent) , mSelectedKeys(selectedKeys) , mKeyUsage(keyUsage) , mSearchText(initialQuery) , mInitialQuery(initialQuery) { setWindowTitle(title); setModal(modal); init(rememberChoice, extendedSelection, text, initialQuery); } Kleo::KeySelectionDialog::KeySelectionDialog(const QString &title, const QString &text, const QString &initialQuery, unsigned int keyUsage, bool extendedSelection, bool rememberChoice, QWidget *parent, bool modal) : QDialog(parent) , mKeyUsage(keyUsage) , mSearchText(initialQuery) , mInitialQuery(initialQuery) { setWindowTitle(title); setModal(modal); init(rememberChoice, extendedSelection, text, initialQuery); } void Kleo::KeySelectionDialog::setUpUI(Options options, const QString &initialQuery) { auto mainLayout = new QVBoxLayout(this); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); mOkButton = buttonBox->button(QDialogButtonBox::Ok); mOkButton->setDefault(true); mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return); mCheckSelectionTimer = new QTimer(this); mStartSearchTimer = new QTimer(this); QFrame *page = new QFrame(this); mainLayout->addWidget(page); mainLayout->addWidget(buttonBox); mTopLayout = new QVBoxLayout(page); mTopLayout->setContentsMargins(0, 0, 0, 0); mTextLabel = new QLabel(page); mTextLabel->setWordWrap(true); // Setting the size policy is necessary as a workaround for https://issues.kolab.org/issue4429 // and http://bugreports.qt.nokia.com/browse/QTBUG-8740 mTextLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); connect(mTextLabel, &QLabel::linkActivated, this, &KeySelectionDialog::slotStartCertificateManager); mTopLayout->addWidget(mTextLabel); mTextLabel->hide(); QPushButton *const searchExternalPB = new QPushButton(i18n("Search for &External Certificates"), page); mTopLayout->addWidget(searchExternalPB, 0, Qt::AlignLeft); connect(searchExternalPB, &QAbstractButton::clicked, this, &KeySelectionDialog::slotStartSearchForExternalCertificates); if (initialQuery.isEmpty()) { searchExternalPB->hide(); } auto hlay = new QHBoxLayout(); mTopLayout->addLayout(hlay); auto le = new QLineEdit(page); le->setClearButtonEnabled(true); le->setText(initialQuery); QLabel *lbSearchFor = new QLabel(i18n("&Search for:"), page); lbSearchFor->setBuddy(le); hlay->addWidget(lbSearchFor); hlay->addWidget(le, 1); le->setFocus(); connect(le, &QLineEdit::textChanged, this, [this](const QString &s) { slotSearch(s); }); connect(mStartSearchTimer, &QTimer::timeout, this, &KeySelectionDialog::slotFilter); mKeyListView = new KeyListView(new ColumnStrategy(mKeyUsage), nullptr, page); mKeyListView->setObjectName(QStringLiteral("mKeyListView")); mKeyListView->header()->stretchLastSection(); mKeyListView->setRootIsDecorated(true); mKeyListView->setSortingEnabled(true); mKeyListView->header()->setSortIndicatorShown(true); mKeyListView->header()->setSortIndicator(1, Qt::AscendingOrder); // sort by User ID if (options & ExtendedSelection) { mKeyListView->setSelectionMode(QAbstractItemView::ExtendedSelection); } mTopLayout->addWidget(mKeyListView, 10); if (options & RememberChoice) { mRememberCB = new QCheckBox(i18n("&Remember choice"), page); mTopLayout->addWidget(mRememberCB); mRememberCB->setWhatsThis( i18n("

If you check this box your choice will " "be stored and you will not be asked again." "

")); } connect(mCheckSelectionTimer, &QTimer::timeout, this, [this]() { slotCheckSelection(); }); connectSignals(); connect(mKeyListView, &Kleo::KeyListView::doubleClicked, this, &KeySelectionDialog::slotTryOk); connect(mKeyListView, &KeyListView::contextMenu, this, &KeySelectionDialog::slotRMB); if (options & RereadKeys) { QPushButton *button = new QPushButton(i18n("&Reread Keys")); buttonBox->addButton(button, QDialogButtonBox::ActionRole); connect(button, &QPushButton::clicked, this, &KeySelectionDialog::slotRereadKeys); } if (options & ExternalCertificateManager) { QPushButton *button = new QPushButton(i18n("&Start Certificate Manager")); buttonBox->addButton(button, QDialogButtonBox::ActionRole); connect(button, &QPushButton::clicked, this, [this]() { slotStartCertificateManager(); }); } connect(mOkButton, &QPushButton::clicked, this, &KeySelectionDialog::slotOk); connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &KeySelectionDialog::slotCancel); mTopLayout->activate(); if (qApp) { QSize dialogSize(sizeHint()); KConfigGroup dialogConfig(KSharedConfig::openStateConfig(), "Key Selection Dialog"); dialogSize = dialogConfig.readEntry("Dialog size", dialogSize); const QByteArray headerState = dialogConfig.readEntry("header", QByteArray()); if (!headerState.isEmpty()) { mKeyListView->header()->restoreState(headerState); } resize(dialogSize); } } void Kleo::KeySelectionDialog::init(bool rememberChoice, bool extendedSelection, const QString &text, const QString &initialQuery) { Options options = {RereadKeys, ExternalCertificateManager}; options.setFlag(ExtendedSelection, extendedSelection); options.setFlag(RememberChoice, rememberChoice); setUpUI(options, initialQuery); setText(text); if (mKeyUsage & OpenPGPKeys) { mOpenPGPBackend = QGpgME::openpgp(); } if (mKeyUsage & SMIMEKeys) { mSMIMEBackend = QGpgME::smime(); } slotRereadKeys(); } Kleo::KeySelectionDialog::~KeySelectionDialog() { disconnectSignals(); KConfigGroup dialogConfig(KSharedConfig::openStateConfig(), "Key Selection Dialog"); dialogConfig.writeEntry("Dialog size", size()); dialogConfig.writeEntry("header", mKeyListView->header()->saveState()); dialogConfig.sync(); } void Kleo::KeySelectionDialog::setText(const QString &text) { mTextLabel->setText(text); mTextLabel->setVisible(!text.isEmpty()); } void Kleo::KeySelectionDialog::setKeys(const std::vector &keys) { for (const GpgME::Key &key : keys) { mKeyListView->slotAddKey(key); } } void Kleo::KeySelectionDialog::connectSignals() { if (mKeyListView->isMultiSelection()) { connect(mKeyListView, &QTreeWidget::itemSelectionChanged, this, &KeySelectionDialog::slotSelectionChanged); } else { connect(mKeyListView, qOverload(&KeyListView::selectionChanged), this, qOverload(&KeySelectionDialog::slotCheckSelection)); } } void Kleo::KeySelectionDialog::disconnectSignals() { if (mKeyListView->isMultiSelection()) { disconnect(mKeyListView, &QTreeWidget::itemSelectionChanged, this, &KeySelectionDialog::slotSelectionChanged); } else { disconnect(mKeyListView, qOverload(&KeyListView::selectionChanged), this, qOverload(&KeySelectionDialog::slotCheckSelection)); } } const GpgME::Key &Kleo::KeySelectionDialog::selectedKey() const { static const GpgME::Key null = GpgME::Key::null; if (mKeyListView->isMultiSelection() || !mKeyListView->selectedItem()) { return null; } return mKeyListView->selectedItem()->key(); } QString Kleo::KeySelectionDialog::fingerprint() const { return QLatin1String(selectedKey().primaryFingerprint()); } QStringList Kleo::KeySelectionDialog::fingerprints() const { QStringList result; for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) { if (const char *fpr = it->primaryFingerprint()) { result.push_back(QLatin1String(fpr)); } } return result; } QStringList Kleo::KeySelectionDialog::pgpKeyFingerprints() const { QStringList result; for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) { if (it->protocol() == GpgME::OpenPGP) { if (const char *fpr = it->primaryFingerprint()) { result.push_back(QLatin1String(fpr)); } } } return result; } QStringList Kleo::KeySelectionDialog::smimeFingerprints() const { QStringList result; for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) { if (it->protocol() == GpgME::CMS) { if (const char *fpr = it->primaryFingerprint()) { result.push_back(QLatin1String(fpr)); } } } return result; } void Kleo::KeySelectionDialog::slotRereadKeys() { mKeyListView->clear(); mListJobCount = 0; mTruncated = 0; mSavedOffsetY = mKeyListView->verticalScrollBar()->value(); disconnectSignals(); mKeyListView->setEnabled(false); // FIXME: save current selection if (mOpenPGPBackend) { startKeyListJobForBackend(mOpenPGPBackend, std::vector(), false /*non-validating*/); } if (mSMIMEBackend) { startKeyListJobForBackend(mSMIMEBackend, std::vector(), false /*non-validating*/); } if (mListJobCount == 0) { mKeyListView->setEnabled(true); KMessageBox::information(this, i18n("No backends found for listing keys. " "Check your installation."), i18n("Key Listing Failed")); connectSignals(); } } void Kleo::KeySelectionDialog::slotStartCertificateManager(const QString &query) { QStringList args; if (!query.isEmpty()) { args << QStringLiteral("--search") << query; } const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra")); if (exec.isEmpty()) { qCWarning(KLEO_UI_LOG) << "Could not find kleopatra executable in PATH"; KMessageBox::error(this, i18n("Could not start certificate manager; " "please check your installation."), i18n("Certificate Manager Error")); } else { QProcess::startDetached(QStringLiteral("kleopatra"), args); qCDebug(KLEO_UI_LOG) << "\nslotStartCertManager(): certificate manager started."; } } #ifndef __KLEO_UI_SHOW_KEY_LIST_ERROR_H__ #define __KLEO_UI_SHOW_KEY_LIST_ERROR_H__ static void showKeyListError(QWidget *parent, const GpgME::Error &err) { Q_ASSERT(err); const QString msg = i18n( "

An error occurred while fetching " "the keys from the backend:

" "

%1

", QString::fromLocal8Bit(err.asString())); KMessageBox::error(parent, msg, i18n("Key Listing Failed")); } #endif // __KLEO_UI_SHOW_KEY_LIST_ERROR_H__ namespace { struct ExtractFingerprint { QString operator()(const GpgME::Key &key) { return QLatin1String(key.primaryFingerprint()); } }; } void Kleo::KeySelectionDialog::startKeyListJobForBackend(const QGpgME::Protocol *backend, const std::vector &keys, bool validate) { Q_ASSERT(backend); QGpgME::KeyListJob *job = backend->keyListJob(false, false, validate); // local, w/o sigs, validation as given if (!job) { return; } connect(job, &QGpgME::KeyListJob::result, this, &KeySelectionDialog::slotKeyListResult); if (validate) { connect(job, &QGpgME::KeyListJob::nextKey, mKeyListView, &KeyListView::slotRefreshKey); } else { connect(job, &QGpgME::KeyListJob::nextKey, mKeyListView, &KeyListView::slotAddKey); } QStringList fprs; std::transform(keys.begin(), keys.end(), std::back_inserter(fprs), ExtractFingerprint()); const GpgME::Error err = job->start(fprs, mKeyUsage & SecretKeys && !(mKeyUsage & PublicKeys)); if (err) { return showKeyListError(this, err); } #ifndef LIBKLEO_NO_PROGRESSDIALOG // FIXME: create a MultiProgressDialog: (void)new ProgressDialog(job, validate ? i18n("Checking selected keys...") : i18n("Fetching keys..."), this); #endif ++mListJobCount; } static void selectKeys(Kleo::KeyListView *klv, const std::vector &selectedKeys) { klv->clearSelection(); if (selectedKeys.empty()) { return; } for (auto it = selectedKeys.begin(); it != selectedKeys.end(); ++it) { if (Kleo::KeyListViewItem *item = klv->itemByFingerprint(it->primaryFingerprint())) { item->setSelected(true); } } } void Kleo::KeySelectionDialog::slotKeyListResult(const GpgME::KeyListResult &res) { if (res.error()) { showKeyListError(this, res.error()); } else if (res.isTruncated()) { ++mTruncated; } if (--mListJobCount > 0) { return; // not yet finished... } if (mTruncated > 0) { KMessageBox::information(this, i18np("One backend returned truncated output.

" "Not all available keys are shown

", "%1 backends returned truncated output.

" "Not all available keys are shown

", mTruncated), i18n("Key List Result")); } mKeyListView->flushKeys(); mKeyListView->setEnabled(true); mListJobCount = mTruncated = 0; mKeysToCheck.clear(); selectKeys(mKeyListView, mSelectedKeys); slotFilter(); connectSignals(); slotSelectionChanged(); // restore the saved position of the contents mKeyListView->verticalScrollBar()->setValue(mSavedOffsetY); mSavedOffsetY = 0; } void Kleo::KeySelectionDialog::slotSelectionChanged() { qCDebug(KLEO_UI_LOG) << "KeySelectionDialog::slotSelectionChanged()"; // (re)start the check selection timer. Checking the selection is delayed // because else drag-selection doesn't work very good (checking key trust // is slow). mCheckSelectionTimer->start(sCheckSelectionDelay); } namespace { struct AlreadyChecked { bool operator()(const GpgME::Key &key) const { return key.keyListMode() & GpgME::Validate; } }; } void Kleo::KeySelectionDialog::slotCheckSelection(KeyListViewItem *item) { qCDebug(KLEO_UI_LOG) << "KeySelectionDialog::slotCheckSelection()"; mCheckSelectionTimer->stop(); mSelectedKeys.clear(); if (!mKeyListView->isMultiSelection()) { if (item) { mSelectedKeys.push_back(item->key()); } } for (KeyListViewItem *it = mKeyListView->firstChild(); it; it = it->nextSibling()) { if (it->isSelected()) { mSelectedKeys.push_back(it->key()); } } mKeysToCheck.clear(); std::remove_copy_if(mSelectedKeys.begin(), mSelectedKeys.end(), std::back_inserter(mKeysToCheck), AlreadyChecked()); if (mKeysToCheck.empty()) { mOkButton->setEnabled(!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)); return; } // performed all fast checks - now for validating key listing: startValidatingKeyListing(); } void Kleo::KeySelectionDialog::startValidatingKeyListing() { if (mKeysToCheck.empty()) { return; } mListJobCount = 0; mTruncated = 0; mSavedOffsetY = mKeyListView->verticalScrollBar()->value(); disconnectSignals(); mKeyListView->setEnabled(false); std::vector smime; std::vector openpgp; for (std::vector::const_iterator it = mKeysToCheck.begin(); it != mKeysToCheck.end(); ++it) { if (it->protocol() == GpgME::OpenPGP) { openpgp.push_back(*it); } else { smime.push_back(*it); } } if (!openpgp.empty()) { Q_ASSERT(mOpenPGPBackend); startKeyListJobForBackend(mOpenPGPBackend, openpgp, true /*validate*/); } if (!smime.empty()) { Q_ASSERT(mSMIMEBackend); startKeyListJobForBackend(mSMIMEBackend, smime, true /*validate*/); } Q_ASSERT(mListJobCount > 0); } bool Kleo::KeySelectionDialog::rememberSelection() const { return mRememberCB && mRememberCB->isChecked(); } void Kleo::KeySelectionDialog::slotRMB(Kleo::KeyListViewItem *item, const QPoint &p) { if (!item) { return; } mCurrentContextMenuItem = item; QMenu menu; menu.addAction(i18n("Recheck Key"), this, &KeySelectionDialog::slotRecheckKey); menu.exec(p); } void Kleo::KeySelectionDialog::slotRecheckKey() { if (!mCurrentContextMenuItem || mCurrentContextMenuItem->key().isNull()) { return; } mKeysToCheck.clear(); mKeysToCheck.push_back(mCurrentContextMenuItem->key()); } void Kleo::KeySelectionDialog::slotTryOk() { if (!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)) { slotOk(); } } void Kleo::KeySelectionDialog::slotOk() { if (mCheckSelectionTimer->isActive()) { slotCheckSelection(); } #if 0 // Laurent I don't understand why we returns here. // button could be disabled again after checking the selected key1 if (!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)) { return; } #endif mStartSearchTimer->stop(); accept(); } void Kleo::KeySelectionDialog::slotCancel() { mCheckSelectionTimer->stop(); mStartSearchTimer->stop(); reject(); } void Kleo::KeySelectionDialog::slotSearch(const QString &text) { mSearchText = text.trimmed().toUpper(); slotSearch(); } void Kleo::KeySelectionDialog::slotSearch() { mStartSearchTimer->setSingleShot(true); mStartSearchTimer->start(sCheckSelectionDelay); } void Kleo::KeySelectionDialog::slotFilter() { if (mSearchText.isEmpty()) { showAllItems(); return; } // OK, so we need to filter: QRegExp keyIdRegExp(QLatin1String("(?:0x)?[A-F0-9]{1,8}"), Qt::CaseInsensitive); if (keyIdRegExp.exactMatch(mSearchText)) { if (mSearchText.startsWith(QLatin1String("0X"))) // search for keyID only: { filterByKeyID(mSearchText.mid(2)); } else // search for UID and keyID: { filterByKeyIDOrUID(mSearchText); } } else { // search in UID: filterByUID(mSearchText); } } void Kleo::KeySelectionDialog::filterByKeyID(const QString &keyID) { Q_ASSERT(keyID.length() <= 8); Q_ASSERT(!keyID.isEmpty()); // regexp in slotFilter should prevent these if (keyID.isEmpty()) { showAllItems(); } else { for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) { item->setHidden(!item->text(0).toUpper().startsWith(keyID)); } } } static bool anyUIDMatches(const Kleo::KeyListViewItem *item, QRegExp &rx) { if (!item) { return false; } const std::vector uids = item->key().userIDs(); for (auto it = uids.begin(); it != uids.end(); ++it) { if (it->id() && rx.indexIn(QString::fromUtf8(it->id())) >= 0) { return true; } } return false; } void Kleo::KeySelectionDialog::filterByKeyIDOrUID(const QString &str) { Q_ASSERT(!str.isEmpty()); // match beginnings of words: QRegExp rx(QLatin1String("\\b") + QRegExp::escape(str), Qt::CaseInsensitive); for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) { item->setHidden(!item->text(0).toUpper().startsWith(str) && !anyUIDMatches(item, rx)); } } void Kleo::KeySelectionDialog::filterByUID(const QString &str) { Q_ASSERT(!str.isEmpty()); // match beginnings of words: QRegExp rx(QLatin1String("\\b") + QRegExp::escape(str), Qt::CaseInsensitive); for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) { item->setHidden(!anyUIDMatches(item, rx)); } } void Kleo::KeySelectionDialog::showAllItems() { for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) { item->setHidden(false); } } diff --git a/src/ui/keyselectiondialog.h b/src/ui/keyselectiondialog.h index d7e72f43c..464de1bcf 100644 --- a/src/ui/keyselectiondialog.h +++ b/src/ui/keyselectiondialog.h @@ -1,202 +1,203 @@ /* -*- c++ -*- keyselectiondialog.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB Based on kpgpui.h SPDX-FileCopyrightText: 2001, 2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" -#include -#include +#include #include #include +#include + #include class QCheckBox; class QLabel; class QPoint; class QRegExp; class QTimer; class QVBoxLayout; namespace Kleo { class KeyListView; class KeyListViewItem; } namespace GpgME { class KeyListResult; } namespace Kleo { class KLEO_EXPORT KeySelectionDialog : public QDialog { Q_OBJECT public: enum Option { // clang-format off RereadKeys = 0x01, ExternalCertificateManager = 0x02, ExtendedSelection = 0x04, RememberChoice = 0x08, // clang-format on }; Q_DECLARE_FLAGS(Options, Option) enum KeyUsage { // clang-format off PublicKeys = 1, SecretKeys = 2, EncryptionKeys = 4, SigningKeys = 8, ValidKeys = 16, TrustedKeys = 32, CertificationKeys = 64, AuthenticationKeys = 128, OpenPGPKeys = 256, SMIMEKeys = 512, AllKeys = PublicKeys | SecretKeys | OpenPGPKeys | SMIMEKeys, ValidEncryptionKeys = AllKeys | EncryptionKeys | ValidKeys, ValidTrustedEncryptionKeys = AllKeys | EncryptionKeys | ValidKeys | TrustedKeys // clang-format on }; explicit KeySelectionDialog(QWidget *parent = nullptr, Options options = Options()); KeySelectionDialog(const QString &title, const QString &text, const std::vector &selectedKeys = std::vector(), unsigned int keyUsage = AllKeys, bool extendedSelection = false, bool rememberChoice = false, QWidget *parent = nullptr, bool modal = true); KeySelectionDialog(const QString &title, const QString &text, const QString &initialPattern, const std::vector &selectedKeys, unsigned int keyUsage = AllKeys, bool extendedSelection = false, bool rememberChoice = false, QWidget *parent = nullptr, bool modal = true); KeySelectionDialog(const QString &title, const QString &text, const QString &initialPattern, unsigned int keyUsage = AllKeys, bool extendedSelection = false, bool rememberChoice = false, QWidget *parent = nullptr, bool modal = true); ~KeySelectionDialog() override; void setText(const QString &text); void setKeys(const std::vector &keys); /** Returns the key ID of the selected key in single selection mode. Otherwise it returns a null key. */ const GpgME::Key &selectedKey() const; QString fingerprint() const; /** Returns a list of selected key IDs. */ const std::vector &selectedKeys() const { return mSelectedKeys; } /// Return all the selected fingerprints QStringList fingerprints() const; /// Return the selected openpgp fingerprints QStringList pgpKeyFingerprints() const; /// Return the selected smime fingerprints QStringList smimeFingerprints() const; bool rememberSelection() const; // Could be used by derived classes to insert their own widget QVBoxLayout *topLayout() const { return mTopLayout; } private Q_SLOTS: void slotRereadKeys(); void slotStartCertificateManager(const QString &query = QString()); void slotStartSearchForExternalCertificates() { slotStartCertificateManager(mInitialQuery); } void slotKeyListResult(const GpgME::KeyListResult &); void slotSelectionChanged(); void slotCheckSelection() { slotCheckSelection(nullptr); } void slotCheckSelection(Kleo::KeyListViewItem *); void slotRMB(Kleo::KeyListViewItem *, const QPoint &); void slotRecheckKey(); void slotTryOk(); void slotOk(); void slotCancel(); void slotSearch(const QString &text); void slotSearch(); void slotFilter(); private: void filterByKeyID(const QString &keyID); void filterByKeyIDOrUID(const QString &keyID); void filterByUID(const QString &uid); void showAllItems(); void connectSignals(); void disconnectSignals(); void startKeyListJobForBackend(const QGpgME::Protocol *, const std::vector &, bool); void startValidatingKeyListing(); void setUpUI(Options options, const QString &); void init(bool, bool, const QString &, const QString &); private: QVBoxLayout *mTopLayout = nullptr; QLabel *mTextLabel = nullptr; Kleo::KeyListView *mKeyListView = nullptr; Kleo::KeyListViewItem *mCurrentContextMenuItem = nullptr; QCheckBox *mRememberCB = nullptr; QPushButton *mOkButton = nullptr; const QGpgME::Protocol *mOpenPGPBackend = nullptr; const QGpgME::Protocol *mSMIMEBackend = nullptr; std::vector mSelectedKeys, mKeysToCheck; unsigned int mKeyUsage; QTimer *mCheckSelectionTimer = nullptr; QTimer *mStartSearchTimer = nullptr; // cross-eventloop temporaries: QString mSearchText; const QString mInitialQuery; int mTruncated = 0; int mListJobCount = 0; int mSavedOffsetY = 0; }; } Q_DECLARE_OPERATORS_FOR_FLAGS(Kleo::KeySelectionDialog::Options) diff --git a/src/ui/messagebox.cpp b/src/ui/messagebox.cpp index 648b205c9..585f9b09d 100644 --- a/src/ui/messagebox.cpp +++ b/src/ui/messagebox.cpp @@ -1,250 +1,252 @@ /* messagebox.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 #include "messagebox.h" #include "auditlogviewer.h" #include -#include -#include -#include - #include #include +#include + +#include + #include #include #include -#include +#include +#include + #include using namespace Kleo; using namespace Kleo::Private; using namespace GpgME; using namespace QGpgME; // static void MessageBox::auditLog(QWidget *parent, const Job *job, const QString &caption) { if (!job) { return; } if (!GpgME::hasFeature(AuditLogFeature, 0) || !job->isAuditLogSupported()) { KMessageBox::information(parent, i18n("Your system does not have support for GnuPG Audit Logs"), i18n("System Error")); return; } const GpgME::Error err = job->auditLogError(); if (err && err.code() != GPG_ERR_NO_DATA) { KMessageBox::information(parent, i18n("An error occurred while trying to retrieve the GnuPG Audit Log:\n%1", QString::fromLocal8Bit(err.asString())), i18n("GnuPG Audit Log Error")); return; } const QString log = job->auditLogAsHtml(); if (log.isEmpty()) { KMessageBox::information(parent, i18n("No GnuPG Audit Log available for this operation."), i18n("No GnuPG Audit Log")); return; } auditLog(parent, log, caption); } // static void MessageBox::auditLog(QWidget *parent, const QString &log, const QString &caption) { auto const alv = new AuditLogViewer(log, parent); alv->setAttribute(Qt::WA_DeleteOnClose); alv->setObjectName(QStringLiteral("alv")); alv->setWindowTitle(caption); alv->show(); } // static void MessageBox::auditLog(QWidget *parent, const Job *job) { auditLog(parent, job, i18n("GnuPG Audit Log Viewer")); } // static void MessageBox::auditLog(QWidget *parent, const QString &log) { auditLog(parent, log, i18n("GnuPG Audit Log Viewer")); } static QString to_information_string(const SigningResult &result) { return (result.error() // ? i18n("Signing failed: %1", QString::fromLocal8Bit(result.error().asString())) : i18n("Signing successful")); } static QString to_error_string(const SigningResult &result) { return to_information_string(result); } static QString to_information_string(const EncryptionResult &result) { return (result.error() // ? i18n("Encryption failed: %1", QString::fromLocal8Bit(result.error().asString())) : i18n("Encryption successful")); } static QString to_error_string(const EncryptionResult &result) { return to_information_string(result); } static QString to_information_string(const SigningResult &sresult, const EncryptionResult &eresult) { return to_information_string(sresult) + QLatin1Char('\n') + to_information_string(eresult); } static QString to_error_string(const SigningResult &sresult, const EncryptionResult &eresult) { return to_information_string(sresult, eresult); } // static void MessageBox::information(QWidget *parent, const SigningResult &result, const Job *job, KMessageBox::Options options) { information(parent, result, job, i18n("Signing Result"), options); } // static void MessageBox::information(QWidget *parent, const SigningResult &result, const Job *job, const QString &caption, KMessageBox::Options options) { make(parent, QMessageBox::Information, to_information_string(result), job, caption, options); } // static void MessageBox::error(QWidget *parent, const SigningResult &result, const Job *job, KMessageBox::Options options) { error(parent, result, job, i18n("Signing Error"), options); } // static void MessageBox::error(QWidget *parent, const SigningResult &result, const Job *job, const QString &caption, KMessageBox::Options options) { make(parent, QMessageBox::Critical, to_error_string(result), job, caption, options); } // static void MessageBox::information(QWidget *parent, const EncryptionResult &result, const Job *job, KMessageBox::Options options) { information(parent, result, job, i18n("Encryption Result"), options); } // static void MessageBox::information(QWidget *parent, const EncryptionResult &result, const Job *job, const QString &caption, KMessageBox::Options options) { make(parent, QMessageBox::Information, to_information_string(result), job, caption, options); } // static void MessageBox::error(QWidget *parent, const EncryptionResult &result, const Job *job, KMessageBox::Options options) { error(parent, result, job, i18n("Encryption Error"), options); } // static void MessageBox::error(QWidget *parent, const EncryptionResult &result, const Job *job, const QString &caption, KMessageBox::Options options) { make(parent, QMessageBox::Critical, to_error_string(result), job, caption, options); } // static void MessageBox::information(QWidget *parent, const SigningResult &sresult, const EncryptionResult &eresult, const Job *job, KMessageBox::Options options) { information(parent, sresult, eresult, job, i18n("Encryption Result"), options); } // static void MessageBox::information(QWidget *parent, const SigningResult &sresult, const EncryptionResult &eresult, const Job *job, const QString &caption, KMessageBox::Options options) { make(parent, QMessageBox::Information, to_information_string(sresult, eresult), job, caption, options); } // static void MessageBox::error(QWidget *parent, const SigningResult &sresult, const EncryptionResult &eresult, const Job *job, KMessageBox::Options options) { error(parent, sresult, eresult, job, i18n("Encryption Error"), options); } // static void MessageBox::error(QWidget *parent, const SigningResult &sresult, const EncryptionResult &eresult, const Job *job, const QString &caption, KMessageBox::Options options) { make(parent, QMessageBox::Critical, to_error_string(sresult, eresult), job, caption, options); } // static bool MessageBox::showAuditLogButton(const QGpgME::Job *job) { if (!job) { qCDebug(KLEO_UI_LOG) << "not showing audit log button (no job instance)"; return false; } if (!GpgME::hasFeature(GpgME::AuditLogFeature, 0)) { qCDebug(KLEO_UI_LOG) << "not showing audit log button (gpgme too old)"; return false; } if (!job->isAuditLogSupported()) { qCDebug(KLEO_UI_LOG) << "not showing audit log button (not supported)"; return false; } if (job->auditLogError().code() == GPG_ERR_NO_DATA) { qCDebug(KLEO_UI_LOG) << "not showing audit log button (GPG_ERR_NO_DATA)"; return false; } if (!job->auditLogError() && job->auditLogAsHtml().isEmpty()) { qCDebug(KLEO_UI_LOG) << "not showing audit log button (success, but result empty)"; return false; } return true; } // static void MessageBox::make(QWidget *parent, QMessageBox::Icon icon, const QString &text, const Job *job, const QString &caption, KMessageBox::Options options) { QDialog *dialog = new QDialog(parent); dialog->setWindowTitle(caption); QDialogButtonBox *box = new QDialogButtonBox(showAuditLogButton(job) ? (QDialogButtonBox::Yes | QDialogButtonBox::No) : QDialogButtonBox::Yes, parent); QPushButton *yesButton = box->button(QDialogButtonBox::Yes); yesButton->setDefault(true); // dialog->setEscapeButton(KDialog::Yes); dialog->setObjectName(QStringLiteral("error")); dialog->setModal(true); KGuiItem::assign(yesButton, KStandardGuiItem::ok()); if (GpgME::hasFeature(GpgME::AuditLogFeature, 0)) { KGuiItem::assign(box->button(QDialogButtonBox::No), KGuiItem(i18n("&Show Audit Log"))); } if (QDialogButtonBox::No == KMessageBox::createKMessageBox(dialog, box, icon, text, QStringList(), QString(), nullptr, options)) { auditLog(nullptr, job); } } diff --git a/src/ui/newkeyapprovaldialog.cpp b/src/ui/newkeyapprovaldialog.cpp index 0012a0a5e..3af73e993 100644 --- a/src/ui/newkeyapprovaldialog.cpp +++ b/src/ui/newkeyapprovaldialog.cpp @@ -1,931 +1,931 @@ /* -*- c++ -*- newkeyapprovaldialog.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "newkeyapprovaldialog.h" #include "keyselectioncombo.h" #include "progressdialog.h" -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include +#include +#include + #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include -#include - #include #include using namespace Kleo; using namespace GpgME; namespace { class EncryptFilter : public DefaultKeyFilter { public: EncryptFilter() : DefaultKeyFilter() { setCanEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr s_encryptFilter = std::shared_ptr(new EncryptFilter); class OpenPGPFilter : public DefaultKeyFilter { public: OpenPGPFilter() : DefaultKeyFilter() { setIsOpenPGP(DefaultKeyFilter::Set); setCanEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr s_pgpEncryptFilter = std::shared_ptr(new OpenPGPFilter); class OpenPGPSignFilter : public DefaultKeyFilter { public: OpenPGPSignFilter() : DefaultKeyFilter() { /* Also list unusable keys to make it transparent why they are unusable */ setDisabled(DefaultKeyFilter::NotSet); setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setCanSign(DefaultKeyFilter::Set); setHasSecret(DefaultKeyFilter::Set); setIsOpenPGP(DefaultKeyFilter::Set); } }; static std::shared_ptr s_pgpSignFilter = std::shared_ptr(new OpenPGPSignFilter); class SMIMEFilter : public DefaultKeyFilter { public: SMIMEFilter() : DefaultKeyFilter() { setIsOpenPGP(DefaultKeyFilter::NotSet); setCanEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr s_smimeEncryptFilter = std::shared_ptr(new SMIMEFilter); class SMIMESignFilter : public DefaultKeyFilter { public: SMIMESignFilter() : DefaultKeyFilter() { setDisabled(DefaultKeyFilter::NotSet); setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setCanSign(DefaultKeyFilter::Set); setIsOpenPGP(DefaultKeyFilter::NotSet); setHasSecret(DefaultKeyFilter::Set); } }; static std::shared_ptr s_smimeSignFilter = std::shared_ptr(new SMIMESignFilter); /* Some decoration and a button to remove the filter for a keyselectioncombo */ class ComboWidget : public QWidget { Q_OBJECT public: explicit ComboWidget(KeySelectionCombo *combo) : mCombo(combo) , mFilterBtn(new QPushButton) { auto hLay = new QHBoxLayout(this); auto infoBtn = new QPushButton; infoBtn->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual"))); infoBtn->setIconSize(QSize(22, 22)); infoBtn->setFlat(true); hLay->addWidget(infoBtn); hLay->addWidget(combo, 1); hLay->addWidget(mFilterBtn, 0); connect(infoBtn, &QPushButton::clicked, this, [this, infoBtn]() { QToolTip::showText(infoBtn->mapToGlobal(QPoint()) + QPoint(infoBtn->width(), 0), mCombo->currentData(Qt::ToolTipRole).toString(), infoBtn, QRect(), 30000); }); // FIXME: This is ugly to enforce but otherwise the // icon is broken. combo->setMinimumHeight(22); mFilterBtn->setMinimumHeight(23); updateFilterButton(); connect(mFilterBtn, &QPushButton::clicked, this, [this]() { const QString curFilter = mCombo->idFilter(); if (curFilter.isEmpty()) { setIdFilter(mLastIdFilter); mLastIdFilter = QString(); } else { setIdFilter(QString()); mLastIdFilter = curFilter; } }); } void setIdFilter(const QString &id) { mCombo->setIdFilter(id); updateFilterButton(); } void updateFilterButton() { if (mCombo->idFilter().isEmpty()) { mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-add-filters"))); mFilterBtn->setToolTip(i18n("Show keys matching the email address")); } else { mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters"))); mFilterBtn->setToolTip(i18n("Show all keys")); } } KeySelectionCombo *combo() { return mCombo; } GpgME::Protocol fixedProtocol() const { return mFixedProtocol; } void setFixedProtocol(GpgME::Protocol proto) { mFixedProtocol = proto; } private: KeySelectionCombo *mCombo; QPushButton *mFilterBtn; QString mLastIdFilter; GpgME::Protocol mFixedProtocol = GpgME::UnknownProtocol; }; static enum GpgME::UserID::Validity keyValidity(const GpgME::Key &key) { enum GpgME::UserID::Validity validity = GpgME::UserID::Validity::Unknown; for (const auto &uid : key.userIDs()) { if (validity == GpgME::UserID::Validity::Unknown || validity > uid.validity()) { validity = uid.validity(); } } return validity; } static bool key_has_addr(const GpgME::Key &key, const QString &addr) { for (const auto &uid : key.userIDs()) { if (QString::fromStdString(uid.addrSpec()).toLower() == addr.toLower()) { return true; } } return false; } bool anyKeyHasProtocol(const std::vector &keys, GpgME::Protocol protocol) { return std::any_of(std::begin(keys), std::end(keys), [protocol](const auto &key) { return key.protocol() == protocol; }); } Key findfirstKeyOfType(const std::vector &keys, GpgME::Protocol protocol) { const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol](const auto &key) { return key.protocol() == protocol; }); return it != std::end(keys) ? *it : Key(); } } // namespace class NewKeyApprovalDialog::Private { private: enum Action { Unset, GenerateKey, IgnoreKey, }; public: enum { OpenPGPButtonId = 1, SMIMEButtonId = 2, }; Private(NewKeyApprovalDialog *qq, bool encrypt, bool sign, GpgME::Protocol forcedProtocol, GpgME::Protocol presetProtocol, const QString &sender, bool allowMixed) : mForcedProtocol{forcedProtocol} , mSender{sender} , mSign{sign} , mEncrypt{encrypt} , mAllowMixed{allowMixed} , q{qq} { Q_ASSERT(forcedProtocol == GpgME::UnknownProtocol || presetProtocol == GpgME::UnknownProtocol || presetProtocol == forcedProtocol); Q_ASSERT(!allowMixed || (allowMixed && forcedProtocol == GpgME::UnknownProtocol)); Q_ASSERT(!(!allowMixed && presetProtocol == GpgME::UnknownProtocol)); // We do the translation here to avoid having the same string multiple times. mGenerateTooltip = i18nc( "@info:tooltip for a 'Generate new key pair' action " "in a combobox when a user does not yet have an OpenPGP or S/MIME key.", "Generate a new key using your E-Mail address.

" "The key is necessary to decrypt and sign E-Mails. " "You will be asked for a passphrase to protect this key and the protected key " "will be stored in your home directory."); mMainLay = new QVBoxLayout; QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); mOkButton = btnBox->button(QDialogButtonBox::Ok); #ifndef NDEBUG mOkButton->setObjectName(QStringLiteral("ok button")); #endif QObject::connect(btnBox, &QDialogButtonBox::accepted, q, [this]() { accepted(); }); QObject::connect(btnBox, &QDialogButtonBox::rejected, q, &QDialog::reject); mScrollArea = new QScrollArea; mScrollArea->setWidget(new QWidget); mScrollLayout = new QVBoxLayout; mScrollArea->widget()->setLayout(mScrollLayout); mScrollArea->setWidgetResizable(true); mScrollArea->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow); mScrollArea->setFrameStyle(QFrame::NoFrame); mScrollLayout->setContentsMargins(0, 0, 0, 0); q->setWindowTitle(i18nc("@title:window", "Security approval")); auto fmtLayout = new QHBoxLayout; mFormatBtns = new QButtonGroup(qq); QAbstractButton *pgpBtn; QAbstractButton *smimeBtn; if (mAllowMixed) { pgpBtn = new QCheckBox(i18n("OpenPGP")); smimeBtn = new QCheckBox(i18n("S/MIME")); } else { pgpBtn = new QRadioButton(i18n("OpenPGP")); smimeBtn = new QRadioButton(i18n("S/MIME")); } #ifndef NDEBUG pgpBtn->setObjectName(QStringLiteral("openpgp button")); smimeBtn->setObjectName(QStringLiteral("smime button")); #endif mFormatBtns->addButton(pgpBtn, OpenPGPButtonId); mFormatBtns->addButton(smimeBtn, SMIMEButtonId); mFormatBtns->setExclusive(!mAllowMixed); fmtLayout->addStretch(-1); fmtLayout->addWidget(pgpBtn); fmtLayout->addWidget(smimeBtn); mMainLay->addLayout(fmtLayout); if (mForcedProtocol != GpgME::UnknownProtocol) { pgpBtn->setChecked(mForcedProtocol == GpgME::OpenPGP); smimeBtn->setChecked(mForcedProtocol == GpgME::CMS); pgpBtn->setVisible(false); smimeBtn->setVisible(false); } else { pgpBtn->setChecked(presetProtocol == GpgME::OpenPGP || presetProtocol == GpgME::UnknownProtocol); smimeBtn->setChecked(presetProtocol == GpgME::CMS || presetProtocol == GpgME::UnknownProtocol); } QObject::connect(mFormatBtns, &QButtonGroup::idClicked, q, [this](int buttonId) { // ensure that at least one protocol button is checked if (mAllowMixed && !mFormatBtns->button(OpenPGPButtonId)->isChecked() && !mFormatBtns->button(SMIMEButtonId)->isChecked()) { mFormatBtns->button(buttonId == OpenPGPButtonId ? SMIMEButtonId : OpenPGPButtonId)->setChecked(true); } updateWidgets(); }); mMainLay->addWidget(mScrollArea); mComplianceLbl = new QLabel; mComplianceLbl->setVisible(false); #ifndef NDEBUG mComplianceLbl->setObjectName(QStringLiteral("compliance label")); #endif auto btnLayout = new QHBoxLayout; btnLayout->addWidget(mComplianceLbl); btnLayout->addWidget(btnBox); mMainLay->addLayout(btnLayout); q->setLayout(mMainLay); } ~Private() = default; GpgME::Protocol currentProtocol() { const bool openPGPButtonChecked = mFormatBtns->button(OpenPGPButtonId)->isChecked(); const bool smimeButtonChecked = mFormatBtns->button(SMIMEButtonId)->isChecked(); if (mAllowMixed) { if (openPGPButtonChecked && !smimeButtonChecked) { return GpgME::OpenPGP; } if (!openPGPButtonChecked && smimeButtonChecked) { return GpgME::CMS; } } else if (openPGPButtonChecked) { return GpgME::OpenPGP; } else if (smimeButtonChecked) { return GpgME::CMS; } return GpgME::UnknownProtocol; } auto findVisibleKeySelectionComboWithGenerateKey() { const auto it = std::find_if(std::begin(mAllCombos), std::end(mAllCombos), [](auto combo) { return combo->isVisible() && combo->currentData(Qt::UserRole).toInt() == GenerateKey; }); return it != std::end(mAllCombos) ? *it : nullptr; } void generateKey(KeySelectionCombo *combo) { const auto &addr = combo->property("address").toString(); auto job = new QGpgME::DefaultKeyGenerationJob(q); auto progress = new Kleo::ProgressDialog(job, i18n("Generating key for '%1'...", addr) + QStringLiteral("\n\n") + i18n("This can take several minutes."), q); progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowContextHelpButtonHint); progress->setWindowTitle(i18nc("@title:window", "Key generation")); progress->setModal(true); progress->setAutoClose(true); progress->setMinimumDuration(0); progress->setValue(0); mRunningJobs << job; connect(job, &QGpgME::DefaultKeyGenerationJob::result, q, [this, job, combo](const GpgME::KeyGenerationResult &result) { handleKeyGenResult(result, job, combo); }); job->start(addr, QString()); return; } void handleKeyGenResult(const GpgME::KeyGenerationResult &result, QGpgME::Job *job, KeySelectionCombo *combo) { mLastError = result.error(); if (!mLastError || mLastError.isCanceled()) { combo->setDefaultKey(QString::fromLatin1(result.fingerprint()), GpgME::OpenPGP); connect(combo, &KeySelectionCombo::keyListingFinished, q, [this, job]() { mRunningJobs.removeAll(job); }); combo->refreshKeys(); } else { mRunningJobs.removeAll(job); } } void checkAccepted() { if (mLastError || mLastError.isCanceled()) { KMessageBox::error(q, QString::fromLocal8Bit(mLastError.asString()), i18n("Operation Failed")); mRunningJobs.clear(); return; } if (!mRunningJobs.empty()) { return; } /* Save the keys */ mAcceptedResult.protocol = currentProtocol(); for (const auto combo : std::as_const(mEncCombos)) { const auto addr = combo->property("address").toString(); const auto key = combo->currentKey(); if (!combo->isVisible() || key.isNull()) { continue; } mAcceptedResult.encryptionKeys[addr].push_back(key); } for (const auto combo : std::as_const(mSigningCombos)) { const auto key = combo->currentKey(); if (!combo->isVisible() || key.isNull()) { continue; } mAcceptedResult.signingKeys.push_back(key); } q->accept(); } void accepted() { // We can assume everything was validly resolved, otherwise // the OK button would have been disabled. // Handle custom items now. if (auto combo = findVisibleKeySelectionComboWithGenerateKey()) { generateKey(combo); return; } checkAccepted(); } auto encryptionKeyFilter(GpgME::Protocol protocol) { switch (protocol) { case OpenPGP: return s_pgpEncryptFilter; case CMS: return s_smimeEncryptFilter; default: return s_encryptFilter; } } void updateWidgets() { const GpgME::Protocol protocol = currentProtocol(); const auto encryptionFilter = encryptionKeyFilter(protocol); for (auto combo : std::as_const(mSigningCombos)) { auto widget = qobject_cast(combo->parentWidget()); if (!widget) { qCDebug(LIBKLEO_LOG) << "Failed to find signature combo widget"; continue; } widget->setVisible(protocol == GpgME::UnknownProtocol || widget->fixedProtocol() == GpgME::UnknownProtocol || widget->fixedProtocol() == protocol); } for (auto combo : std::as_const(mEncCombos)) { auto widget = qobject_cast(combo->parentWidget()); if (!widget) { qCDebug(LIBKLEO_LOG) << "Failed to find combo widget"; continue; } widget->setVisible(protocol == GpgME::UnknownProtocol || widget->fixedProtocol() == GpgME::UnknownProtocol || widget->fixedProtocol() == protocol); if (widget->isVisible() && combo->property("address") != mSender) { combo->setKeyFilter(encryptionFilter); } } // hide the labels indicating the protocol of the sender's keys if only a single protocol is active const auto protocolLabels = q->findChildren(QStringLiteral("protocol label")); for (auto label : protocolLabels) { label->setVisible(protocol == GpgME::UnknownProtocol); } } auto createProtocolLabel(GpgME::Protocol protocol) { auto label = new QLabel(Formatting::displayName(protocol)); label->setObjectName(QStringLiteral("protocol label")); return label; } ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key, GpgME::Protocol protocol = GpgME::UnknownProtocol) { Q_ASSERT(!key.isNull() || protocol != UnknownProtocol); protocol = !key.isNull() ? key.protocol() : protocol; auto combo = new KeySelectionCombo(); auto comboWidget = new ComboWidget(combo); #ifndef NDEBUG combo->setObjectName(QStringLiteral("signing key")); #endif if (protocol == GpgME::OpenPGP) { combo->setKeyFilter(s_pgpSignFilter); } else if (protocol == GpgME::CMS) { combo->setKeyFilter(s_smimeSignFilter); } if (key.isNull() || key_has_addr(key, mSender)) { comboWidget->setIdFilter(mSender); } comboWidget->setFixedProtocol(protocol); if (!key.isNull()) { combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), protocol); } if (key.isNull() && protocol == OpenPGP) { combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey, mGenerateTooltip); } combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("emblem-unavailable")), i18n("Don't confirm identity and integrity"), IgnoreKey, i18nc("@info:tooltip for not selecting a key for signing.", "The E-Mail will not be cryptographically signed.")); mSigningCombos << combo; mAllCombos << combo; combo->setProperty("address", addr); connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this]() { updateOkButton(); }); connect(combo, qOverload(&QComboBox::currentIndexChanged), q, [this]() { updateOkButton(); }); return comboWidget; } void setSigningKeys(const std::vector &preferredKeys, const std::vector &alternativeKeys) { auto group = new QGroupBox(i18nc("Caption for signing key selection", "Confirm identity '%1' as:", mSender)); group->setAlignment(Qt::AlignLeft); auto sigLayout = new QVBoxLayout(group); const bool mayNeedOpenPGP = mForcedProtocol != CMS; const bool mayNeedCMS = mForcedProtocol != OpenPGP; if (mayNeedOpenPGP) { if (mAllowMixed) { sigLayout->addWidget(createProtocolLabel(OpenPGP)); } const Key preferredKey = findfirstKeyOfType(preferredKeys, OpenPGP); const Key alternativeKey = findfirstKeyOfType(alternativeKeys, OpenPGP); if (!preferredKey.isNull()) { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey; auto comboWidget = createSigningCombo(mSender, preferredKey); sigLayout->addWidget(comboWidget); } else if (!alternativeKey.isNull()) { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey; auto comboWidget = createSigningCombo(mSender, alternativeKey); sigLayout->addWidget(comboWidget); } else { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for OpenPGP key"; auto comboWidget = createSigningCombo(mSender, Key(), OpenPGP); sigLayout->addWidget(comboWidget); } } if (mayNeedCMS) { if (mAllowMixed) { sigLayout->addWidget(createProtocolLabel(CMS)); } const Key preferredKey = findfirstKeyOfType(preferredKeys, CMS); const Key alternativeKey = findfirstKeyOfType(alternativeKeys, CMS); if (!preferredKey.isNull()) { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey; auto comboWidget = createSigningCombo(mSender, preferredKey); sigLayout->addWidget(comboWidget); } else if (!alternativeKey.isNull()) { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey; auto comboWidget = createSigningCombo(mSender, alternativeKey); sigLayout->addWidget(comboWidget); } else { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for S/MIME key"; auto comboWidget = createSigningCombo(mSender, Key(), CMS); sigLayout->addWidget(comboWidget); } } mScrollLayout->addWidget(group); } ComboWidget *createEncryptionCombo(const QString &addr, const GpgME::Key &key, GpgME::Protocol fixedProtocol) { auto combo = new KeySelectionCombo(false); auto comboWidget = new ComboWidget(combo); #ifndef NDEBUG combo->setObjectName(QStringLiteral("encryption key")); #endif if (fixedProtocol == GpgME::OpenPGP) { combo->setKeyFilter(s_pgpEncryptFilter); } else if (fixedProtocol == GpgME::CMS) { combo->setKeyFilter(s_smimeEncryptFilter); } else { combo->setKeyFilter(s_encryptFilter); } if (key.isNull() || key_has_addr(key, addr)) { comboWidget->setIdFilter(addr); } comboWidget->setFixedProtocol(fixedProtocol); if (!key.isNull()) { combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), fixedProtocol); } if (addr == mSender && key.isNull() && fixedProtocol == OpenPGP) { combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey, mGenerateTooltip); } combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("emblem-unavailable")), i18n("No key. Recipient will be unable to decrypt."), IgnoreKey, i18nc("@info:tooltip for No Key selected for a specific recipient.", "Do not select a key for this recipient.

" "The recipient will receive the encrypted E-Mail, but it can only " "be decrypted with the other keys selected in this dialog.")); connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this]() { updateOkButton(); }); connect(combo, qOverload(&QComboBox::currentIndexChanged), q, [this]() { updateOkButton(); }); mEncCombos << combo; mAllCombos << combo; combo->setProperty("address", addr); return comboWidget; } void addEncryptionAddr(const QString &addr, GpgME::Protocol preferredKeysProtocol, const std::vector &preferredKeys, GpgME::Protocol alternativeKeysProtocol, const std::vector &alternativeKeys, QGridLayout *encGrid) { if (addr == mSender) { const bool mayNeedOpenPGP = mForcedProtocol != CMS; const bool mayNeedCMS = mForcedProtocol != OpenPGP; if (mayNeedOpenPGP) { if (mAllowMixed) { encGrid->addWidget(createProtocolLabel(OpenPGP), encGrid->rowCount(), 0); } for (const auto &key : preferredKeys) { if (key.protocol() == OpenPGP) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, OpenPGP); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } for (const auto &key : alternativeKeys) { if (key.protocol() == OpenPGP) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, OpenPGP); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } if (!anyKeyHasProtocol(preferredKeys, OpenPGP) && !anyKeyHasProtocol(alternativeKeys, OpenPGP)) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for OpenPGP key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), OpenPGP); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } if (mayNeedCMS) { if (mAllowMixed) { encGrid->addWidget(createProtocolLabel(CMS), encGrid->rowCount(), 0); } for (const auto &key : preferredKeys) { if (key.protocol() == CMS) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, CMS); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } for (const auto &key : alternativeKeys) { if (key.protocol() == CMS) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, CMS); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } if (!anyKeyHasProtocol(preferredKeys, CMS) && !anyKeyHasProtocol(alternativeKeys, CMS)) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for S/MIME key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), CMS); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } } else { encGrid->addWidget(new QLabel(addr), encGrid->rowCount(), 0); for (const auto &key : preferredKeys) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, preferredKeysProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } for (const auto &key : alternativeKeys) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, alternativeKeysProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } if (!mAllowMixed) { if (preferredKeys.empty()) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(preferredKeysProtocol) << "key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), preferredKeysProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } if (alternativeKeys.empty() && alternativeKeysProtocol != UnknownProtocol) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(alternativeKeysProtocol) << "key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), alternativeKeysProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } else { if (preferredKeys.empty() && alternativeKeys.empty()) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for any key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), UnknownProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } } } void setEncryptionKeys(GpgME::Protocol preferredKeysProtocol, const QMap> &preferredKeys, GpgME::Protocol alternativeKeysProtocol, const QMap> &alternativeKeys) { { auto group = new QGroupBox(i18nc("Encrypt to self (email address):", "Encrypt to self (%1):", mSender)); #ifndef NDEBUG group->setObjectName(QStringLiteral("encrypt-to-self box")); #endif group->setAlignment(Qt::AlignLeft); auto encGrid = new QGridLayout(group); addEncryptionAddr(mSender, preferredKeysProtocol, preferredKeys.value(mSender), alternativeKeysProtocol, alternativeKeys.value(mSender), encGrid); encGrid->setColumnStretch(1, -1); mScrollLayout->addWidget(group); } const bool hasOtherRecipients = std::any_of(preferredKeys.keyBegin(), preferredKeys.keyEnd(), [this](const auto &recipient) { return recipient != mSender; }); if (hasOtherRecipients) { auto group = new QGroupBox(i18n("Encrypt to others:")); #ifndef NDEBUG group->setObjectName(QStringLiteral("encrypt-to-others box")); #endif group->setAlignment(Qt::AlignLeft); auto encGrid = new QGridLayout{group}; for (auto it = std::begin(preferredKeys); it != std::end(preferredKeys); ++it) { const auto &address = it.key(); const auto &keys = it.value(); if (address != mSender) { addEncryptionAddr(address, preferredKeysProtocol, keys, alternativeKeysProtocol, alternativeKeys.value(address), encGrid); } } encGrid->setColumnStretch(1, -1); mScrollLayout->addWidget(group); } mScrollLayout->addStretch(-1); } void updateOkButton() { static QString origOkText = mOkButton->text(); const bool isGenerate = bool(findVisibleKeySelectionComboWithGenerateKey()); const bool allVisibleEncryptionKeysAreIgnored = std::all_of(std::begin(mEncCombos), std::end(mEncCombos), [](auto combo) { return !combo->isVisible() || combo->currentData(Qt::UserRole).toInt() == IgnoreKey; }); // If we don't encrypt the ok button is always enabled. But otherwise // we only enable it if we encrypt to at least one recipient. mOkButton->setEnabled(!mEncrypt || !allVisibleEncryptionKeysAreIgnored); mOkButton->setText(isGenerate ? i18n("Generate") : origOkText); if (!Kleo::gnupgUsesDeVsCompliance()) { return; } // Handle compliance bool de_vs = Kleo::gnupgIsDeVsCompliant(); if (de_vs) { const GpgME::Protocol protocol = currentProtocol(); for (const auto combo : std::as_const(mAllCombos)) { if (!combo->isVisible()) { continue; } const auto key = combo->currentKey(); if (key.isNull()) { continue; } if (protocol != GpgME::UnknownProtocol && key.protocol() != protocol) { continue; } if (!Formatting::isKeyDeVs(key) || keyValidity(key) < GpgME::UserID::Validity::Full) { de_vs = false; break; } } } if (de_vs) { mOkButton->setIcon(QIcon::fromTheme(QStringLiteral("security-high"))); mOkButton->setStyleSheet( QStringLiteral("background-color: ") + QStringLiteral( "#D5FAE2")); // KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name()); mComplianceLbl->setText(i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "%1 communication possible.", Formatting::deVsString())); } else { mOkButton->setIcon(QIcon::fromTheme(QStringLiteral("security-medium"))); mOkButton->setStyleSheet( QStringLiteral("background-color: ") + QStringLiteral( "#FAE9EB")); // KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name())); mComplianceLbl->setText(i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "%1 communication not possible.", Formatting::deVsString())); } mComplianceLbl->setVisible(true); } GpgME::Protocol mForcedProtocol; QList mSigningCombos; QList mEncCombos; QList mAllCombos; QScrollArea *mScrollArea; QVBoxLayout *mScrollLayout; QPushButton *mOkButton; QVBoxLayout *mMainLay; QButtonGroup *mFormatBtns; QString mSender; bool mSign; bool mEncrypt; bool mAllowMixed; NewKeyApprovalDialog *q; QList mRunningJobs; GpgME::Error mLastError; QLabel *mComplianceLbl; KeyResolver::Solution mAcceptedResult; QString mGenerateTooltip; }; NewKeyApprovalDialog::NewKeyApprovalDialog(bool encrypt, bool sign, const QString &sender, KeyResolver::Solution preferredSolution, KeyResolver::Solution alternativeSolution, bool allowMixed, GpgME::Protocol forcedProtocol, QWidget *parent, Qt::WindowFlags f) : QDialog(parent, f) , d{std::make_unique(this, encrypt, sign, forcedProtocol, preferredSolution.protocol, sender, allowMixed)} { if (sign) { d->setSigningKeys(std::move(preferredSolution.signingKeys), std::move(alternativeSolution.signingKeys)); } if (encrypt) { d->setEncryptionKeys(allowMixed ? UnknownProtocol : preferredSolution.protocol, std::move(preferredSolution.encryptionKeys), allowMixed ? UnknownProtocol : alternativeSolution.protocol, std::move(alternativeSolution.encryptionKeys)); } d->updateWidgets(); d->updateOkButton(); const auto size = sizeHint(); const auto desk = screen()->size(); resize(QSize(desk.width() / 3, qMin(size.height(), desk.height() / 2))); } Kleo::NewKeyApprovalDialog::~NewKeyApprovalDialog() = default; KeyResolver::Solution NewKeyApprovalDialog::result() { return d->mAcceptedResult; } #include "newkeyapprovaldialog.moc" diff --git a/src/ui/newkeyapprovaldialog.h b/src/ui/newkeyapprovaldialog.h index 1360dbee9..9eccb4165 100644 --- a/src/ui/newkeyapprovaldialog.h +++ b/src/ui/newkeyapprovaldialog.h @@ -1,79 +1,79 @@ /* -*- c++ -*- newkeyapprovaldialog.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include "kleo_export.h" + #include #include #include -#include "kleo_export.h" - namespace Kleo { /** @brief A dialog to show for encryption / signing key approval or selection. * * This class is intended to replace the old KeyApprovalDialog with a new * and simpler interface. * * Resolved recipients in this API means a recipient could be resolved * to a single useful key. An unresolved recipient is a recipient for * whom no key could be found. Import / Search will be offered for such * a recipient. Multiple keys for signing / recipient can come e.g. from * group configuration or Addressbook / Identity configuration. * * The Dialog uses the Level System for validity display and shows an * overall outgoing level. * */ class KLEO_EXPORT NewKeyApprovalDialog : public QDialog { Q_OBJECT public: /** @brief Create a new Key Approval Dialog. * * @param sender: The address of the sender, this may be used if signing is not * specified to identify a recipient for which "Generate Key" should * be offered. * @param preferredSolution: The preferred signing and/or encryption keys for the sender * and the recipients. * @param alternativeSolution: An alternative set of signing and/or encryption keys for the sender * and the recipients. Typically, S/MIME-only, if preferred solution is OpenPGP-only, * and vice versa. Ignored, if mixed protocol selection is allowed. * @param allowMixed: Whether or not the dialog should allow mixed S/MIME / OpenPGP key selection. * @param forcedProtocol: A specific forced protocol. * @param parent: The parent widget. * @param f: The Qt window flags. */ explicit NewKeyApprovalDialog(bool encrypt, bool sign, const QString &sender, KeyResolver::Solution preferredSolution, KeyResolver::Solution alternativeSolution, bool allowMixed, GpgME::Protocol forcedProtocol, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); ~NewKeyApprovalDialog() override; /** @brief The selected signing and/or encryption keys. Only valid after the dialog was accepted. */ KeyResolver::Solution result(); private: class Private; std::unique_ptr d; }; } // namespace kleo diff --git a/src/ui/progressbar.h b/src/ui/progressbar.h index 806582045..556fef745 100644 --- a/src/ui/progressbar.h +++ b/src/ui/progressbar.h @@ -1,53 +1,55 @@ /* progressbar.h 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 */ #pragma once #include "kleo_export.h" + #include + class QTimer; namespace Kleo { /** @short A QProgressBar with self-powered busy indicator */ class KLEO_EXPORT ProgressBar : public QProgressBar { Q_OBJECT public: explicit ProgressBar(QWidget *parent = nullptr); public Q_SLOTS: void slotProgress(const QString &message, int type, int current, int total); void slotProgress(const QString &message, int current, int total); /*! reimplementation to support self-powered busy indicator */ void setValue(int progress); /*! reimplementation to support self-powered busy indicator */ void setMaximum(int total); /*! reimplementation to support self-powered busy indicator */ void reset(); /*! reimplementation to preserve visibility */ void setRange(int cur, int tot) { QProgressBar::setRange(cur, tot); } private Q_SLOTS: void slotBusyTimerTick(); private: void fixup(bool); private: QTimer *mBusyTimer = nullptr; int mRealProgress; }; } diff --git a/src/ui/progressdialog.h b/src/ui/progressdialog.h index 1b62e21e2..5273a5bbf 100644 --- a/src/ui/progressdialog.h +++ b/src/ui/progressdialog.h @@ -1,51 +1,53 @@ /* progressdialog.h 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 */ #pragma once #include "kleo_export.h" + #include #ifndef QT_NO_PROGRESSDIALOG +#include + #include -#include namespace Kleo { /** @short A progress dialog for Kleo::Jobs */ class KLEO_EXPORT ProgressDialog : public QProgressDialog { Q_OBJECT public: ProgressDialog(QGpgME::Job *job, const QString &baseText, QWidget *widget = nullptr, Qt::WindowFlags f = {}); ~ProgressDialog() override; public Q_SLOTS: /*! reimplementation */ void setMinimumDuration(int ms); private Q_SLOTS: void slotProgress(const QString &what, int current, int total); void slotDone(); private: QString mBaseText; }; } #else #ifndef LIBKLEO_NO_PROGRESSDIALOG #define LIBKLEO_NO_PROGRESSDIALOG #endif #endif // QT_NO_PROGRESSDIALOG diff --git a/src/ui/readerportselection.cpp b/src/ui/readerportselection.cpp index aad0310f6..7daf3825a 100644 --- a/src/ui/readerportselection.cpp +++ b/src/ui/readerportselection.cpp @@ -1,144 +1,144 @@ /* ui/readerportselection.cpp This file is part of libkleopatra SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "readerportselection.h" -#include +#include #include #include #if __has_include() #include #endif #include #include #include #include using namespace Kleo; class ReaderPortSelection::Private { public: Private(ReaderPortSelection *q); void setValue(const QString &value); QString value() const; private: void onCurrentIndexChanged(int); void onEditTextChanged(const QString &); private: ReaderPortSelection *const q = nullptr; QComboBox *const mComboBox = nullptr; }; ReaderPortSelection::Private::Private(Kleo::ReaderPortSelection *qq) : q{qq} , mComboBox{new QComboBox{qq}} { auto layout = new QHBoxLayout{q}; layout->setContentsMargins({}); layout->addWidget(mComboBox); mComboBox->addItem(i18nc("@item:inlistbox", "Default reader"), {}); GpgME::Error err; const auto readers = SCDaemon::getReaders(err); if (err) { qCWarning(LIBKLEO_LOG) << "Getting available smart card readers failed:" << err; } else { std::for_each(std::begin(readers), std::end(readers), [this](const auto &reader) { const auto readerId = QString::fromStdString(reader); mComboBox->addItem(readerId, readerId); }); } mComboBox->addItem(QString{}, {}); mComboBox->setToolTip(xi18nc("@info:tooltip", "Select the smart card reader that GnuPG shall use." "The first item will make GnuPG use the first reader that is found." "The last item allows you to enter a custom reader ID or reader port number." "All other items represent readers that were found by GnuPG." "")); connect(mComboBox, qOverload(&QComboBox::currentIndexChanged), q, [this](int index) { onCurrentIndexChanged(index); Q_EMIT q->valueChanged(q->value()); }); connect(mComboBox, &QComboBox::editTextChanged, q, [this](const QString &text) { onEditTextChanged(text); Q_EMIT q->valueChanged(q->value()); }); } void ReaderPortSelection::Private::setValue(const QString &value) { if (value.isEmpty()) { mComboBox->setCurrentIndex(0); return; } const int indexOfValue = mComboBox->findData(value); if (indexOfValue != -1) { mComboBox->setCurrentIndex(indexOfValue); } else { mComboBox->setCurrentIndex(mComboBox->count() - 1); mComboBox->setEditText(value); } } QString ReaderPortSelection::Private::value() const { return mComboBox->currentData().toString(); } void ReaderPortSelection::Private::onCurrentIndexChanged(int index) { // the last item serves as input for a custom entry mComboBox->setEditable(index == mComboBox->count() - 1); if (mComboBox->lineEdit()) { mComboBox->lineEdit()->setPlaceholderText(i18nc("@item:inlistbox", "Custom reader ID or port number")); } } void ReaderPortSelection::Private::onEditTextChanged(const QString &text) { const int lastIndex = mComboBox->count() - 1; // do not overwrite the text of the custom item with the text of another item if (mComboBox->currentIndex() == lastIndex) { mComboBox->setItemText(lastIndex, text); mComboBox->setItemData(lastIndex, text); } } ReaderPortSelection::ReaderPortSelection(QWidget *parent) : QWidget{parent} , d{new Private{this}} { } ReaderPortSelection::~ReaderPortSelection() = default; void ReaderPortSelection::setValue(const QString &value) { d->setValue(value); } QString ReaderPortSelection::value() const { return d->value(); } diff --git a/src/ui/readerportselection.h b/src/ui/readerportselection.h index 75b978fd8..2d235186c 100644 --- a/src/ui/readerportselection.h +++ b/src/ui/readerportselection.h @@ -1,38 +1,38 @@ /* ui/readerportselection.h This file is part of libkleopatra SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include - #include "kleo_export.h" +#include + namespace Kleo { class KLEO_EXPORT ReaderPortSelection : public QWidget { Q_OBJECT public: ReaderPortSelection(QWidget *parent = nullptr); ~ReaderPortSelection() override; void setValue(const QString &value); QString value() const; Q_SIGNALS: void valueChanged(const QString &newValue); private: class Private; const std::unique_ptr d; }; } diff --git a/src/utils/assuan.cpp b/src/utils/assuan.cpp index 876f8abe0..5d1676302 100644 --- a/src/utils/assuan.cpp +++ b/src/utils/assuan.cpp @@ -1,150 +1,150 @@ /* utils/assuan.cpp This file is part of libkleopatra SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "assuan.h" #include -#include - #if __has_include() #include #endif +#include + #include #include #include using namespace GpgME; using namespace Kleo; using namespace Kleo::Assuan; using namespace std::chrono_literals; static const auto initialRetryDelay = 125ms; static const auto maxRetryDelay = 1000ms; static const auto maxConnectionAttempts = 10; namespace { static QDebug operator<<(QDebug s, const std::string &string) { return s << QString::fromStdString(string); } static QDebug operator<<(QDebug s, const std::vector> &v) { using pair = std::pair; s << '('; for (const pair &p : v) { s << "status(" << QString::fromStdString(p.first) << ") =" << QString::fromStdString(p.second) << '\n'; } return s << ')'; } } bool Kleo::Assuan::agentIsRunning() { Error err; const std::unique_ptr ctx = Context::createForEngine(AssuanEngine, &err); if (err) { qCWarning(LIBKLEO_LOG) << __func__ << ": Creating context for Assuan engine failed:" << err; return false; } static const char *command = "GETINFO version"; err = ctx->assuanTransact(command); if (!err) { // all good } else if (err.code() == GPG_ERR_ASS_CONNECT_FAILED) { qCDebug(LIBKLEO_LOG) << __func__ << ": Connecting to the agent failed."; } else { qCWarning(LIBKLEO_LOG) << __func__ << ": Starting Assuan transaction for" << command << "failed:" << err; } return !err; } std::unique_ptr Kleo::Assuan::sendCommand(std::shared_ptr &context, const std::string &command, std::unique_ptr transaction, GpgME::Error &err) { qCDebug(LIBKLEO_LOG) << __func__ << command; int connectionAttempts = 1; err = context->assuanTransact(command.c_str(), std::move(transaction)); auto retryDelay = initialRetryDelay; while (err.code() == GPG_ERR_ASS_CONNECT_FAILED && connectionAttempts < maxConnectionAttempts) { // Esp. on Windows the agent processes may take their time so we try // in increasing waits for them to start up qCDebug(LIBKLEO_LOG) << "Connecting to the agent failed. Retrying in" << retryDelay.count() << "ms"; QThread::msleep(retryDelay.count()); retryDelay = std::min(retryDelay * 2, maxRetryDelay); connectionAttempts++; err = context->assuanTransact(command.c_str(), context->takeLastAssuanTransaction()); } if (err.code()) { qCDebug(LIBKLEO_LOG) << __func__ << command << "failed:" << err; if (err.code() >= GPG_ERR_ASS_GENERAL && err.code() <= GPG_ERR_ASS_UNKNOWN_INQUIRE) { qCDebug(LIBKLEO_LOG) << "Assuan problem, killing context"; context.reset(); } return {}; } return context->takeLastAssuanTransaction(); } std::unique_ptr Kleo::Assuan::sendCommand(std::shared_ptr &context, const std::string &command, Error &err) { std::unique_ptr t = sendCommand(context, command, std::make_unique(), err); return std::unique_ptr(dynamic_cast(t.release())); } std::string Kleo::Assuan::sendDataCommand(std::shared_ptr context, const std::string &command, Error &err) { std::string data; const std::unique_ptr t = sendCommand(context, command, err); if (t.get()) { data = t->data(); qCDebug(LIBKLEO_LOG) << __func__ << command << ": got" << QString::fromStdString(data); } else { qCDebug(LIBKLEO_LOG) << __func__ << command << ": t == NULL"; } return data; } std::vector> Kleo::Assuan::sendStatusLinesCommand(std::shared_ptr context, const std::string &command, Error &err) { std::vector> statusLines; const std::unique_ptr t = sendCommand(context, command, err); if (t.get()) { statusLines = t->statusLines(); qCDebug(LIBKLEO_LOG) << __func__ << command << ": got" << statusLines; } else { qCDebug(LIBKLEO_LOG) << __func__ << command << ": t == NULL"; } return statusLines; } std::string Kleo::Assuan::sendStatusCommand(const std::shared_ptr &context, const std::string &command, Error &err) { const auto lines = sendStatusLinesCommand(context, command, err); // The status is only the last attribute // e.g. for SCD SERIALNO it would only be "SERIALNO" and for SCD GETATTR FOO // it would only be FOO const auto lastSpace = command.rfind(' '); const auto needle = lastSpace == std::string::npos ? command : command.substr(lastSpace + 1); for (const auto &pair : lines) { if (pair.first == needle) { return pair.second; } } return {}; } diff --git a/src/utils/assuan.h b/src/utils/assuan.h index 5e37d68c4..eee35e04d 100644 --- a/src/utils/assuan.h +++ b/src/utils/assuan.h @@ -1,72 +1,72 @@ /* utils/assuan.h This file is part of libkleopatra SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include "kleo_export.h" + #include #include -#include "kleo_export.h" - namespace GpgME { class AssuanTransaction; class Context; class DefaultAssuanTransaction; class Error; } namespace Kleo { /** The Assuan namespace collects functions for communicating with the GnuPG * agent via the Assuan protocol. */ namespace Assuan { /** Checks if the GnuPG agent is running and accepts connections. */ KLEO_EXPORT bool agentIsRunning(); /** Sends the Assuan @p command using the @p transaction and the @p assuanContext * to the GnuPG agent and waits for the result. The returned transaction can be used * to retrieve the result. * If an error occurred, then @p err provides details. */ KLEO_EXPORT std::unique_ptr sendCommand(std::shared_ptr &assuanContext, const std::string &command, std::unique_ptr transaction, GpgME::Error &err); /** Sends the Assuan @p command using a default Assuan transaction and the @p assuanContext * to the GnuPG agent and waits for the result. The returned transaction can be used * to retrieve the result. * If an error occurred, then @p err provides details. */ KLEO_EXPORT std::unique_ptr sendCommand(std::shared_ptr &assuanContext, const std::string &command, GpgME::Error &err); /** Sends the Assuan @p command using a default Assuan transaction and the @p assuanContext * to the GnuPG agent and waits for the result. Returns the data that was sent by * GnuPG agent in response to the @p command. * If an error occurred, then @p err provides details. */ KLEO_EXPORT std::string sendDataCommand(std::shared_ptr assuanContext, const std::string &command, GpgME::Error &err); /** Sends the Assuan @p command using a default Assuan transaction and the @p assuanContext * to the GnuPG agent and waits for the result. Returns the status lines that were sent by * GnuPG agent in response to the @p command. * If an error occurred, then @p err provides details. */ KLEO_EXPORT std::vector> sendStatusLinesCommand(std::shared_ptr assuanContext, const std::string &command, GpgME::Error &err); /** Sends the Assuan @p command using a default Assuan transaction and the @p assuanContext * to the GnuPG agent and waits for the result. Returns the status that was sent by * GnuPG agent in response to the @p command. * If an error occurred, then @p err provides details. */ KLEO_EXPORT std::string sendStatusCommand(const std::shared_ptr &assuanContext, const std::string &command, GpgME::Error &err); } } diff --git a/src/utils/classify.cpp b/src/utils/classify.cpp index c227f6f72..652cc59d4 100644 --- a/src/utils/classify.cpp +++ b/src/utils/classify.cpp @@ -1,425 +1,426 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/classify.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "classify.h" #include "algorithm.h" -#include +#include #include +#include + #include #include #include #include #include #include #include #include -#include #include #include using namespace Kleo::Class; namespace { const unsigned int ExamineContentHint = 0x8000; static const struct _classification { char extension[4]; unsigned int classification; } classifications[] = { // ordered by extension {"arl", Kleo::Class::CMS | Binary | CertificateRevocationList}, {"asc", Kleo::Class::OpenPGP | Ascii | OpaqueSignature | DetachedSignature | CipherText | AnyCertStoreType | ExamineContentHint}, {"cer", Kleo::Class::CMS | Binary | Certificate}, {"crl", Kleo::Class::CMS | Binary | CertificateRevocationList}, {"crt", Kleo::Class::CMS | Binary | Certificate}, {"der", Kleo::Class::CMS | Binary | Certificate | CertificateRevocationList}, {"gpg", Kleo::Class::OpenPGP | Binary | OpaqueSignature | CipherText | AnyCertStoreType | ExamineContentHint}, {"p10", Kleo::Class::CMS | Ascii | CertificateRequest}, {"p12", Kleo::Class::CMS | Binary | ExportedPSM}, {"p7c", Kleo::Class::CMS | Binary | Certificate}, {"p7m", Kleo::Class::CMS | AnyFormat | CipherText}, {"p7s", Kleo::Class::CMS | AnyFormat | AnySignature}, {"pem", Kleo::Class::CMS | Ascii | AnyType | ExamineContentHint}, {"pfx", Kleo::Class::CMS | Binary | Certificate}, {"pgp", Kleo::Class::OpenPGP | Binary | OpaqueSignature | CipherText | AnyCertStoreType | ExamineContentHint}, {"sig", Kleo::Class::OpenPGP | AnyFormat | DetachedSignature}, }; static const QMap gpgmeTypeMap{ // clang-format off {GpgME::Data::PGPSigned, Kleo::Class::OpenPGP | OpaqueSignature }, /* PGPOther might be just an unencrypted unsigned pgp message. Decrypt * would yield the plaintext anyway so for us this is CipherText. */ {GpgME::Data::PGPOther, Kleo::Class::OpenPGP | CipherText }, {GpgME::Data::PGPKey, Kleo::Class::OpenPGP | Certificate }, {GpgME::Data::CMSSigned, Kleo::Class::CMS | AnySignature }, {GpgME::Data::CMSEncrypted, Kleo::Class::CMS | CipherText }, /* See PGPOther */ {GpgME::Data::CMSOther, Kleo::Class::CMS | CipherText }, {GpgME::Data::X509Cert, Kleo::Class::CMS | Certificate }, {GpgME::Data::PKCS12, Kleo::Class::CMS | Binary | ExportedPSM }, {GpgME::Data::PGPEncrypted, Kleo::Class::OpenPGP | CipherText }, {GpgME::Data::PGPSignature, Kleo::Class::OpenPGP | DetachedSignature}, // clang-format on }; static const unsigned int defaultClassification = NoClass; template class Op> struct ByExtension { using result_type = bool; template bool operator()(const T &lhs, const T &rhs) const { return Op()(qstricmp(lhs.extension, rhs.extension), 0); } template bool operator()(const T &lhs, const char *rhs) const { return Op()(qstricmp(lhs.extension, rhs), 0); } template bool operator()(const char *lhs, const T &rhs) const { return Op()(qstricmp(lhs, rhs.extension), 0); } bool operator()(const char *lhs, const char *rhs) const { return Op()(qstricmp(lhs, rhs), 0); } }; static const struct _content_classification { char content[28]; unsigned int classification; } content_classifications[] = { // clang-format off {"CERTIFICATE", Certificate }, {"ENCRYPTED MESSAGE", CipherText }, {"MESSAGE", OpaqueSignature | CipherText }, {"PKCS12", ExportedPSM }, {"PRIVATE KEY BLOCK", ExportedPSM }, {"PUBLIC KEY BLOCK", Certificate }, {"SIGNATURE", DetachedSignature }, {"SIGNED MESSAGE", ClearsignedMessage | DetachedSignature}, // clang-format on }; template class Op> struct ByContent { using result_type = bool; const unsigned int N; explicit ByContent(unsigned int n) : N(n) { } template bool operator()(const T &lhs, const T &rhs) const { return Op()(qstrncmp(lhs.content, rhs.content, N), 0); } template bool operator()(const T &lhs, const char *rhs) const { return Op()(qstrncmp(lhs.content, rhs, N), 0); } template bool operator()(const char *lhs, const T &rhs) const { return Op()(qstrncmp(lhs, rhs.content, N), 0); } bool operator()(const char *lhs, const char *rhs) const { return Op()(qstrncmp(lhs, rhs, N), 0); } }; } unsigned int Kleo::classify(const QStringList &fileNames) { if (fileNames.empty()) { return 0; } unsigned int result = classify(fileNames.front()); for (const QString &fileName : fileNames) { result &= classify(fileName); } return result; } static unsigned int classifyExtension(const QFileInfo &fi) { const _classification *const it = Kleo::binary_find(std::begin(classifications), std::end(classifications), fi.suffix().toLatin1().constData(), ByExtension()); if (it != std::end(classifications)) { if (!(it->classification & ExamineContentHint)) { return it->classification; } } return it == std::end(classifications) ? defaultClassification : it->classification; } unsigned int Kleo::classify(const QString &filename) { Q_ASSERT(std::is_sorted(std::begin(classifications), std::end(classifications), ByExtension())); const QFileInfo fi(filename); if (!fi.exists()) { return 0; } QFile file(filename); /* The least reliable but always available classification */ const unsigned int extClass = classifyExtension(fi); if (!GpgME::hasFeature(0, GpgME::BinaryAndFineGrainedIdentify) && !(extClass & ExamineContentHint)) { /* GpgME's identify and our internal Classify were so incomplete * before BinaryAndFineGrainedIdentify that we are better of * to just use the file extension if ExamineContentHint is not set. */ qCDebug(LIBKLEO_LOG) << "Classified based only on extension."; return extClass; } if (!file.open(QIODevice::ReadOnly)) { qCDebug(LIBKLEO_LOG) << "Failed to open file: " << filename << " for classification."; return extClass; } /* More reliable */ const unsigned int contentClass = classifyContent(file.read(4096)); if (contentClass != defaultClassification) { qCDebug(LIBKLEO_LOG) << "Classified based on content as:" << contentClass; return contentClass; } /* Probably some X509 Stuff that GpgME in it's wisdom does not handle. Again * file extension is probably more reliable as the last resort. */ qCDebug(LIBKLEO_LOG) << "No classification based on content."; return extClass; } static unsigned int classifyContentInteral(const QByteArray &data) { Q_ASSERT(std::is_sorted(std::begin(content_classifications), std::end(content_classifications), ByContent(100))); static const char beginString[] = "-----BEGIN "; static const QByteArrayMatcher beginMatcher(beginString); int pos = beginMatcher.indexIn(data); if (pos < 0) { return defaultClassification; } pos += sizeof beginString - 1; const bool pgp = qstrncmp(data.data() + pos, "PGP ", 4) == 0; if (pgp) { pos += 4; } const int epos = data.indexOf("-----\n", pos); if (epos < 0) { return defaultClassification; } const _content_classification *const cit = Kleo::binary_find(std::begin(content_classifications), std::end(content_classifications), data.data() + pos, ByContent(epos - pos)); if (cit != std::end(content_classifications)) { return cit->classification | (pgp ? Kleo::Class::OpenPGP : Kleo::Class::CMS); } return defaultClassification; } unsigned int Kleo::classifyContent(const QByteArray &data) { /* As of Version 1.6.0 GpgME does not distinguish between detached * signatures and signatures. So we prefer kleo's classification and * only use gpgme as fallback. * With newer versions we have a better identify that really inspects * the PGP Packages. Which is by far the most reliable classification. * So this is already used for the default classification. File extensions * and our classifyinternal is only used as a fallback. */ if (!GpgME::hasFeature(0, GpgME::BinaryAndFineGrainedIdentify)) { unsigned int ourClassification = classifyContentInteral(data); if (ourClassification != defaultClassification) { return ourClassification; } } QGpgME::QByteArrayDataProvider dp(data); GpgME::Data gpgmeData(&dp); GpgME::Data::Type type = gpgmeData.type(); return gpgmeTypeMap.value(type, defaultClassification); } QString Kleo::printableClassification(unsigned int classification) { QStringList parts; if (classification & Kleo::Class::CMS) { parts.push_back(QStringLiteral("CMS")); } if (classification & Kleo::Class::OpenPGP) { parts.push_back(QStringLiteral("OpenPGP")); } if (classification & Kleo::Class::Binary) { parts.push_back(QStringLiteral("Binary")); } if (classification & Kleo::Class::Ascii) { parts.push_back(QStringLiteral("Ascii")); } if (classification & Kleo::Class::DetachedSignature) { parts.push_back(QStringLiteral("DetachedSignature")); } if (classification & Kleo::Class::OpaqueSignature) { parts.push_back(QStringLiteral("OpaqueSignature")); } if (classification & Kleo::Class::ClearsignedMessage) { parts.push_back(QStringLiteral("ClearsignedMessage")); } if (classification & Kleo::Class::CipherText) { parts.push_back(QStringLiteral("CipherText")); } if (classification & Kleo::Class::Certificate) { parts.push_back(QStringLiteral("Certificate")); } if (classification & Kleo::Class::ExportedPSM) { parts.push_back(QStringLiteral("ExportedPSM")); } if (classification & Kleo::Class::CertificateRequest) { parts.push_back(QStringLiteral("CertificateRequest")); } return parts.join(QLatin1String(", ")); } static QString chopped(QString s, unsigned int n) { s.chop(n); return s; } /*! \return the data file that corresponds to the signature file \a signatureFileName, or QString(), if no such file can be found. */ QString Kleo::findSignedData(const QString &signatureFileName) { if (!mayBeDetachedSignature(signatureFileName)) { return QString(); } const QString baseName = chopped(signatureFileName, 4); return QFile::exists(baseName) ? baseName : QString(); } /*! \return all (existing) candidate signature files for \a signedDataFileName Note that there can very well be more than one such file, e.g. if the same data file was signed by both CMS and OpenPGP certificates. */ QStringList Kleo::findSignatures(const QString &signedDataFileName) { QStringList result; for (unsigned int i = 0, end = sizeof(classifications) / sizeof(_classification); i < end; ++i) { if (classifications[i].classification & DetachedSignature) { const QString candidate = signedDataFileName + QLatin1Char('.') + QLatin1String(classifications[i].extension); if (QFile::exists(candidate)) { result.push_back(candidate); } } } return result; } /*! \return the (likely) output filename for \a inputFileName, or "inputFileName.out" if none can be determined. */ QString Kleo::outputFileName(const QString &inputFileName) { const QFileInfo fi(inputFileName); if (!std::binary_search(std::begin(classifications), std::end(classifications), fi.suffix().toLatin1().constData(), ByExtension())) { return inputFileName + QLatin1String(".out"); } else { return chopped(inputFileName, 4); } } /*! \return the commonly used extension for files of type \a classification, or NULL if none such exists. */ const char *Kleo::outputFileExtension(unsigned int classification, bool usePGPFileExt) { if (usePGPFileExt && (classification & Class::OpenPGP) && (classification & Class::Binary)) { return "pgp"; } for (unsigned int i = 0; i < sizeof classifications / sizeof *classifications; ++i) { if ((classifications[i].classification & classification) == classification) { return classifications[i].extension; } } return nullptr; } bool Kleo::isFingerprint(const QString &fpr) { static QRegularExpression fprRegex(QStringLiteral("[0-9a-fA-F]{40}")); return fprRegex.match(fpr).hasMatch(); } bool Kleo::isChecksumFile(const QString &file) { static bool initialized; static QList patterns; const QFileInfo fi(file); if (!fi.exists()) { return false; } if (!initialized) { const auto getChecksumDefinitions = ChecksumDefinition::getChecksumDefinitions(); for (const std::shared_ptr &cd : getChecksumDefinitions) { if (cd) { const auto patternsList = cd->patterns(); for (const QString &pattern : patternsList) { #ifdef Q_OS_WIN patterns << QRegExp(pattern, Qt::CaseInsensitive); #else patterns << QRegExp(pattern, Qt::CaseSensitive); #endif } } } initialized = true; } const QString fileName = fi.fileName(); for (const QRegExp &pattern : std::as_const(patterns)) { if (pattern.exactMatch(fileName)) { return true; } } return false; } diff --git a/src/utils/classify.h b/src/utils/classify.h index 7d51f4d50..0ea966420 100644 --- a/src/utils/classify.h +++ b/src/utils/classify.h @@ -1,273 +1,274 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/classify.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" + #include #include class QByteArray; class QString; namespace Kleo { namespace Class { enum { // clang-format off NoClass = 0, // protocol: CMS = 0x01, OpenPGP = 0x02, AnyProtocol = OpenPGP | CMS, ProtocolMask = AnyProtocol, // format: Binary = 0x04, Ascii = 0x08, AnyFormat = Binary | Ascii, FormatMask = AnyFormat, // type: DetachedSignature = 0x010, OpaqueSignature = 0x020, ClearsignedMessage = 0x040, AnySignature = DetachedSignature | OpaqueSignature | ClearsignedMessage, CipherText = 0x080, AnyMessageType = AnySignature | CipherText, Importable = 0x100, Certificate = 0x200 | Importable, ExportedPSM = 0x400 | Importable, AnyCertStoreType = Certificate | ExportedPSM, CertificateRequest = 0x800, CertificateRevocationList = 0x1000, AnyType = AnyMessageType | AnyCertStoreType | CertificateRequest | CertificateRevocationList, TypeMask = AnyType // clang-format on }; } KLEO_EXPORT unsigned int classify(const QString &filename); KLEO_EXPORT unsigned int classify(const QStringList &fileNames); KLEO_EXPORT unsigned int classifyContent(const QByteArray &data); KLEO_EXPORT QString findSignedData(const QString &signatureFileName); KLEO_EXPORT QStringList findSignatures(const QString &signedDataFileName); KLEO_EXPORT QString outputFileName(const QString &input); /** Check if a string looks like a fingerprint (SHA1 sum) */ KLEO_EXPORT bool isFingerprint(const QString &fpr); /** Check if a filename matches a ChecksumDefinition pattern */ KLEO_EXPORT bool isChecksumFile(const QString &file); KLEO_EXPORT const char *outputFileExtension(unsigned int classification, bool usePGPFileExt); KLEO_EXPORT QString printableClassification(unsigned int classification); inline bool isCMS(const QString &filename) { return (classify(filename) & Class::ProtocolMask) == Class::CMS; } inline bool isCMS(const unsigned int classification) { return (classification & Class::ProtocolMask) == Class::CMS; } inline bool mayBeCMS(const QString &filename) { return classify(filename) & Class::CMS; } inline bool mayBeCMS(const unsigned int classification) { return classification & Class::CMS; } inline bool isOpenPGP(const QString &filename) { return (classify(filename) & Class::ProtocolMask) == Class::OpenPGP; } inline bool isOpenPGP(const unsigned int classification) { return (classification & Class::ProtocolMask) == Class::OpenPGP; } inline bool mayBeOpenPGP(const QString &filename) { return classify(filename) & Class::OpenPGP; } inline bool mayBeOpenPGP(const unsigned int classification) { return classification & Class::OpenPGP; } inline bool isBinary(const QString &filename) { return (classify(filename) & Class::FormatMask) == Class::Binary; } inline bool isBinary(const unsigned int classification) { return (classification & Class::FormatMask) == Class::Binary; } inline bool mayBeBinary(const QString &filename) { return classify(filename) & Class::Binary; } inline bool mayBeBinary(const unsigned int classification) { return classification & Class::Binary; } inline bool isAscii(const QString &filename) { return (classify(filename) & Class::FormatMask) == Class::Ascii; } inline bool isAscii(const unsigned int classification) { return (classification & Class::FormatMask) == Class::Ascii; } inline bool mayBeAscii(const QString &filename) { return classify(filename) & Class::Ascii; } inline bool mayBeAscii(const unsigned int classification) { return classification & Class::Ascii; } inline bool isDetachedSignature(const QString &filename) { return (classify(filename) & Class::TypeMask) == Class::DetachedSignature; } inline bool isDetachedSignature(const unsigned int classification) { return (classification & Class::TypeMask) == Class::DetachedSignature; } inline bool mayBeDetachedSignature(const QString &filename) { return classify(filename) & Class::DetachedSignature; } inline bool mayBeDetachedSignature(const unsigned int classification) { return classification & Class::DetachedSignature; } inline bool isOpaqueSignature(const QString &filename) { return (classify(filename) & Class::TypeMask) == Class::OpaqueSignature; } inline bool isOpaqueSignature(const unsigned int classification) { return (classification & Class::TypeMask) == Class::OpaqueSignature; } inline bool mayBeOpaqueSignature(const QString &filename) { return classify(filename) & Class::OpaqueSignature; } inline bool mayBeOpaqueSignature(const unsigned int classification) { return classification & Class::OpaqueSignature; } inline bool isCipherText(const QString &filename) { return (classify(filename) & Class::TypeMask) == Class::CipherText; } inline bool isCipherText(const unsigned int classification) { return (classification & Class::TypeMask) == Class::CipherText; } inline bool mayBeCipherText(const QString &filename) { return classify(filename) & Class::CipherText; } inline bool mayBeCipherText(const unsigned int classification) { return classification & Class::CipherText; } inline bool isAnyMessageType(const QString &filename) { return (classify(filename) & Class::TypeMask) == Class::AnyMessageType; } inline bool isAnyMessageType(const unsigned int classification) { return (classification & Class::TypeMask) == Class::AnyMessageType; } inline bool mayBeAnyMessageType(const QString &filename) { return classify(filename) & Class::AnyMessageType; } inline bool mayBeAnyMessageType(const unsigned int classification) { return classification & Class::AnyMessageType; } inline bool isCertificateRevocationList(const QString &filename) { return (classify(filename) & Class::TypeMask) == Class::CertificateRevocationList; } inline bool isCertificateRevocationList(const unsigned int classification) { return (classification & Class::TypeMask) == Class::CertificateRevocationList; } inline bool mayBeCertificateRevocationList(const QString &filename) { return classify(filename) & Class::CertificateRevocationList; } inline bool mayBeCertificateRevocationList(const unsigned int classification) { return classification & Class::CertificateRevocationList; } inline bool isAnyCertStoreType(const QString &filename) { return (classify(filename) & Class::TypeMask) == Class::AnyCertStoreType; } inline bool isAnyCertStoreType(const unsigned int classification) { return (classification & Class::TypeMask) == Class::AnyCertStoreType; } inline bool mayBeAnyCertStoreType(const QString &filename) { return classify(filename) & Class::AnyCertStoreType; } inline bool mayBeAnyCertStoreType(const unsigned int classification) { return classification & Class::AnyCertStoreType; } inline GpgME::Protocol findProtocol(const unsigned int classification) { if (isOpenPGP(classification)) { return GpgME::OpenPGP; } else if (isCMS(classification)) { return GpgME::CMS; } else { return GpgME::UnknownProtocol; } } inline GpgME::Protocol findProtocol(const QString &filename) { return findProtocol(classify(filename)); } } diff --git a/src/utils/filesystemwatcher.cpp b/src/utils/filesystemwatcher.cpp index 5e6e4ada5..e603791c0 100644 --- a/src/utils/filesystemwatcher.cpp +++ b/src/utils/filesystemwatcher.cpp @@ -1,329 +1,329 @@ /* -*- mode: c++; c-basic-offset:4 -*- filesystemwatcher.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "filesystemwatcher.h" -#include +#include #include #include #include #include #include #include #include using namespace Kleo; class FileSystemWatcher::Private { FileSystemWatcher *const q; public: explicit Private(FileSystemWatcher *qq, const QStringList &paths = QStringList()); ~Private() { delete m_watcher; } void onFileChanged(const QString &path); void onDirectoryChanged(const QString &path); void handleTimer(); void onTimeout(); void connectWatcher(); QFileSystemWatcher *m_watcher = nullptr; QTimer m_timer; std::set m_seenPaths; std::set m_cachedDirectories; std::set m_cachedFiles; QStringList m_paths, m_blacklist, m_whitelist; }; FileSystemWatcher::Private::Private(FileSystemWatcher *qq, const QStringList &paths) : q(qq) , m_watcher(nullptr) , m_paths(paths) { m_timer.setSingleShot(true); connect(&m_timer, &QTimer::timeout, q, [this]() { onTimeout(); }); } static bool is_matching(const QString &file, const QStringList &list) { for (const QString &entry : list) { if (QRegExp(entry, Qt::CaseInsensitive, QRegExp::Wildcard).exactMatch(file)) { return true; } } return false; } static bool is_blacklisted(const QString &file, const QStringList &blacklist) { return is_matching(file, blacklist); } static bool is_whitelisted(const QString &file, const QStringList &whitelist) { if (whitelist.empty()) { return true; // special case } return is_matching(file, whitelist); } void FileSystemWatcher::Private::onFileChanged(const QString &path) { const QFileInfo fi(path); if (is_blacklisted(fi.fileName(), m_blacklist)) { return; } if (!is_whitelisted(fi.fileName(), m_whitelist)) { return; } qCDebug(LIBKLEO_LOG) << path; if (fi.exists()) { m_seenPaths.insert(path); } else { m_seenPaths.erase(path); } m_cachedFiles.insert(path); handleTimer(); } static QStringList list_dir_absolute(const QString &path, const QStringList &blacklist, const QStringList &whitelist) { QDir dir(path); QStringList entries = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot); QStringList::iterator end = std::remove_if(entries.begin(), entries.end(), [&blacklist](const QString &entry) { return is_blacklisted(entry, blacklist); }); if (!whitelist.empty()) { end = std::remove_if(entries.begin(), end, [&whitelist](const QString &entry) { return !is_whitelisted(entry, whitelist); }); } entries.erase(end, entries.end()); std::sort(entries.begin(), entries.end()); std::transform(entries.begin(), entries.end(), entries.begin(), [&dir](const QString &entry) { return dir.absoluteFilePath(entry); }); return entries; } static QStringList find_new_files(const QStringList ¤t, const std::set &seen) { QStringList result; std::set_difference(current.begin(), current.end(), seen.begin(), seen.end(), std::back_inserter(result)); return result; } void FileSystemWatcher::Private::onDirectoryChanged(const QString &path) { const QStringList newFiles = find_new_files(list_dir_absolute(path, m_blacklist, m_whitelist), m_seenPaths); if (newFiles.empty()) { return; } qCDebug(LIBKLEO_LOG) << "newFiles" << newFiles; m_cachedFiles.insert(newFiles.begin(), newFiles.end()); q->addPaths(newFiles); m_cachedDirectories.insert(path); handleTimer(); } void FileSystemWatcher::Private::onTimeout() { std::set dirs; std::set files; dirs.swap(m_cachedDirectories); files.swap(m_cachedFiles); if (dirs.empty() && files.empty()) { return; } Q_EMIT q->triggered(); for (const QString &i : std::as_const(dirs)) { Q_EMIT q->directoryChanged(i); } for (const QString &i : std::as_const(files)) { Q_EMIT q->fileChanged(i); } } void FileSystemWatcher::Private::handleTimer() { if (m_timer.interval() == 0) { onTimeout(); return; } m_timer.start(); } void FileSystemWatcher::Private::connectWatcher() { if (!m_watcher) { return; } connect(m_watcher, &QFileSystemWatcher::directoryChanged, q, [this](const QString &str) { onDirectoryChanged(str); }); connect(m_watcher, &QFileSystemWatcher::fileChanged, q, [this](const QString &str) { onFileChanged(str); }); } FileSystemWatcher::FileSystemWatcher(QObject *p) : QObject(p) , d(new Private(this)) { setEnabled(true); } FileSystemWatcher::FileSystemWatcher(const QStringList &paths, QObject *p) : QObject(p) , d(new Private(this, paths)) { setEnabled(true); } void FileSystemWatcher::setEnabled(bool enable) { if (isEnabled() == enable) { return; } if (enable) { Q_ASSERT(!d->m_watcher); d->m_watcher = new QFileSystemWatcher; if (!d->m_paths.empty()) { d->m_watcher->addPaths(d->m_paths); } d->connectWatcher(); } else { Q_ASSERT(d->m_watcher); delete d->m_watcher; d->m_watcher = nullptr; } } bool FileSystemWatcher::isEnabled() const { return d->m_watcher != nullptr; } FileSystemWatcher::~FileSystemWatcher() { } void FileSystemWatcher::setDelay(int ms) { Q_ASSERT(ms >= 0); d->m_timer.setInterval(ms); } int FileSystemWatcher::delay() const { return d->m_timer.interval(); } void FileSystemWatcher::blacklistFiles(const QStringList &paths) { d->m_blacklist += paths; QStringList blacklisted; d->m_paths.erase(kdtools::separate_if(d->m_paths.begin(), d->m_paths.end(), std::back_inserter(blacklisted), d->m_paths.begin(), [this](const QString &path) { return is_blacklisted(path, d->m_blacklist); }) .second, d->m_paths.end()); if (d->m_watcher && !blacklisted.empty()) { d->m_watcher->removePaths(blacklisted); } } void FileSystemWatcher::whitelistFiles(const QStringList &patterns) { d->m_whitelist += patterns; // ### would be nice to add newly-matching paths here right away, // ### but it's not as simple as blacklisting above, esp. since we // ### don't want to subject addPath()'ed paths to whitelisting. } static QStringList resolve(const QStringList &paths, const QStringList &blacklist, const QStringList &whitelist) { if (paths.empty()) { return QStringList(); } QStringList result; for (const QString &path : paths) { if (QDir(path).exists()) { result += list_dir_absolute(path, blacklist, whitelist); } } return result + resolve(result, blacklist, whitelist); } void FileSystemWatcher::addPaths(const QStringList &paths) { if (paths.empty()) { return; } const QStringList newPaths = paths + resolve(paths, d->m_blacklist, d->m_whitelist); if (!newPaths.empty()) { qCDebug(LIBKLEO_LOG) << "adding\n " << newPaths.join(QLatin1String("\n ")) << "\n/end"; } d->m_paths += newPaths; d->m_seenPaths.insert(newPaths.begin(), newPaths.end()); if (d->m_watcher && !newPaths.empty()) { d->m_watcher->addPaths(newPaths); } } void FileSystemWatcher::addPath(const QString &path) { addPaths(QStringList(path)); } void FileSystemWatcher::removePaths(const QStringList &paths) { if (paths.empty()) { return; } for (const QString &i : paths) { d->m_paths.removeAll(i); } if (d->m_watcher) { d->m_watcher->removePaths(paths); } } void FileSystemWatcher::removePath(const QString &path) { removePaths(QStringList(path)); } #include "moc_filesystemwatcher.cpp" diff --git a/src/utils/filesystemwatcher.h b/src/utils/filesystemwatcher.h index 6e7af17c6..6389ca36a 100644 --- a/src/utils/filesystemwatcher.h +++ b/src/utils/filesystemwatcher.h @@ -1,55 +1,55 @@ /* -*- mode: c++; c-basic-offset:4 -*- filesystemwatcher.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include - #include "kleo_export.h" -class QString; +#include #include +class QString; + namespace Kleo { class KLEO_EXPORT FileSystemWatcher : public QObject { Q_OBJECT public: explicit FileSystemWatcher(QObject *parent = nullptr); explicit FileSystemWatcher(const QStringList &paths, QObject *parent = nullptr); ~FileSystemWatcher() override; void setDelay(int ms); int delay() const; void setEnabled(bool enable); bool isEnabled() const; void addPaths(const QStringList &paths); void addPath(const QString &path); void blacklistFiles(const QStringList &patterns); void whitelistFiles(const QStringList &patterns); QStringList files() const; void removePaths(const QStringList &path); void removePath(const QString &path); Q_SIGNALS: void directoryChanged(const QString &path); void fileChanged(const QString &path); void triggered(); private: class Private; QScopedPointer const d; }; } diff --git a/src/utils/formatting.cpp b/src/utils/formatting.cpp index 9c4be9f0e..2a1183b7d 100644 --- a/src/utils/formatting.cpp +++ b/src/utils/formatting.cpp @@ -1,1314 +1,1314 @@ /* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*- utils/formatting.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "formatting.h" #include "cryptoconfig.h" #include "gnupg.h" -#include -#include -#include -#include +#include +#include +#include +#include -#include -#include +#include +#include #include #include -#include -#include - #include #include #include #include #include #include // for Qt::escape +#include +#include + using namespace GpgME; using namespace Kleo; // // Name // QString Formatting::prettyName(int proto, const char *id, const char *name_, const char *comment_) { if (proto == GpgME::OpenPGP) { const QString name = QString::fromUtf8(name_); if (name.isEmpty()) { return QString(); } const QString comment = QString::fromUtf8(comment_); if (comment.isEmpty()) { return name; } return QStringLiteral("%1 (%2)").arg(name, comment); } if (proto == GpgME::CMS) { const DN subject(id); const QString cn = subject[QStringLiteral("CN")].trimmed(); if (cn.isEmpty()) { return subject.prettyDN(); } return cn; } return QString(); } QString Formatting::prettyNameAndEMail(int proto, const char *id, const char *name_, const char *email_, const char *comment_) { return prettyNameAndEMail(proto, QString::fromUtf8(id), QString::fromUtf8(name_), prettyEMail(email_, id), QString::fromUtf8(comment_)); } QString Formatting::prettyNameAndEMail(int proto, const QString &id, const QString &name, const QString &email, const QString &comment) { if (proto == GpgME::OpenPGP) { if (name.isEmpty()) { if (email.isEmpty()) { return QString(); } else if (comment.isEmpty()) { return QStringLiteral("<%1>").arg(email); } else { return QStringLiteral("(%2) <%1>").arg(email, comment); } } if (email.isEmpty()) { if (comment.isEmpty()) { return name; } else { return QStringLiteral("%1 (%2)").arg(name, comment); } } if (comment.isEmpty()) { return QStringLiteral("%1 <%2>").arg(name, email); } else { return QStringLiteral("%1 (%3) <%2>").arg(name, email, comment); } } if (proto == GpgME::CMS) { const DN subject(id); const QString cn = subject[QStringLiteral("CN")].trimmed(); if (cn.isEmpty()) { return subject.prettyDN(); } return cn; } return QString(); } QString Formatting::prettyUserID(const UserID &uid) { if (uid.parent().protocol() == GpgME::OpenPGP) { return prettyNameAndEMail(uid); } const QByteArray id = QByteArray(uid.id()).trimmed(); if (id.startsWith('<')) { return prettyEMail(uid.email(), uid.id()); } if (id.startsWith('(')) { // ### parse uri/dns: return QString::fromUtf8(uid.id()); } else { return DN(uid.id()).prettyDN(); } } QString Formatting::prettyKeyID(const char *id) { if (!id) { return QString(); } return QLatin1String("0x") + QString::fromLatin1(id).toUpper(); } QString Formatting::prettyNameAndEMail(const UserID &uid) { return prettyNameAndEMail(uid.parent().protocol(), uid.id(), uid.name(), uid.email(), uid.comment()); } QString Formatting::prettyNameAndEMail(const Key &key) { return prettyNameAndEMail(key.userID(0)); } QString Formatting::prettyName(const Key &key) { return prettyName(key.userID(0)); } QString Formatting::prettyName(const UserID &uid) { return prettyName(uid.parent().protocol(), uid.id(), uid.name(), uid.comment()); } QString Formatting::prettyName(const UserID::Signature &sig) { return prettyName(GpgME::OpenPGP, sig.signerUserID(), sig.signerName(), sig.signerComment()); } // // EMail // QString Formatting::prettyEMail(const Key &key) { for (unsigned int i = 0, end = key.numUserIDs(); i < end; ++i) { const QString email = prettyEMail(key.userID(i)); if (!email.isEmpty()) { return email; } } return QString(); } QString Formatting::prettyEMail(const UserID &uid) { return prettyEMail(uid.email(), uid.id()); } QString Formatting::prettyEMail(const UserID::Signature &sig) { return prettyEMail(sig.signerEmail(), sig.signerUserID()); } QString Formatting::prettyEMail(const char *email_, const char *id) { QString email; QString name; QString comment; if (email_ && KEmailAddress::splitAddress(QString::fromUtf8(email_), name, email, comment) == KEmailAddress::AddressOk) { return email; } else { return DN(id)[QStringLiteral("EMAIL")].trimmed(); } } // // Tooltip // namespace { static QString protect_whitespace(QString s) { static const QLatin1Char SP(' '); static const QLatin1Char NBSP('\xA0'); return s.replace(SP, NBSP); } template QString format_row(const QString &field, const T_arg &arg) { return QStringLiteral("%1:%2").arg(protect_whitespace(field), arg); } QString format_row(const QString &field, const QString &arg) { return QStringLiteral("%1:%2").arg(protect_whitespace(field), arg.toHtmlEscaped()); } QString format_row(const QString &field, const char *arg) { return format_row(field, QString::fromUtf8(arg)); } QString format_keytype(const Key &key) { const Subkey subkey = key.subkey(0); if (key.hasSecret()) { return i18n("%1-bit %2 (secret key available)", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString())); } else { return i18n("%1-bit %2", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString())); } } QString format_subkeytype(const Subkey &subkey) { const auto algo = subkey.publicKeyAlgorithm(); if (algo == Subkey::AlgoECC || algo == Subkey::AlgoECDSA || algo == Subkey::AlgoECDH || algo == Subkey::AlgoEDDSA) { return QString::fromStdString(subkey.algoName()); } return i18n("%1-bit %2", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString())); } QString format_keyusage(const Key &key) { QStringList capabilities; if (key.canReallySign()) { if (key.isQualified()) { capabilities.push_back(i18n("Signing (Qualified)")); } else { capabilities.push_back(i18n("Signing")); } } if (key.canEncrypt()) { capabilities.push_back(i18n("Encryption")); } if (key.canCertify()) { capabilities.push_back(i18n("Certifying User-IDs")); } if (key.canAuthenticate()) { capabilities.push_back(i18n("SSH Authentication")); } return capabilities.join(QLatin1String(", ")); } QString format_subkeyusage(const Subkey &subkey) { QStringList capabilities; if (subkey.canSign()) { if (subkey.isQualified()) { capabilities.push_back(i18n("Signing (Qualified)")); } else { capabilities.push_back(i18n("Signing")); } } if (subkey.canEncrypt()) { capabilities.push_back(i18n("Encryption")); } if (subkey.canCertify()) { capabilities.push_back(i18n("Certifying User-IDs")); } if (subkey.canAuthenticate()) { capabilities.push_back(i18n("SSH Authentication")); } return capabilities.join(QLatin1String(", ")); } static QString time_t2string(time_t t) { const QDateTime dt = QDateTime::fromSecsSinceEpoch(t); return QLocale().toString(dt, QLocale::ShortFormat); } static QString make_red(const QString &txt) { return QLatin1String("") + txt.toHtmlEscaped() + QLatin1String(""); } } QString Formatting::toolTip(const Key &key, int flags) { if (flags == 0 || (key.protocol() != GpgME::CMS && key.protocol() != GpgME::OpenPGP)) { return QString(); } const Subkey subkey = key.subkey(0); QString result; if (flags & Validity) { if (key.protocol() == GpgME::OpenPGP || (key.keyListMode() & Validate)) { if (key.isRevoked()) { result = make_red(i18n("Revoked")); } else if (key.isExpired()) { result = make_red(i18n("Expired")); } else if (key.isDisabled()) { result = i18n("Disabled"); } else if (key.keyListMode() & GpgME::Validate) { unsigned int fullyTrusted = 0; for (const auto &uid : key.userIDs()) { if (uid.validity() >= UserID::Validity::Full) { fullyTrusted++; } } if (fullyTrusted == key.numUserIDs()) { result = i18n("All User-IDs are certified."); const auto compliance = complianceStringForKey(key); if (!compliance.isEmpty()) { result += QStringLiteral("
") + compliance; } } else { result = i18np("One User-ID is not certified.", "%1 User-IDs are not certified.", key.numUserIDs() - fullyTrusted); } } else { result = i18n("The validity cannot be checked at the moment."); } } else { result = i18n("The validity cannot be checked at the moment."); } } if (flags == Validity) { return result; } result += QLatin1String(""); if (key.protocol() == GpgME::CMS) { if (flags & SerialNumber) { result += format_row(i18n("Serial number"), key.issuerSerial()); } if (flags & Issuer) { result += format_row(i18n("Issuer"), key.issuerName()); } } if (flags & UserIDs) { const std::vector uids = key.userIDs(); if (!uids.empty()) { result += format_row(key.protocol() == GpgME::CMS ? i18n("Subject") : i18n("User-ID"), prettyUserID(uids.front())); } if (uids.size() > 1) { for (auto it = uids.begin() + 1, end = uids.end(); it != end; ++it) { if (!it->isRevoked() && !it->isInvalid()) { result += format_row(i18n("a.k.a."), prettyUserID(*it)); } } } } if (flags & ExpiryDates) { result += format_row(i18n("Created"), time_t2string(subkey.creationTime())); if (key.isExpired()) { result += format_row(i18n("Expired"), time_t2string(subkey.expirationTime())); } else if (!subkey.neverExpires()) { result += format_row(i18n("Expires"), time_t2string(subkey.expirationTime())); } } if (flags & CertificateType) { result += format_row(i18n("Type"), format_keytype(key)); } if (flags & CertificateUsage) { result += format_row(i18n("Usage"), format_keyusage(key)); } if (flags & KeyID) { result += format_row(i18n("Key-ID"), QString::fromLatin1(key.shortKeyID())); } if (flags & Fingerprint) { result += format_row(i18n("Fingerprint"), key.primaryFingerprint()); } if (flags & OwnerTrust) { if (key.protocol() == GpgME::OpenPGP) { result += format_row(i18n("Certification trust"), ownerTrustShort(key)); } else if (key.isRoot()) { result += format_row(i18n("Trusted issuer?"), key.userID(0).validity() == UserID::Ultimate ? i18n("Yes") : i18n("No")); } } if (flags & StorageLocation) { if (const char *card = subkey.cardSerialNumber()) { result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card))); } else { result += format_row(i18n("Stored"), i18nc("stored...", "on this computer")); } } if (flags & Subkeys) { for (const auto &sub : key.subkeys()) { result += QLatin1String("
"); result += format_row(i18n("Subkey"), sub.fingerprint()); if (sub.isRevoked()) { result += format_row(i18n("Status"), i18n("Revoked")); } else if (sub.isExpired()) { result += format_row(i18n("Status"), i18n("Expired")); } if (flags & ExpiryDates) { result += format_row(i18n("Created"), time_t2string(sub.creationTime())); if (key.isExpired()) { result += format_row(i18n("Expired"), time_t2string(sub.expirationTime())); } else if (!subkey.neverExpires()) { result += format_row(i18n("Expires"), time_t2string(sub.expirationTime())); } } if (flags & CertificateType) { result += format_row(i18n("Type"), format_subkeytype(sub)); } if (flags & CertificateUsage) { result += format_row(i18n("Usage"), format_subkeyusage(sub)); } if (flags & StorageLocation) { if (const char *card = sub.cardSerialNumber()) { result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card))); } else { result += format_row(i18n("Stored"), i18nc("stored...", "on this computer")); } } } } result += QLatin1String("
"); return result; } namespace { template QString getValidityStatement(const Container &keys) { const bool allKeysAreOpenPGP = std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.protocol() == GpgME::OpenPGP; }); const bool allKeysAreValidated = std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.keyListMode() & Validate; }); if (allKeysAreOpenPGP || allKeysAreValidated) { const bool someKeysAreBad = std::any_of(keys.cbegin(), keys.cend(), std::mem_fn(&Key::isBad)); if (someKeysAreBad) { return i18n("Some keys are revoked, expired, disabled, or invalid."); } else { const bool allKeysAreFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Formatting::uidsHaveFullValidity); if (allKeysAreFullyValid) { return i18n("All keys are certified."); } else { return i18n("Some keys are not certified."); } } } return i18n("The validity of the keys cannot be checked at the moment."); } } QString Formatting::toolTip(const KeyGroup &group, int flags) { static const unsigned int maxNumKeysForTooltip = 20; if (group.isNull()) { return QString(); } const KeyGroup::Keys &keys = group.keys(); if (keys.size() == 0) { return i18nc("@info:tooltip", "This group does not contain any keys."); } const QString validity = (flags & Validity) ? getValidityStatement(keys) : QString(); if (flags == Validity) { return validity; } // list either up to maxNumKeysForTooltip keys or (maxNumKeysForTooltip-1) keys followed by "and n more keys" const unsigned int numKeysForTooltip = keys.size() > maxNumKeysForTooltip ? maxNumKeysForTooltip - 1 : keys.size(); QStringList result; result.reserve(3 + 2 + numKeysForTooltip + 2); if (!validity.isEmpty()) { result.push_back(QStringLiteral("

")); result.push_back(validity.toHtmlEscaped()); result.push_back(QStringLiteral("

")); } result.push_back(QStringLiteral("

")); result.push_back(i18n("Keys:")); { auto it = keys.cbegin(); for (unsigned int i = 0; i < numKeysForTooltip; ++i, ++it) { result.push_back(QLatin1String("
") + Formatting::summaryLine(*it).toHtmlEscaped()); } } if (keys.size() > numKeysForTooltip) { result.push_back(QLatin1String("
") + i18ncp("this follows a list of keys", "and 1 more key", "and %1 more keys", keys.size() - numKeysForTooltip)); } result.push_back(QStringLiteral("

")); return result.join(QLatin1Char('\n')); } // // Creation and Expiration // namespace { static QDate time_t2date(time_t t) { if (!t) { return {}; } const QDateTime dt = QDateTime::fromSecsSinceEpoch(t); return dt.date(); } static QString accessible_date_format() { return i18nc( "date format suitable for screen readers; " "d: day as a number without a leading zero, " "MMMM: localized month name, " "yyyy: year as a four digit number", "MMMM d, yyyy"); } template QString expiration_date_string(const T &tee, const QString &noExpiration) { return tee.neverExpires() ? noExpiration : Formatting::dateString(time_t2date(tee.expirationTime())); } template QDate creation_date(const T &tee) { return time_t2date(tee.creationTime()); } template QDate expiration_date(const T &tee) { return time_t2date(tee.expirationTime()); } } QString Formatting::dateString(time_t t) { return dateString(time_t2date(t)); } QString Formatting::dateString(const QDate &date) { return QLocale().toString(date, QLocale::ShortFormat); } QString Formatting::accessibleDate(time_t t) { return accessibleDate(time_t2date(t)); } QString Formatting::accessibleDate(const QDate &date) { return QLocale().toString(date, accessible_date_format()); } QString Formatting::expirationDateString(const Key &key, const QString &noExpiration) { return expiration_date_string(key.subkey(0), noExpiration); } QString Formatting::expirationDateString(const Subkey &subkey, const QString &noExpiration) { return expiration_date_string(subkey, noExpiration); } QString Formatting::expirationDateString(const UserID::Signature &sig, const QString &noExpiration) { return expiration_date_string(sig, noExpiration); } QDate Formatting::expirationDate(const Key &key) { return expiration_date(key.subkey(0)); } QDate Formatting::expirationDate(const Subkey &subkey) { return expiration_date(subkey); } QDate Formatting::expirationDate(const UserID::Signature &sig) { return expiration_date(sig); } QString Formatting::accessibleExpirationDate(const Key &key, const QString &noExpiration) { if (key.subkey(0).neverExpires()) { return noExpiration.isEmpty() ? i18n("no expiration") : noExpiration; } else { return accessibleDate(expirationDate(key)); } } QString Formatting::creationDateString(const Key &key) { return dateString(creation_date(key.subkey(0))); } QString Formatting::creationDateString(const Subkey &subkey) { return dateString(creation_date(subkey)); } QString Formatting::creationDateString(const UserID::Signature &sig) { return dateString(creation_date(sig)); } QDate Formatting::creationDate(const Key &key) { return creation_date(key.subkey(0)); } QDate Formatting::creationDate(const Subkey &subkey) { return creation_date(subkey); } QDate Formatting::creationDate(const UserID::Signature &sig) { return creation_date(sig); } QString Formatting::accessibleCreationDate(const Key &key) { return accessibleDate(creationDate(key)); } // // Types // QString Formatting::displayName(GpgME::Protocol p) { if (p == GpgME::CMS) { return i18nc("X.509/CMS encryption standard", "S/MIME"); } if (p == GpgME::OpenPGP) { return i18n("OpenPGP"); } return i18nc("Unknown encryption protocol", "Unknown"); } QString Formatting::type(const Key &key) { return displayName(key.protocol()); } QString Formatting::type(const Subkey &subkey) { return QString::fromUtf8(subkey.publicKeyAlgorithmAsString()); } QString Formatting::type(const KeyGroup &group) { Q_UNUSED(group) return i18nc("a group of keys/certificates", "Group"); } // // Status / Validity // QString Formatting::ownerTrustShort(const Key &key) { return ownerTrustShort(key.ownerTrust()); } QString Formatting::ownerTrustShort(Key::OwnerTrust trust) { switch (trust) { case Key::Unknown: return i18nc("unknown trust level", "unknown"); case Key::Never: return i18n("untrusted"); case Key::Marginal: return i18nc("marginal trust", "marginal"); case Key::Full: return i18nc("full trust", "full"); case Key::Ultimate: return i18nc("ultimate trust", "ultimate"); case Key::Undefined: return i18nc("undefined trust", "undefined"); default: Q_ASSERT(!"unexpected owner trust value"); break; } return QString(); } QString Formatting::validityShort(const Subkey &subkey) { if (subkey.isRevoked()) { return i18n("revoked"); } if (subkey.isExpired()) { return i18n("expired"); } if (subkey.isDisabled()) { return i18n("disabled"); } if (subkey.isInvalid()) { return i18n("invalid"); } return i18nc("as in good/valid signature", "good"); } QString Formatting::validityShort(const UserID &uid) { if (uid.isRevoked()) { return i18n("revoked"); } if (uid.isInvalid()) { return i18n("invalid"); } switch (uid.validity()) { case UserID::Unknown: return i18nc("unknown trust level", "unknown"); case UserID::Undefined: return i18nc("undefined trust", "undefined"); case UserID::Never: return i18n("untrusted"); case UserID::Marginal: return i18nc("marginal trust", "marginal"); case UserID::Full: return i18nc("full trust", "full"); case UserID::Ultimate: return i18nc("ultimate trust", "ultimate"); } return QString(); } QString Formatting::validityShort(const UserID::Signature &sig) { switch (sig.status()) { case UserID::Signature::NoError: if (!sig.isInvalid()) { /* See RFC 4880 Section 5.2.1 */ switch (sig.certClass()) { case 0x10: /* Generic */ case 0x11: /* Persona */ case 0x12: /* Casual */ case 0x13: /* Positive */ return i18n("valid"); case 0x30: return i18n("revoked"); default: return i18n("class %1", sig.certClass()); } } Q_FALLTHROUGH(); // fall through: case UserID::Signature::GeneralError: return i18n("invalid"); case UserID::Signature::SigExpired: return i18n("expired"); case UserID::Signature::KeyExpired: return i18n("certificate expired"); case UserID::Signature::BadSignature: return i18nc("fake/invalid signature", "bad"); case UserID::Signature::NoPublicKey: { /* GnuPG returns the same error for no public key as for expired * or revoked certificates. */ const auto key = KeyCache::instance()->findByKeyIDOrFingerprint(sig.signerKeyID()); if (key.isNull()) { return i18n("no public key"); } else if (key.isExpired()) { return i18n("key expired"); } else if (key.isRevoked()) { return i18n("key revoked"); } else if (key.isDisabled()) { return i18n("key disabled"); } /* can't happen */ return QStringLiteral("unknown"); } } return QString(); } QIcon Formatting::validityIcon(const UserID::Signature &sig) { switch (sig.status()) { case UserID::Signature::NoError: if (!sig.isInvalid()) { /* See RFC 4880 Section 5.2.1 */ switch (sig.certClass()) { case 0x10: /* Generic */ case 0x11: /* Persona */ case 0x12: /* Casual */ case 0x13: /* Positive */ return QIcon::fromTheme(QStringLiteral("emblem-success")); case 0x30: return QIcon::fromTheme(QStringLiteral("emblem-error")); default: return QIcon(); } } Q_FALLTHROUGH(); // fall through: case UserID::Signature::BadSignature: case UserID::Signature::GeneralError: return QIcon::fromTheme(QStringLiteral("emblem-error")); case UserID::Signature::SigExpired: case UserID::Signature::KeyExpired: return QIcon::fromTheme(QStringLiteral("emblem-information")); case UserID::Signature::NoPublicKey: return QIcon::fromTheme(QStringLiteral("emblem-question")); } return QIcon(); } QString Formatting::formatKeyLink(const Key &key) { if (key.isNull()) { return QString(); } return QStringLiteral("%2").arg(QLatin1String(key.primaryFingerprint()), Formatting::prettyName(key)); } QString Formatting::formatForComboBox(const GpgME::Key &key) { const QString name = prettyName(key); QString mail = prettyEMail(key); if (!mail.isEmpty()) { mail = QLatin1Char('<') + mail + QLatin1Char('>'); } return i18nc("name, email, key id", "%1 %2 (%3)", name, mail, QLatin1String(key.shortKeyID())).simplified(); } namespace { static QString keyToString(const Key &key) { Q_ASSERT(!key.isNull()); const QString email = Formatting::prettyEMail(key); const QString name = Formatting::prettyName(key); if (name.isEmpty()) { return email; } else if (email.isEmpty()) { return name; } else { return QStringLiteral("%1 <%2>").arg(name, email); } } } const char *Formatting::summaryToString(const Signature::Summary summary) { if (summary & Signature::Red) { return "RED"; } if (summary & Signature::Green) { return "GREEN"; } return "YELLOW"; } QString Formatting::signatureToString(const Signature &sig, const Key &key) { if (sig.isNull()) { return QString(); } const bool red = (sig.summary() & Signature::Red); const bool valid = (sig.summary() & Signature::Valid); if (red) { if (key.isNull()) { if (const char *fpr = sig.fingerprint()) { return i18n("Bad signature by unknown certificate %1: %2", QString::fromLatin1(fpr), QString::fromLocal8Bit(sig.status().asString())); } else { return i18n("Bad signature by an unknown certificate: %1", QString::fromLocal8Bit(sig.status().asString())); } } else { return i18n("Bad signature by %1: %2", keyToString(key), QString::fromLocal8Bit(sig.status().asString())); } } else if (valid) { if (key.isNull()) { if (const char *fpr = sig.fingerprint()) { return i18n("Good signature by unknown certificate %1.", QString::fromLatin1(fpr)); } else { return i18n("Good signature by an unknown certificate."); } } else { return i18n("Good signature by %1.", keyToString(key)); } } else if (key.isNull()) { if (const char *fpr = sig.fingerprint()) { return i18n("Invalid signature by unknown certificate %1: %2", QString::fromLatin1(fpr), QString::fromLocal8Bit(sig.status().asString())); } else { return i18n("Invalid signature by an unknown certificate: %1", QString::fromLocal8Bit(sig.status().asString())); } } else { return i18n("Invalid signature by %1: %2", keyToString(key), QString::fromLocal8Bit(sig.status().asString())); } } // // ImportResult // QString Formatting::importMetaData(const Import &import, const QStringList &ids) { const QString result = importMetaData(import); if (result.isEmpty()) { return QString(); } else { return result + QLatin1Char('\n') + i18n("This certificate was imported from the following sources:") + QLatin1Char('\n') + ids.join(QLatin1Char('\n')); } } QString Formatting::importMetaData(const Import &import) { if (import.isNull()) { return QString(); } if (import.error().isCanceled()) { return i18n("The import of this certificate was canceled."); } if (import.error()) { return i18n("An error occurred importing this certificate: %1", QString::fromLocal8Bit(import.error().asString())); } const unsigned int status = import.status(); if (status & Import::NewKey) { return (status & Import::ContainedSecretKey) ? i18n("This certificate was new to your keystore. The secret key is available.") : i18n("This certificate is new to your keystore."); } QStringList results; if (status & Import::NewUserIDs) { results.push_back(i18n("New user-ids were added to this certificate by the import.")); } if (status & Import::NewSignatures) { results.push_back(i18n("New signatures were added to this certificate by the import.")); } if (status & Import::NewSubkeys) { results.push_back(i18n("New subkeys were added to this certificate by the import.")); } return results.empty() ? i18n("The import contained no new data for this certificate. It is unchanged.") : results.join(QLatin1Char('\n')); } // // Overview in CertificateDetailsDialog // QString Formatting::formatOverview(const Key &key) { return toolTip(key, AllOptions); } QString Formatting::usageString(const Subkey &sub) { QStringList usageStrings; if (sub.canCertify()) { usageStrings << i18n("Certify"); } if (sub.canSign()) { usageStrings << i18n("Sign"); } if (sub.canEncrypt()) { usageStrings << i18n("Encrypt"); } if (sub.canAuthenticate()) { usageStrings << i18n("Authenticate"); } return usageStrings.join(QLatin1String(", ")); } QString Formatting::summaryLine(const Key &key) { return keyToString(key) + QLatin1Char(' ') + i18nc("(validity, protocol, creation date)", "(%1, %2, created: %3)", Formatting::complianceStringShort(key), displayName(key.protocol()), Formatting::creationDateString(key)); } QString Formatting::summaryLine(const KeyGroup &group) { switch (group.source()) { case KeyGroup::ApplicationConfig: case KeyGroup::GnuPGConfig: return i18ncp("name of group of keys (n key(s), validity)", "%2 (1 key, %3)", "%2 (%1 keys, %3)", group.keys().size(), group.name(), Formatting::complianceStringShort(group)); case KeyGroup::Tags: return i18ncp("name of group of keys (n key(s), validity, tag)", "%2 (1 key, %3, tag)", "%2 (%1 keys, %3, tag)", group.keys().size(), group.name(), Formatting::complianceStringShort(group)); default: return i18ncp("name of group of keys (n key(s), validity, group ...)", "%2 (1 key, %3, unknown origin)", "%2 (%1 keys, %3, unknown origin)", group.keys().size(), group.name(), Formatting::complianceStringShort(group)); } } namespace { QIcon iconForValidity(UserID::Validity validity) { switch (validity) { case UserID::Ultimate: case UserID::Full: case UserID::Marginal: return QIcon::fromTheme(QStringLiteral("emblem-success")); case UserID::Never: return QIcon::fromTheme(QStringLiteral("emblem-error")); case UserID::Undefined: case UserID::Unknown: default: return QIcon::fromTheme(QStringLiteral("emblem-information")); } } } // Icon for certificate selection indication QIcon Formatting::iconForUid(const UserID &uid) { return iconForValidity(uid.validity()); } QString Formatting::validity(const UserID &uid) { switch (uid.validity()) { case UserID::Ultimate: return i18n("The certificate is marked as your own."); case UserID::Full: return i18n("The certificate belongs to this recipient."); case UserID::Marginal: return i18n("The trust model indicates marginally that the certificate belongs to this recipient."); case UserID::Never: return i18n("This certificate should not be used."); case UserID::Undefined: case UserID::Unknown: default: return i18n("There is no indication that this certificate belongs to this recipient."); } } QString Formatting::validity(const KeyGroup &group) { if (group.isNull()) { return QString(); } const KeyGroup::Keys &keys = group.keys(); if (keys.size() == 0) { return i18n("This group does not contain any keys."); } return getValidityStatement(keys); } namespace { UserID::Validity minimalValidityOfNotRevokedUserIDs(const Key &key) { std::vector userIDs = key.userIDs(); const auto endOfNotRevokedUserIDs = std::remove_if(userIDs.begin(), userIDs.end(), std::mem_fn(&UserID::isRevoked)); const int minValidity = std::accumulate(userIDs.begin(), endOfNotRevokedUserIDs, UserID::Ultimate + 1, [](int validity, const UserID &userID) { return std::min(validity, static_cast(userID.validity())); }); return minValidity <= UserID::Ultimate ? static_cast(minValidity) : UserID::Unknown; } template UserID::Validity minimalValidity(const Container &keys) { const int minValidity = std::accumulate(keys.cbegin(), keys.cend(), UserID::Ultimate + 1, [](int validity, const Key &key) { return std::min(validity, minimalValidityOfNotRevokedUserIDs(key)); }); return minValidity <= UserID::Ultimate ? static_cast(minValidity) : UserID::Unknown; } } QIcon Formatting::validityIcon(const KeyGroup &group) { return iconForValidity(minimalValidity(group.keys())); } bool Formatting::uidsHaveFullValidity(const Key &key) { return minimalValidityOfNotRevokedUserIDs(key) >= UserID::Full; } QString Formatting::complianceMode() { const auto complianceValue = getCryptoConfigStringValue("gpg", "compliance"); return complianceValue == QLatin1String("gnupg") ? QString() : complianceValue; } bool Formatting::isKeyDeVs(const GpgME::Key &key) { for (const auto &sub : key.subkeys()) { if (sub.isExpired() || sub.isRevoked()) { // Ignore old subkeys continue; } if (!sub.isDeVs()) { return false; } } return true; } QString Formatting::complianceStringForKey(const GpgME::Key &key) { // There will likely be more in the future for other institutions // for now we only have DE-VS if (Kleo::gnupgIsDeVsCompliant()) { if (uidsHaveFullValidity(key) && isKeyDeVs(key)) { return i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "May be used for %1 communication.", deVsString()); } else { return i18nc( "VS-NfD-conforming is a German standard for restricted documents. For which special restrictions about algorithms apply. The string describes " "if a key is compliant to that..", "May not be used for %1 communication.", deVsString()); } } return QString(); } QString Formatting::complianceStringShort(const GpgME::Key &key) { const bool keyValidityChecked = (key.keyListMode() & GpgME::Validate); if (keyValidityChecked && Formatting::uidsHaveFullValidity(key)) { if (Kleo::gnupgIsDeVsCompliant() && Formatting::isKeyDeVs(key)) { return QStringLiteral("★ ") + deVsString(true); } return i18nc("As in all user IDs are valid.", "certified"); } if (key.isExpired()) { return i18n("expired"); } if (key.isRevoked()) { return i18n("revoked"); } if (key.isDisabled()) { return i18n("disabled"); } if (key.isInvalid()) { return i18n("invalid"); } if (keyValidityChecked) { return i18nc("As in not all user IDs are valid.", "not certified"); } return i18nc("The validity of the user IDs has not been/could not be checked", "not checked"); } QString Formatting::complianceStringShort(const KeyGroup &group) { const KeyGroup::Keys &keys = group.keys(); const bool allKeysFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Formatting::uidsHaveFullValidity); if (allKeysFullyValid) { return i18nc("As in all keys are valid.", "all certified"); } return i18nc("As in not all keys are valid.", "not all certified"); } QString Formatting::prettyID(const char *id) { if (!id) { return QString(); } QString ret = QString::fromLatin1(id).toUpper().replace(QRegularExpression(QStringLiteral("(....)")), QStringLiteral("\\1 ")).trimmed(); // For the standard 10 group fingerprint let us use a double space in the // middle to increase readability if (ret.size() == 49) { ret.insert(24, QLatin1Char(' ')); } return ret; } QString Formatting::accessibleHexID(const char *id) { static const QRegularExpression groupOfFourRegExp{QStringLiteral("(?:(.)(.)(.)(.))")}; QString ret; ret = QString::fromLatin1(id); if (!ret.isEmpty() && (ret.size() % 4 == 0)) { ret = ret.replace(groupOfFourRegExp, QStringLiteral("\\1 \\2 \\3 \\4, ")).chopped(2); } return ret; } QString Formatting::origin(int o) { switch (o) { case Key::OriginKS: return i18n("Keyserver"); case Key::OriginDane: return QStringLiteral("DANE"); case Key::OriginWKD: return QStringLiteral("WKD"); case Key::OriginURL: return QStringLiteral("URL"); case Key::OriginFile: return i18n("File import"); case Key::OriginSelf: return i18n("Generated"); case Key::OriginOther: case Key::OriginUnknown: default: return i18n("Unknown"); } } QString Formatting::deVsString(bool compliant) { const auto filter = KeyFilterManager::instance()->keyFilterByID(compliant ? QStringLiteral("de-vs-filter") : QStringLiteral("not-de-vs-filter")); if (!filter) { return compliant ? i18n("VS-NfD compliant") : i18n("Not VS-NfD compliant"); } return filter->name(); } namespace { QString formatTrustScope(const char *trustScope) { static const QRegularExpression escapedNonAlphaNum{QStringLiteral(R"(\\([^0-9A-Za-z]))")}; const auto scopeRegExp = QString::fromUtf8(trustScope); if (scopeRegExp.startsWith(u"<[^>]+[@.]") && scopeRegExp.endsWith(u">$")) { // looks like a trust scope regular expression created by gpg auto domain = scopeRegExp.mid(10, scopeRegExp.size() - 10 - 2); domain.replace(escapedNonAlphaNum, QStringLiteral(R"(\1)")); return domain; } return scopeRegExp; } } QString Formatting::trustSignatureDomain(const GpgME::UserID::Signature &sig) { #ifdef GPGMEPP_SUPPORTS_TRUST_SIGNATURES return formatTrustScope(sig.trustScope()); #else return {}; #endif } QString Formatting::trustSignature(const GpgME::UserID::Signature &sig) { #ifdef GPGMEPP_SUPPORTS_TRUST_SIGNATURES switch (sig.trustValue()) { case TrustSignatureTrust::Partial: return i18nc("Certifies this key as partially trusted introducer for 'domain name'.", "Certifies this key as partially trusted introducer for '%1'.", trustSignatureDomain(sig)); case TrustSignatureTrust::Complete: return i18nc("Certifies this key as fully trusted introducer for 'domain name'.", "Certifies this key as fully trusted introducer for '%1'.", trustSignatureDomain(sig)); default: return {}; } #else return {}; #endif } diff --git a/src/utils/formatting.h b/src/utils/formatting.h index 407c5c574..6d1c05d23 100644 --- a/src/utils/formatting.h +++ b/src/utils/formatting.h @@ -1,182 +1,183 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/formatting.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include - #include "kleo_export.h" -class QString; #include + +#include + +class QString; class QDate; class QIcon; namespace GpgME { class Import; } namespace Kleo { class KeyGroup; namespace Formatting { KLEO_EXPORT QString prettyNameAndEMail(int proto, const char *id, const char *name, const char *email, const char *comment = nullptr); KLEO_EXPORT QString prettyNameAndEMail(int proto, const QString &id, const QString &name, const QString &email, const QString &comment = {}); KLEO_EXPORT QString prettyNameAndEMail(const GpgME::Key &key); KLEO_EXPORT QString prettyNameAndEMail(const GpgME::UserID &key); KLEO_EXPORT QString prettyUserID(const GpgME::UserID &uid); KLEO_EXPORT QString prettyKeyID(const char *id); KLEO_EXPORT QString prettyName(int proto, const char *id, const char *name, const char *comment = nullptr); KLEO_EXPORT QString prettyName(const GpgME::Key &key); KLEO_EXPORT QString prettyName(const GpgME::UserID &uid); KLEO_EXPORT QString prettyName(const GpgME::UserID::Signature &sig); KLEO_EXPORT QString prettyEMail(const char *email, const char *id); KLEO_EXPORT QString prettyEMail(const GpgME::Key &key); KLEO_EXPORT QString prettyEMail(const GpgME::UserID &uid); KLEO_EXPORT QString prettyEMail(const GpgME::UserID::Signature &sig); /* Formats a fingerprint or keyid into groups of four */ KLEO_EXPORT QString prettyID(const char *id); KLEO_EXPORT QString accessibleHexID(const char *id); // clang-format off enum ToolTipOption { KeyID = 0x001, Validity = 0x002, StorageLocation = 0x004, SerialNumber = 0x008, Issuer = 0x010, Subject = 0x020, ExpiryDates = 0x040, CertificateType = 0x080, CertificateUsage = 0x100, Fingerprint = 0x200, UserIDs = 0x400, OwnerTrust = 0x800, Subkeys = 0x1000, AllOptions = 0xffff }; // clang-format on KLEO_EXPORT QString toolTip(const GpgME::Key &key, int opts); KLEO_EXPORT QString toolTip(const Kleo::KeyGroup &group, int opts); /// Returns expiration date of @p key as string, or @p noExpiration if the key doesn't expire. KLEO_EXPORT QString expirationDateString(const GpgME::Key &key, const QString &noExpiration = {}); /// Returns expiration date of @p subkey as string, or @p noExpiration if the subkey doesn't expire. KLEO_EXPORT QString expirationDateString(const GpgME::Subkey &subkey, const QString &noExpiration = {}); /// Returns expiration date of @p sig as string, or @p noExpiration if the signature doesn't expire. KLEO_EXPORT QString expirationDateString(const GpgME::UserID::Signature &sig, const QString &noExpiration = {}); KLEO_EXPORT QDate expirationDate(const GpgME::Key &key); KLEO_EXPORT QDate expirationDate(const GpgME::Subkey &subkey); KLEO_EXPORT QDate expirationDate(const GpgME::UserID::Signature &sig); /** * Returns expiration date of @p key as string suitable for screen readers. * If the key doesn't expire, then it returns @p noExpiration if @p noExpiration is not empty. Otherwise, * returns the localization of "no expiration". */ KLEO_EXPORT QString accessibleExpirationDate(const GpgME::Key &key, const QString &noExpiration = {}); KLEO_EXPORT QString creationDateString(const GpgME::Key &key); KLEO_EXPORT QString creationDateString(const GpgME::Subkey &subkey); KLEO_EXPORT QString creationDateString(const GpgME::UserID::Signature &sig); KLEO_EXPORT QDate creationDate(const GpgME::Key &key); KLEO_EXPORT QDate creationDate(const GpgME::Subkey &subkey); KLEO_EXPORT QDate creationDate(const GpgME::UserID::Signature &sig); KLEO_EXPORT QString accessibleCreationDate(const GpgME::Key &key); /* Convert a GPGME style time or a QDate to a localized string */ KLEO_EXPORT QString dateString(time_t t); KLEO_EXPORT QString dateString(const QDate &date); KLEO_EXPORT QString accessibleDate(time_t t); KLEO_EXPORT QString accessibleDate(const QDate &date); KLEO_EXPORT QString displayName(GpgME::Protocol prot); KLEO_EXPORT QString type(const GpgME::Key &key); KLEO_EXPORT QString type(const GpgME::Subkey &subkey); KLEO_EXPORT QString type(const Kleo::KeyGroup &group); KLEO_EXPORT QString ownerTrustShort(const GpgME::Key &key); KLEO_EXPORT QString ownerTrustShort(GpgME::Key::OwnerTrust trust); KLEO_EXPORT QString validityShort(const GpgME::Subkey &subkey); KLEO_EXPORT QString validityShort(const GpgME::UserID &uid); KLEO_EXPORT QString validityShort(const GpgME::UserID::Signature &sig); KLEO_EXPORT QIcon validityIcon(const GpgME::UserID::Signature &sig); /* A sentence about the validity of the UserID */ KLEO_EXPORT QString validity(const GpgME::UserID &uid); KLEO_EXPORT QString validity(const Kleo::KeyGroup &group); KLEO_EXPORT QIcon validityIcon(const Kleo::KeyGroup &group); KLEO_EXPORT QString formatForComboBox(const GpgME::Key &key); KLEO_EXPORT QString formatKeyLink(const GpgME::Key &key); KLEO_EXPORT QString signatureToString(const GpgME::Signature &sig, const GpgME::Key &key); KLEO_EXPORT const char *summaryToString(const GpgME::Signature::Summary summary); KLEO_EXPORT QString importMetaData(const GpgME::Import &import); KLEO_EXPORT QString importMetaData(const GpgME::Import &import, const QStringList &sources); KLEO_EXPORT QString formatOverview(const GpgME::Key &key); KLEO_EXPORT QString usageString(const GpgME::Subkey &subkey); KLEO_EXPORT QString summaryLine(const GpgME::Key &key); KLEO_EXPORT QString summaryLine(const KeyGroup &group); KLEO_EXPORT QIcon iconForUid(const GpgME::UserID &uid); /* Is the key valid i.e. are all uids fully trusted? */ KLEO_EXPORT bool uidsHaveFullValidity(const GpgME::Key &key); /* The compliance mode of the gnupg system. Empty if compliance * mode is not set. * Use Kleo::gnupgComplianceMode() instead. */ KLEO_DEPRECATED_EXPORT QString complianceMode(); /* Is the given key in compliance with CO_DE_VS? */ KLEO_EXPORT bool isKeyDeVs(const GpgME::Key &key); /* Localized string describing the name of the VS-NfD Compliance filter. If * compliant is false the name of the not Compliant filter. * * This is required to make the string configurable which is * a common request from users because VS-NfD compliance is called * differently in different environments. E.g NATO RESTRICTED or * EU RESTRICTED. */ KLEO_EXPORT QString deVsString(bool compliant = true); /* A sentence if the key confirms to the current compliance mode */ KLEO_EXPORT QString complianceStringForKey(const GpgME::Key &key); /* A single word for use in keylists to describe the validity of the * given key, including any conformance statements relevant to the * current conformance mode. */ KLEO_EXPORT QString complianceStringShort(const GpgME::Key &key); KLEO_EXPORT QString complianceStringShort(const Kleo::KeyGroup &group); /* The origin of the key mapped to a localized string */ KLEO_EXPORT QString origin(int o); /* Human-readable trust signature scope (for trust signature regexp created by GnuPG) */ KLEO_EXPORT QString trustSignatureDomain(const GpgME::UserID::Signature &sig); /* Summary of trust signature properties */ KLEO_EXPORT QString trustSignature(const GpgME::UserID::Signature &sig); } } diff --git a/src/utils/gnupg.cpp b/src/utils/gnupg.cpp index 958f7b5e9..24d920a91 100644 --- a/src/utils/gnupg.cpp +++ b/src/utils/gnupg.cpp @@ -1,686 +1,687 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/gnupg.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2020-2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "gnupg.h" #include "assuan.h" #include "compat.h" #include "cryptoconfig.h" #include "hex.h" #include -#include -#include -#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include + +#include +#include +#include + #include #ifdef Q_OS_WIN #include "gnupg-registry.h" #endif // Q_OS_WIN #include #include -#include - using namespace GpgME; QString Kleo::gnupgHomeDirectory() { static const QString homeDir = QString::fromUtf8(GpgME::dirInfo("homedir")); return homeDir; } int Kleo::makeGnuPGError(int code) { return gpg_error(static_cast(code)); } static QString findGpgExe(GpgME::Engine engine, const QString &exe) { const GpgME::EngineInfo info = GpgME::engineInfo(engine); return info.fileName() ? QFile::decodeName(info.fileName()) : QStandardPaths::findExecutable(exe); } QString Kleo::gpgConfPath() { static const auto path = findGpgExe(GpgME::GpgConfEngine, QStringLiteral("gpgconf")); return path; } QString Kleo::gpgSmPath() { static const auto path = findGpgExe(GpgME::GpgSMEngine, QStringLiteral("gpgsm")); return path; } QString Kleo::gpgPath() { static const auto path = findGpgExe(GpgME::GpgEngine, QStringLiteral("gpg")); return path; } QStringList Kleo::gnupgFileWhitelist() { return { // The obvious pubring QStringLiteral("pubring.gpg"), // GnuPG 2.1 pubring QStringLiteral("pubring.kbx"), // Trust in X509 Certificates QStringLiteral("trustlist.txt"), // Trustdb controls ownertrust and thus WOT validity QStringLiteral("trustdb.gpg"), // We want to update when smartcard status changes QStringLiteral("reader*.status"), // No longer used in 2.1 but for 2.0 we want this QStringLiteral("secring.gpg"), // Secret keys (living under private-keys-v1.d/) QStringLiteral("*.key"), // Changes to the trustmodel / compliance mode might // affect validity so we check this, too. // Globbing for gpg.conf* here will trigger too often // as gpgconf creates files like gpg.conf.bak or // gpg.conf.tmp12312.gpgconf that should not trigger // a change. QStringLiteral("gpg.conf"), QStringLiteral("gpg.conf-?"), QStringLiteral("gpg.conf-?.?"), }; } QStringList Kleo::gnupgFolderWhitelist() { static const QDir gnupgHome{gnupgHomeDirectory()}; return { gnupgHome.path(), gnupgHome.filePath(QStringLiteral("private-keys-v1.d")), }; } namespace { class Gpg4win { public: static const Gpg4win *instance() { // We use singleton to do the signature check only once. static Gpg4win *inst = nullptr; if (!inst) { inst = new Gpg4win(); } return inst; } private: QString mVersion; QString mDescription; QString mDescLong; bool mSignedVersion; Gpg4win() : mVersion(QStringLiteral("Unknown Windows Version")) , mDescription(i18n("Certificate Manager and Unified Crypto GUI")) , mDescLong(QStringLiteral("Visit the Gpg4win homepage")) , mSignedVersion(false) { const QString instPath = Kleo::gpg4winInstallPath(); const QString verPath = instPath + QStringLiteral("/../VERSION"); QFile versionFile(verPath); QString versVersion; QString versDescription; QString versDescLong; // Open the file first to avoid a verify and then read issue where // "auditors" might say its an issue,... if (!versionFile.open(QIODevice::ReadOnly)) { // No need to translate this should only be the case in development // builds. return; } else { // Expect a three line format of three HTML strings. versVersion = QString::fromUtf8(versionFile.readLine()).trimmed(); versDescription = QString::fromUtf8(versionFile.readLine()).trimmed(); versDescLong = QString::fromUtf8(versionFile.readLine()).trimmed(); } const QString sigPath = verPath + QStringLiteral(".sig"); QFileInfo versionSig(instPath + QStringLiteral("/../VERSION.sig")); if (versionSig.exists()) { /* We have a signed version so let us check it against the GnuPG * release keys. */ QProcess gpgv; gpgv.setProgram(Kleo::gpgPath().replace(QStringLiteral("gpg.exe"), QStringLiteral("gpgv.exe"))); const QString keyringPath(QStringLiteral("%1/../share/gnupg/distsigkey.gpg").arg(Kleo::gnupgInstallPath())); gpgv.setArguments(QStringList() << QStringLiteral("--keyring") << keyringPath << QStringLiteral("--") << sigPath << verPath); gpgv.start(); gpgv.waitForFinished(); if (gpgv.exitStatus() == QProcess::NormalExit && !gpgv.exitCode()) { qCDebug(LIBKLEO_LOG) << "Valid Version: " << versVersion; mVersion = versVersion; mDescription = versDescription; mDescLong = versDescLong; mSignedVersion = true; } else { qCDebug(LIBKLEO_LOG) << "gpgv failed with stderr: " << gpgv.readAllStandardError(); qCDebug(LIBKLEO_LOG) << "gpgv stdout" << gpgv.readAllStandardOutput(); } } else { qCDebug(LIBKLEO_LOG) << "No signed VERSION file found."; } // Also take Version information from unsigned Versions. mVersion = versVersion; } public: const QString &version() const { return mVersion; } const QString &description() const { return mDescription; } const QString &longDescription() const { return mDescLong; } bool isSignedVersion() const { return mSignedVersion; } }; } // namespace bool Kleo::gpg4winSignedversion() { return Gpg4win::instance()->isSignedVersion(); } QString Kleo::gpg4winVersionNumber() { // extract the actual version number from the string returned by Gpg4win::version(); // we assume that Gpg4win::version() returns a version number (conforming to the semantic // versioning spec) optionally prefixed with some text followed by a dash, // e.g. "Gpg4win-3.1.15-beta15"; see https://dev.gnupg.org/T5663 static const QRegularExpression catchSemVerRegExp{QLatin1String{R"(-([0-9]+(?:\.[0-9]+)*(?:-[.0-9A-Za-z-]+)?(?:\+[.0-9a-zA-Z-]+)?)$)"}}; QString ret; const auto match = catchSemVerRegExp.match(gpg4winVersion()); if (match.hasMatch()) { ret = match.captured(1); } else { ret = gpg4winVersion(); } qCDebug(LIBKLEO_LOG) << __func__ << "returns" << ret; return ret; } QString Kleo::gpg4winVersion() { return Gpg4win::instance()->version(); } QString Kleo::gpg4winDescription() { return Gpg4win::instance()->description(); } QString Kleo::gpg4winLongDescription() { return Gpg4win::instance()->longDescription(); } QString Kleo::gpg4winInstallPath() { #ifdef Q_OS_WIN // QApplication::applicationDirPath is only used as a fallback // to support the case where Kleopatra is not installed from // Gpg4win but Gpg4win is also installed. char *instDir = read_w32_registry_string("HKEY_LOCAL_MACHINE", "Software\\GPG4Win", "Install Directory"); if (!instDir) { // Fallback to HKCU instDir = read_w32_registry_string("HKEY_CURRENT_USER", "Software\\GPG4Win", "Install Directory"); } if (instDir) { QString ret = QString::fromLocal8Bit(instDir) + QStringLiteral("/bin"); free(instDir); return ret; } qCDebug(LIBKLEO_LOG) << "Gpg4win not found. Falling back to Kleopatra instdir."; #endif return QCoreApplication::applicationDirPath(); } QString Kleo::gnupgInstallPath() { #ifdef Q_OS_WIN // QApplication::applicationDirPath is only used as a fallback // to support the case where Kleopatra is not installed from // Gpg4win but Gpg4win is also installed. char *instDir = read_w32_registry_string("HKEY_LOCAL_MACHINE", "Software\\GnuPG", "Install Directory"); if (!instDir) { // Fallback to HKCU instDir = read_w32_registry_string("HKEY_CURRENT_USER", "Software\\GnuPG", "Install Directory"); } if (instDir) { QString ret = QString::fromLocal8Bit(instDir) + QStringLiteral("/bin"); free(instDir); return ret; } qCDebug(LIBKLEO_LOG) << "GnuPG not found. Falling back to gpgconf list dir."; #endif return gpgConfListDir("bindir"); } QString Kleo::gpgConfListDir(const char *which) { if (!which || !*which) { return QString(); } const QString gpgConfPath = Kleo::gpgConfPath(); if (gpgConfPath.isEmpty()) { return QString(); } QProcess gpgConf; qCDebug(LIBKLEO_LOG) << "gpgConfListDir: starting " << qPrintable(gpgConfPath) << " --list-dirs"; gpgConf.start(gpgConfPath, QStringList() << QStringLiteral("--list-dirs")); if (!gpgConf.waitForFinished()) { qCDebug(LIBKLEO_LOG) << "gpgConfListDir(): failed to execute gpgconf: " << qPrintable(gpgConf.errorString()); qCDebug(LIBKLEO_LOG) << "output was:\n" << gpgConf.readAllStandardError().constData(); return QString(); } const QList lines = gpgConf.readAllStandardOutput().split('\n'); for (const QByteArray &line : lines) { if (line.startsWith(which) && line[qstrlen(which)] == ':') { const int begin = qstrlen(which) + 1; int end = line.size(); while (end && (line[end - 1] == '\n' || line[end - 1] == '\r')) { --end; } const QString result = QDir::fromNativeSeparators(QFile::decodeName(hexdecode(line.mid(begin, end - begin)))); qCDebug(LIBKLEO_LOG) << "gpgConfListDir: found " << qPrintable(result) << " for '" << which << "'entry"; return result; } } qCDebug(LIBKLEO_LOG) << "gpgConfListDir(): didn't find '" << which << "'" << "entry in output:\n" << gpgConf.readAllStandardError().constData(); return QString(); } static std::array getVersionFromString(const char *actual, bool &ok) { std::array ret; ok = false; if (!actual) { return ret; } QString versionString = QString::fromLatin1(actual); // Try to fix it up QRegExp rx(QLatin1String(R"((\d+)\.(\d+)\.(\d+)(?:-svn\d+)?.*)")); for (int i = 0; i < 3; i++) { if (!rx.exactMatch(versionString)) { versionString += QStringLiteral(".0"); } else { ok = true; break; } } if (!ok) { qCDebug(LIBKLEO_LOG) << "Can't parse version " << actual; return ret; } for (int i = 0; i < 3; ++i) { ret[i] = rx.cap(i + 1).toUInt(&ok); if (!ok) { return ret; } } ok = true; return ret; } bool Kleo::versionIsAtLeast(const char *minimum, const char *actual) { if (!minimum || !actual) { return false; } bool ok; const auto minimum_version = getVersionFromString(minimum, ok); if (!ok) { return false; } const auto actual_version = getVersionFromString(actual, ok); if (!ok) { return false; } return !std::lexicographical_compare(std::begin(actual_version), std::end(actual_version), std::begin(minimum_version), std::end(minimum_version)); } bool Kleo::engineIsVersion(int major, int minor, int patch, GpgME::Engine engine) { static QMap> cachedVersions; const int required_version[] = {major, minor, patch}; // Gpgconf means spawning processes which is expensive on windows. std::array actual_version; if (!cachedVersions.contains(engine)) { const Error err = checkEngine(engine); if (err.code() == GPG_ERR_INV_ENGINE) { qCDebug(LIBKLEO_LOG) << "isVersion: invalid engine. '"; return false; } const char *actual = GpgME::engineInfo(engine).version(); bool ok; actual_version = getVersionFromString(actual, ok); qCDebug(LIBKLEO_LOG) << "Parsed" << actual << "as: " << actual_version[0] << '.' << actual_version[1] << '.' << actual_version[2] << '.'; if (!ok) { return false; } cachedVersions.insert(engine, actual_version); } else { actual_version = cachedVersions.value(engine); } // return ! ( actual_version < required_version ) return !std::lexicographical_compare(std::begin(actual_version), std::end(actual_version), std::begin(required_version), std::end(required_version)); } const QString &Kleo::paperKeyInstallPath() { static const QString pkPath = (QStandardPaths::findExecutable(QStringLiteral("paperkey"), QStringList() << QCoreApplication::applicationDirPath()).isEmpty() ? QStandardPaths::findExecutable(QStringLiteral("paperkey")) : QStandardPaths::findExecutable(QStringLiteral("paperkey"), QStringList() << QCoreApplication::applicationDirPath())); return pkPath; } bool Kleo::haveKeyserverConfigured() { if (engineIsVersion(2, 1, 19)) { // since 2.1.19 there is a builtin keyserver return true; } return !Kleo::keyserver().isEmpty(); } QString Kleo::keyserver() { QString result = getCryptoConfigStringValue("gpg", "keyserver"); if (result.isEmpty()) { result = getCryptoConfigStringValue("dirmngr", "keyserver"); } return result; } bool Kleo::haveX509DirectoryServerConfigured() { return !getCryptoConfigUrlList("dirmngr", "ldapserver").empty() // || !getCryptoConfigUrlList("dirmngr", "LDAP Server").empty() // || !getCryptoConfigUrlList("gpgsm", "keyserver").empty(); } bool Kleo::gpgComplianceP(const char *mode) { const auto conf = QGpgME::cryptoConfig(); const auto entry = getCryptoConfigEntry(conf, "gpg", "compliance"); return entry && entry->stringValue() == QString::fromLatin1(mode); } bool Kleo::gnupgUsesDeVsCompliance() { return getCryptoConfigStringValue("gpg", "compliance") == QLatin1String{"de-vs"}; } bool Kleo::gnupgIsDeVsCompliant() { if (!gnupgUsesDeVsCompliance()) { return false; } // The pseudo option compliance_de_vs was fully added in 2.2.34; // For versions between 2.2.28 and 2.2.33 there was a broken config // value with a wrong type. So for them we add an extra check. This // can be removed in future versions because. For GnuPG we could assume // non-compliance for older versions as versions of Kleopatra for // which this matters are bundled with new enough versions of GnuPG anyway if (engineIsVersion(2, 2, 28) && !engineIsVersion(2, 2, 34)) { return true; } return getCryptoConfigIntValue("gpg", "compliance_de_vs", 0) != 0; } enum GpgME::UserID::Validity Kleo::keyValidity(const GpgME::Key &key) { enum UserID::Validity validity = UserID::Validity::Unknown; for (const auto &uid : key.userIDs()) { if (validity == UserID::Validity::Unknown || validity > uid.validity()) { validity = uid.validity(); } } return validity; } #ifdef Q_OS_WIN static QString fromEncoding(unsigned int src_encoding, const char *data) { int n = MultiByteToWideChar(src_encoding, 0, data, -1, NULL, 0); if (n < 0) { return QString(); } wchar_t *result = (wchar_t *)malloc((n + 1) * sizeof *result); n = MultiByteToWideChar(src_encoding, 0, data, -1, result, n); if (n < 0) { free(result); return QString(); } const auto ret = QString::fromWCharArray(result, n); free(result); return ret; } #endif QString Kleo::stringFromGpgOutput(const QByteArray &ba) { #ifdef Q_OS_WIN /* Qt on Windows uses GetACP while GnuPG prefers * GetConsoleOutputCP. * * As we are not a console application GetConsoleOutputCP * usually returns 0. * From experience the closest thing that let's us guess * what GetConsoleOutputCP returns for a console application * it appears to be the OEMCP. */ unsigned int cpno = GetConsoleOutputCP(); if (!cpno) { cpno = GetOEMCP(); } if (!cpno) { cpno = GetACP(); } if (!cpno) { qCDebug(LIBKLEO_LOG) << "Failed to find native codepage"; return QString(); } return fromEncoding(cpno, ba.constData()); #else return QString::fromLocal8Bit(ba); #endif } QStringList Kleo::backendVersionInfo() { QStringList versions; if (Kleo::engineIsVersion(2, 2, 24, GpgME::GpgConfEngine)) { QProcess p; qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions ..."; p.start(Kleo::gpgConfPath(), {QStringLiteral("--show-versions")}); // wait at most 1 second if (!p.waitForFinished(1000)) { qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions timed out after 1 second."; } else if (p.exitStatus() != QProcess::NormalExit || p.exitCode() != 0) { qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions failed:" << p.errorString(); qCDebug(LIBKLEO_LOG) << "gpgconf stderr:" << p.readAllStandardError(); qCDebug(LIBKLEO_LOG) << "gpgconf stdout:" << p.readAllStandardOutput(); } else { const QByteArray output = p.readAllStandardOutput().replace("\r\n", "\n"); qCDebug(LIBKLEO_LOG) << "gpgconf stdout:" << p.readAllStandardOutput(); const auto lines = output.split('\n'); for (const auto &line : lines) { if (line.startsWith("* GnuPG") || line.startsWith("* Libgcrypt")) { const auto components = line.split(' '); versions.push_back(QString::fromLatin1(components.at(1) + ' ' + components.value(2))); } } } } return versions; } namespace { template auto startGpgConf(const QStringList &arguments, Function1 onSuccess, Function2 onFailure) { auto process = new QProcess; process->setProgram(Kleo::gpgConfPath()); process->setArguments(arguments); QObject::connect(process, &QProcess::started, [process]() { qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") was started successfully"; }); QObject::connect(process, &QProcess::errorOccurred, [process, onFailure](auto error) { qCDebug(LIBKLEO_LOG).nospace() << "Error while running gpgconf (" << process << "): " << error; process->deleteLater(); onFailure(); }); QObject::connect(process, &QProcess::readyReadStandardError, [process]() { for (const auto &line : process->readAllStandardError().trimmed().split('\n')) { qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") stderr: " << line; } }); QObject::connect(process, &QProcess::readyReadStandardOutput, [process]() { (void)process->readAllStandardOutput(); /* ignore stdout */ }); QObject::connect(process, qOverload(&QProcess::finished), [process, onSuccess, onFailure](int exitCode, QProcess::ExitStatus exitStatus) { if (exitStatus == QProcess::NormalExit) { qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") exited (exit code: " << exitCode << ")"; if (exitCode == 0) { onSuccess(); } else { onFailure(); } } else { qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") crashed (exit code: " << exitCode << ")"; onFailure(); } process->deleteLater(); }); qCDebug(LIBKLEO_LOG).nospace() << "Starting gpgconf (" << process << ") with arguments " << process->arguments().join(QLatin1Char(' ')) << " ..."; process->start(); return process; } static auto startGpgConf(const QStringList &arguments) { return startGpgConf( arguments, []() {}, []() {}); } } void Kleo::launchGpgAgent() { static QPointer process; static qint64 mSecsSinceEpochOfLastLaunch = 0; static int numberOfFailedLaunches = 0; if (Kleo::Assuan::agentIsRunning()) { qCDebug(LIBKLEO_LOG) << __func__ << ": gpg-agent is already running"; return; } if (process) { qCDebug(LIBKLEO_LOG) << __func__ << ": gpg-agent is already being launched"; return; } const auto now = QDateTime::currentMSecsSinceEpoch(); if (now - mSecsSinceEpochOfLastLaunch < 1000) { // reduce attempts to launch the agent to 1 attempt per second return; } mSecsSinceEpochOfLastLaunch = now; if (numberOfFailedLaunches > 5) { qCWarning(LIBKLEO_LOG) << __func__ << ": Launching gpg-agent failed" << numberOfFailedLaunches << "times in a row. Giving up."; return; } process = startGpgConf( {QStringLiteral("--launch"), QStringLiteral("gpg-agent")}, []() { numberOfFailedLaunches = 0; }, []() { numberOfFailedLaunches++; }); } void Kleo::killDaemons() { static QPointer process; if (process) { qCDebug(LIBKLEO_LOG) << __func__ << ": The daemons are already being shut down"; return; } process = startGpgConf({QStringLiteral("--kill"), QStringLiteral("all")}); } diff --git a/src/utils/gnupg.h b/src/utils/gnupg.h index 5bd7e1c28..e502fa70d 100644 --- a/src/utils/gnupg.h +++ b/src/utils/gnupg.h @@ -1,117 +1,118 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/gnupg.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2020-2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include "kleo_export.h" + +#include + #include #include -#include "kleo_export.h" - class QString; -#include class QByteArray; namespace Kleo { KLEO_EXPORT QString gnupgHomeDirectory(); KLEO_EXPORT QString gpgConfPath(); KLEO_EXPORT QString gpgSmPath(); KLEO_EXPORT QString gpgPath(); KLEO_EXPORT QString gpgConfListDir(const char *which); KLEO_EXPORT QString gpg4winInstallPath(); // Returns the version number. KLEO_EXPORT QString gpg4winVersionNumber(); // Returns the version number with an optional product specific prefix. KLEO_EXPORT QString gpg4winVersion(); KLEO_EXPORT bool gpg4winSignedversion(); KLEO_EXPORT QString gpg4winDescription(); KLEO_EXPORT QString gpg4winLongDescription(); KLEO_EXPORT QString gnupgInstallPath(); KLEO_EXPORT const QString &paperKeyInstallPath(); /** * Returns a list of filename globs of files in one of the whitelisted folders * to watch for changes. * \sa gnupgFolderWhitelist, Kleo::FileSystemWatcher */ KLEO_EXPORT QStringList gnupgFileWhitelist(); /** * Returns a list of absolute paths of folders to watch for changes. * \sa gnupgFileWhitelist, Kleo::FileSystemWatcher */ KLEO_EXPORT QStringList gnupgFolderWhitelist(); KLEO_EXPORT int makeGnuPGError(int code); KLEO_EXPORT bool engineIsVersion(int major, int minor, int patch, GpgME::Engine = GpgME::GpgConfEngine); /** Returns true, if GnuPG knows which keyserver to use for keyserver * operations. * Since version 2.1.19 GnuPG has a builtin default keyserver, so that this * function always returns true. For older versions of GnuPG it checks if * a keyserver has been configured. */ KLEO_EXPORT bool haveKeyserverConfigured(); /** Returns the configured keyserver or an empty string if no keyserver is * configured. * Note: Since GnuPG 2.1.19 gpg/dirmngr uses a default keyserver if no * keyserver is configured. */ KLEO_EXPORT QString keyserver(); /** Returns true, if GnuPG knows which server to use for directory service * operations for X.509 certificates. */ KLEO_EXPORT bool haveX509DirectoryServerConfigured(); /* Use gnupgUsesDeVsCompliance() or gnupgIsDeVsCompliant() instead. */ KLEO_DEPRECATED_EXPORT bool gpgComplianceP(const char *mode); /** Returns true, if compliance mode "de-vs" is configured for GnuPG. * Note: It does not check whether the used GnuPG is actually compliant. */ KLEO_EXPORT bool gnupgUsesDeVsCompliance(); /** Returns true, if compliance mode "de-vs" is configured for GnuPG and if * GnuPG passes a basic compliance check, i.e. at least libgcrypt and the used * RNG are compliant. */ KLEO_EXPORT bool gnupgIsDeVsCompliant(); KLEO_EXPORT enum GpgME::UserID::Validity keyValidity(const GpgME::Key &key); /* Convert GnuPG output to a QString with proper encoding. * Takes Gpg Quirks into account and might handle future * changes in GnuPG Output. */ KLEO_EXPORT QString stringFromGpgOutput(const QByteArray &ba); /* Check if a minimum version is there. Strings should be in the format: * 1.2.3 */ KLEO_EXPORT bool versionIsAtLeast(const char *minimum, const char *actual); /** Returns a list of component names (e.g. GnuPG, libgcrypt) followed by * version numbers. This is meant for displaying in the About dialog. */ KLEO_EXPORT QStringList backendVersionInfo(); /** Launch the GnuPG agent if it is not already running. */ KLEO_EXPORT void launchGpgAgent(); /** Shut down all GnuPG daemons. They will be restarted automatically when * needed. */ KLEO_EXPORT void killDaemons(); } diff --git a/src/utils/hex.cpp b/src/utils/hex.cpp index 824524ba4..8e39cafb1 100644 --- a/src/utils/hex.cpp +++ b/src/utils/hex.cpp @@ -1,132 +1,132 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/hex.cpp This file is part of libkleopatra SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "hex.h" -#include +#include #include #include #include using namespace Kleo; static unsigned char unhex(unsigned char ch) { if (ch >= '0' && ch <= '9') { return ch - '0'; } if (ch >= 'A' && ch <= 'F') { return ch - 'A' + 10; } if (ch >= 'a' && ch <= 'f') { return ch - 'a' + 10; } const char cch = ch; throw Kleo::Exception(gpg_error(GPG_ERR_ASS_SYNTAX), i18n("Invalid hex char '%1' in input stream.", QString::fromLatin1(&cch, 1))); } std::string Kleo::hexdecode(const std::string &in) { std::string result; result.reserve(in.size()); for (std::string::const_iterator it = in.begin(), end = in.end(); it != end; ++it) { if (*it == '%') { ++it; unsigned char ch = '\0'; if (it == end) { throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX), i18n("Premature end of hex-encoded char in input stream")); } ch |= unhex(*it) << 4; ++it; if (it == end) { throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX), i18n("Premature end of hex-encoded char in input stream")); } ch |= unhex(*it); result.push_back(ch); } else if (*it == '+') { result += ' '; } else { result.push_back(*it); } } return result; } std::string Kleo::hexencode(const std::string &in) { std::string result; result.reserve(3 * in.size()); static const char hex[] = "0123456789ABCDEF"; for (std::string::const_iterator it = in.begin(), end = in.end(); it != end; ++it) { switch (const unsigned char ch = *it) { default: if ((ch >= '!' && ch <= '~') || ch > 0xA0) { result += ch; break; } // else fall through case ' ': result += '+'; break; case '"': case '#': case '$': case '%': case '\'': case '+': case '=': result += '%'; result += hex[(ch & 0xF0) >> 4]; result += hex[(ch & 0x0F)]; break; } } return result; } std::string Kleo::hexdecode(const char *in) { if (!in) { return std::string(); } return hexdecode(std::string(in)); } std::string Kleo::hexencode(const char *in) { if (!in) { return std::string(); } return hexencode(std::string(in)); } QByteArray Kleo::hexdecode(const QByteArray &in) { if (in.isNull()) { return QByteArray(); } const std::string result = hexdecode(std::string(in.constData())); return QByteArray(result.data(), result.size()); } QByteArray Kleo::hexencode(const QByteArray &in) { if (in.isNull()) { return QByteArray(); } const std::string result = hexencode(std::string(in.constData())); return QByteArray(result.data(), result.size()); } diff --git a/src/utils/hex.h b/src/utils/hex.h index a6d457c7d..6d3ed5ffb 100644 --- a/src/utils/hex.h +++ b/src/utils/hex.h @@ -1,30 +1,30 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/hex.h This file is part of libkleopatra SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include - #include "kleo_export.h" +#include + class QByteArray; namespace Kleo { KLEO_EXPORT std::string hexencode(const char *s); KLEO_EXPORT std::string hexdecode(const char *s); KLEO_EXPORT std::string hexencode(const std::string &s); KLEO_EXPORT std::string hexdecode(const std::string &s); KLEO_EXPORT QByteArray hexencode(const QByteArray &s); KLEO_EXPORT QByteArray hexdecode(const QByteArray &s); } diff --git a/src/utils/keyhelpers.cpp b/src/utils/keyhelpers.cpp index e19a38581..4dc57e94a 100644 --- a/src/utils/keyhelpers.cpp +++ b/src/utils/keyhelpers.cpp @@ -1,62 +1,62 @@ /* utils/keyhelpers.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keyhelpers.h" -#include +#include #include using namespace Kleo; namespace { bool havePublicKeyForSignature(const GpgME::UserID::Signature &signature) { // GnuPG returns status "NoPublicKey" for missing signing keys, but also // for expired or revoked signing keys. return (signature.status() != GpgME::UserID::Signature::NoPublicKey) // || !KeyCache::instance()->findByKeyIDOrFingerprint(signature.signerKeyID()).isNull(); } auto _getMissingSignerKeyIds(const std::vector &signatures) { return std::accumulate(std::begin(signatures), std::end(signatures), std::set{}, [](auto &keyIds, const auto &signature) { if (!havePublicKeyForSignature(signature)) { keyIds.insert(QLatin1String{signature.signerKeyID()}); } return keyIds; }); } } std::set Kleo::getMissingSignerKeyIds(const std::vector &userIds) { return std::accumulate(std::begin(userIds), std::end(userIds), std::set{}, [](auto &keyIds, const auto &userID) { if (!userID.isBad()) { const auto newKeyIds = _getMissingSignerKeyIds(userID.signatures()); std::copy(std::begin(newKeyIds), std::end(newKeyIds), std::inserter(keyIds, std::end(keyIds))); } return keyIds; }); } std::set Kleo::getMissingSignerKeyIds(const std::vector &keys) { return std::accumulate(std::begin(keys), std::end(keys), std::set{}, [](auto &keyIds, const auto &key) { if (!key.isBad()) { const auto newKeyIds = getMissingSignerKeyIds(key.userIDs()); std::copy(std::begin(newKeyIds), std::end(newKeyIds), std::inserter(keyIds, std::end(keyIds))); } return keyIds; }); } diff --git a/src/utils/keyhelpers.h b/src/utils/keyhelpers.h index 12f50399b..17e9b183e 100644 --- a/src/utils/keyhelpers.h +++ b/src/utils/keyhelpers.h @@ -1,47 +1,47 @@ /* utils/keyhelpers.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2021-2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include "kleo_export.h" + #include #include #include #include -#include "kleo_export.h" - namespace GpgME { class Key; class UserID; } namespace Kleo { template QStringList getFingerprints(const KeyContainer &keys) { QStringList fingerprints; fingerprints.reserve(keys.size()); std::transform(std::begin(keys), std::end(keys), std::back_inserter(fingerprints), [](const auto &key) { return QString::fromLatin1(key.primaryFingerprint()); }); return fingerprints; } KLEO_EXPORT std::set getMissingSignerKeyIds(const std::vector &userIds); KLEO_EXPORT std::set getMissingSignerKeyIds(const std::vector &keys); } diff --git a/src/utils/qtstlhelpers.h b/src/utils/qtstlhelpers.h index 3bfe0365e..da44dd788 100644 --- a/src/utils/qtstlhelpers.h +++ b/src/utils/qtstlhelpers.h @@ -1,27 +1,27 @@ /* utils/qtstlhelpers.h This file is part of libkleopatra SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include "kleo_export.h" + #include #include #include -#include "kleo_export.h" - class QString; namespace Kleo { KLEO_EXPORT std::vector toStdStrings(const QList &list); } diff --git a/src/utils/scdaemon.h b/src/utils/scdaemon.h index c812fbd87..79a0ce509 100644 --- a/src/utils/scdaemon.h +++ b/src/utils/scdaemon.h @@ -1,38 +1,38 @@ /* utils/scdaemon.h This file is part of libkleopatra SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include "kleo_export.h" + #include #include -#include "kleo_export.h" - namespace GpgME { class Error; } namespace Kleo { /** This namespace collects higher-level functions for retrieving information * from the GnuPG smart card daemon. */ namespace SCDaemon { /** Returns the list of available smart card readers. If an error occurred, * then @p err provides details. * The returned strings are mostly useful for configuring the reader to use * via the reader-port option of scdaemon. */ KLEO_EXPORT std::vector getReaders(GpgME::Error &err); } } diff --git a/src/utils/stringutils.h b/src/utils/stringutils.h index 8cc982365..37b1ffd7a 100644 --- a/src/utils/stringutils.h +++ b/src/utils/stringutils.h @@ -1,25 +1,25 @@ /* utils/string.h This file is part of libkleopatra SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include "kleo_export.h" + #include #include -#include "kleo_export.h" - namespace Kleo { /** Splits the string @p s into substrings wherever the character @p c occurs, * and returns the list of those strings. */ KLEO_EXPORT std::vector split(const std::string &s, char c); } diff --git a/tests/gnupgviewer.h b/tests/gnupgviewer.h index 209281c18..cb19cd6bc 100644 --- a/tests/gnupgviewer.h +++ b/tests/gnupgviewer.h @@ -1,39 +1,40 @@ /* gnupgviewer.h This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include +#include #include + namespace Kleo { class GnuPGProcessBase; } -#include class GnuPGViewer : public QTextEdit { Q_OBJECT public: GnuPGViewer(QWidget *parent = nullptr); ~GnuPGViewer(); void setProcess(Kleo::GnuPGProcessBase *process); private Q_SLOTS: void slotStdout(); void slotStderr(); void slotStatus(Kleo::GnuPGProcessBase *, const QString &, const QStringList &); void slotProcessExited(int, QProcess::ExitStatus); private: Kleo::GnuPGProcessBase *mProcess = nullptr; QString mLastStdout, mLastStderr, mLastStatus; }; diff --git a/tests/test_auditlog.cpp b/tests/test_auditlog.cpp index c57f9385e..ca6e42025 100644 --- a/tests/test_auditlog.cpp +++ b/tests/test_auditlog.cpp @@ -1,78 +1,78 @@ /* test_auditlog.cpp This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-only */ +#include + +#include +#include + +#include +#include + // clang-format off const char * auditlog = "
" "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
*Data verification succeeded
Yes
*  Data available
Yes
*  Signature available
Yes
*  Parsing data succeeded
Yes
*      (data hash algorithm: SHA1)
*  Signature 0
Good
*      (#0B/CN=Email CA 2013,O=Intevation GmbH,C=DE)
*      (data hash algorithm: SHA1)
*      (attr hash algorithm: SHA1)
*    Certificate chain available
Yes
*        (#00/CN=Root CA 2010,O=Intevation GmbH,C=DE)
*        (#04/CN=Root CA 2010,O=Intevation GmbH,C=DE)
*          (/CN=Email CA 2013,O=Intevation GmbH,C=DE)
*          (/<ca@intevation.de>)
*          (/(3:uri24:http://ca.intevation.org))
*        (#0B/CN=Email CA 2013,O=Intevation GmbH,C=DE)
*          (/CN=Andre Heinecke,O=Intevation GmbH,C=DE)
*          (/<andre.heinecke@intevation.de>)
*          (/<aheinecke@intevation.de>)
*          (/<andre@heinecke.or.at>)
*    Certificate chain valid
Yes
*    Root certificate trustworthy
Yes
*    CRL/OCSP check of certificates
Not enabled
*  Included certificates
2
*      (#0B/CN=Email CA 2013,O=Intevation GmbH,C=DE)
*        (/CN=Andre Heinecke,O=Intevation GmbH,C=DE)
*        (/<andre.heinecke@intevation.de>)
*        (/<aheinecke@intevation.de>)
*        (/<andre@heinecke.or.at>)
*      (#04/CN=Root CA 2010,O=Intevation GmbH,C=DE)
*        (/CN=Email CA 2013,O=Intevation GmbH,C=DE)
*        (/<ca@intevation.de>)
*        (/(3:uri24:http://ca.intevation.org))
*Gpg-Agent usable
Yes
" "
"; // clang-format on -#include "libkleo/messagebox.h" - -#include - -#include -#include -#include - int main(int argc, char **argv) { QApplication app(argc, argv); KAboutData aboutData(QStringLiteral("test_auditlog"), i18n("Auditlog Test"), QStringLiteral("0.1")); QCommandLineParser parser; KAboutData::setApplicationData(aboutData); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); Kleo::MessageBox::auditLog(nullptr, QString::fromLatin1(auditlog), QStringLiteral("Test")); return app.exec(); } diff --git a/tests/test_keygen.h b/tests/test_keygen.h index 36b3aa5c1..4ac4f8539 100644 --- a/tests/test_keygen.h +++ b/tests/test_keygen.h @@ -1,40 +1,39 @@ /* test_keygen.h This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-only */ #pragma once -#include - #include +#include namespace GpgME { class Error; class KeyGenerationResult; } class QLineEdit; class KeyGenerator : public QDialog { Q_OBJECT public: KeyGenerator(QWidget *parent = nullptr); ~KeyGenerator() override; public Q_SLOTS: void slotStartKeyGeneration(); void slotResult(const GpgME::KeyGenerationResult &res, const QByteArray &keyData); private: void showError(const GpgME::Error &err); private: QLineEdit *mLineEdits[20]; };