Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F32914405
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
37 KB
Subscribers
None
View Options
diff --git a/src/dialogs/certifywidget.cpp b/src/dialogs/certifywidget.cpp
index da7fedcc7..fb751774f 100644
--- a/src/dialogs/certifywidget.cpp
+++ b/src/dialogs/certifywidget.cpp
@@ -1,980 +1,978 @@
/* dialogs/certifywidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2019, 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "certifywidget.h"
#include "dialogs/animatedexpander.h"
#include "view/infofield.h"
#include <utils/accessibility.h>
#include <utils/expiration.h>
#include <utils/gui-helper.h>
#include <settings.h>
#include "kleopatra_debug.h"
#include <KConfigGroup>
#include <KDateComboBox>
#include <KLocalizedString>
#include <KMessageBox>
#include <KMessageWidget>
#include <KSeparator>
#include <KSharedConfig>
#include <Libkleo/Algorithm>
#include <Libkleo/DefaultKeyFilter>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyHelpers>
#include <Libkleo/KeySelectionCombo>
#include <Libkleo/Predicates>
#include <Libkleo/TreeWidget>
#include <QGpgME/ChangeOwnerTrustJob>
#include <QGpgME/Protocol>
#include <QAction>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QLineEdit>
#include <QParallelAnimationGroup>
#include <QPropertyAnimation>
#include <QPushButton>
#include <QScrollArea>
#include <QToolButton>
#include <QVBoxLayout>
#include <gpgme++/key.h>
Q_DECLARE_METATYPE(GpgME::UserID)
using namespace Kleo;
using namespace GpgME;
static QDebug operator<<(QDebug s, const GpgME::UserID &userID)
{
return s << Formatting::prettyUserID(userID);
}
namespace
{
class SecKeyFilter : public DefaultKeyFilter
{
public:
SecKeyFilter()
: DefaultKeyFilter()
{
setRevoked(DefaultKeyFilter::NotSet);
setExpired(DefaultKeyFilter::NotSet);
setHasSecret(DefaultKeyFilter::Set);
setCanCertify(DefaultKeyFilter::Set);
setIsOpenPGP(DefaultKeyFilter::Set);
}
bool matches(const GpgME::Key &key, Kleo::KeyFilter::MatchContexts contexts) const override
{
if (!(availableMatchContexts() & contexts)) {
return false;
}
if (_detail::ByFingerprint<std::equal_to>()(key, mExcludedKey)) {
return false;
}
return DefaultKeyFilter::matches(key, contexts);
}
void setExcludedKey(const GpgME::Key &key)
{
mExcludedKey = key;
}
private:
GpgME::Key mExcludedKey;
};
auto checkBoxSize(const QCheckBox *checkBox)
{
QStyleOptionButton opt;
return checkBox->style()->sizeFromContents(QStyle::CT_CheckBox, &opt, QSize(), checkBox);
}
class TreeWidgetInternal : public TreeWidget
{
Q_OBJECT
public:
using TreeWidget::TreeWidget;
protected:
void focusInEvent(QFocusEvent *event) override
{
TreeWidget::focusInEvent(event);
// queue the invokation, so that it happens after the widget itself got focus
QMetaObject::invokeMethod(this, &TreeWidgetInternal::forceAccessibleFocusEventForCurrentItem, Qt::QueuedConnection);
}
bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) override
{
if (event && event->type() == QEvent::KeyPress) {
const auto *const keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Space || keyEvent->key() == Qt::Key_Select) {
// toggle checked state regardless of the index's column
return TreeWidget::edit(index.siblingAtColumn(0), trigger, event);
}
}
return TreeWidget::edit(index, trigger, event);
}
private:
void forceAccessibleFocusEventForCurrentItem()
{
// force Qt to send a focus event for the current item to accessibility
// tools; otherwise, the user has no idea which item is selected when the
// list gets keyboard input focus
const auto current = currentIndex();
setCurrentIndex({});
setCurrentIndex(current);
}
};
struct UserIDCheckState {
GpgME::UserID userId;
Qt::CheckState checkState;
};
}
class CertifyWidget::Private
{
public:
enum Role { UserIdRole = Qt::UserRole };
enum Mode {
SingleCertification,
BulkCertification,
};
enum TagsState {
TagsMustBeChecked,
TagsLoading,
TagsLoaded,
};
Private(CertifyWidget *qq)
: q{qq}
{
auto mainLay = new QVBoxLayout{q};
{
mInfoLabel = new QLabel{i18n("Verify the fingerprint, mark the user IDs you want to certify, "
"and select the key you want to certify the user IDs with.<br>"
"<i>Note: Only the fingerprint clearly identifies the key and its owner.</i>"),
q};
mInfoLabel->setWordWrap(true);
labelHelper.addLabel(mInfoLabel);
mainLay->addWidget(mInfoLabel);
}
mainLay->addWidget(new KSeparator{Qt::Horizontal, q});
{
auto grid = new QGridLayout;
grid->setColumnStretch(1, 1);
int row = -1;
row++;
mFprField = std::make_unique<InfoField>(i18n("Fingerprint:"), q);
grid->addWidget(mFprField->label(), row, 0);
grid->addLayout(mFprField->layout(), row, 1);
row++;
auto label = new QLabel{i18n("Certify with:"), q};
mSecKeySelect = new KeySelectionCombo{/* secretOnly= */ true, q};
mSecKeySelect->setKeyFilter(std::make_shared<SecKeyFilter>());
label->setBuddy(mSecKeySelect);
grid->addWidget(label, row, 0);
grid->addWidget(mSecKeySelect);
mainLay->addLayout(grid);
}
mMissingOwnerTrustInfo = new KMessageWidget{q};
mSetOwnerTrustAction = new QAction{q};
mSetOwnerTrustAction->setText(i18nc("@action:button", "Set Owner Trust"));
mSetOwnerTrustAction->setToolTip(i18nc("@info:tooltip",
"Click to set the trust level of the selected certification key to ultimate trust. "
"This is what you usually want to do for your own keys."));
connect(mSetOwnerTrustAction, &QAction::triggered, q, [this]() {
setOwnerTrust();
});
mMissingOwnerTrustInfo->addAction(mSetOwnerTrustAction);
mMissingOwnerTrustInfo->setVisible(false);
mainLay->addWidget(mMissingOwnerTrustInfo);
mainLay->addWidget(new KSeparator{Qt::Horizontal, q});
mBadCertificatesInfo = new KMessageWidget{q};
mBadCertificatesInfo->setMessageType(KMessageWidget::Warning);
mBadCertificatesInfo->setIcon(QIcon::fromTheme(QStringLiteral("data-warning"), QIcon::fromTheme(QStringLiteral("dialog-warning"))));
mBadCertificatesInfo->setText(i18nc("@info", "One or more certificates cannot be certified."));
mBadCertificatesInfo->setCloseButtonVisible(false);
mBadCertificatesInfo->setVisible(false);
mainLay->addWidget(mBadCertificatesInfo);
userIdListView = new TreeWidget{q};
userIdListView->setAccessibleName(i18n("User IDs"));
userIdListView->setEditTriggers(QAbstractItemView::NoEditTriggers);
userIdListView->setSelectionMode(QAbstractItemView::SingleSelection);
userIdListView->setRootIsDecorated(false);
userIdListView->setUniformRowHeights(true);
userIdListView->setAllColumnsShowFocus(false);
userIdListView->setHeaderHidden(true);
userIdListView->setHeaderLabels({i18nc("@title:column", "User ID")});
mainLay->addWidget(userIdListView, 1);
// Setup the advanced area
mAdvancedOptionsExpander = new AnimatedExpander{i18n("Advanced"), i18n("Show advanced options"), q};
mainLay->addWidget(mAdvancedOptionsExpander);
auto advLay = new QVBoxLayout;
mExportCB = new QCheckBox{q};
mExportCB->setText(i18n("Certify for everyone to see (exportable)"));
mExportCB->setToolTip(xi18nc("@info:tooltip",
"Check this option, if you want to share your certifications with others. "
"If you just want to mark certificates as certified for yourself, then you can uncheck it."));
advLay->addWidget(mExportCB);
{
auto layout = new QHBoxLayout;
mPublishCB = new QCheckBox{q};
- if (keyserver().startsWith(QLatin1StringView("ldap://"))) {
- mPublishCB->setText(i18nc("@label:checkbox", "Publish in internal directory afterwards"));
+ if (keyserver().startsWith(QLatin1StringView("ldap:")) || keyserver().startsWith(QLatin1StringView("ldaps:"))) {
+ mPublishCB->setText(i18nc("@label:checkbox", "Publish in internal directory"));
} else if (keyserver() == QLatin1StringView("none")) {
- mPublishCB->setText(i18nc("@label:checkbox", "Publish on keyserver afterwards"));
+ mPublishCB->setText(i18nc("@label:checkbox", "Publish on keyserver"));
} else {
- auto server = keyserver();
- server.remove(QRegularExpression(QStringLiteral("([a-z])+://")));
- mPublishCB->setText(i18nc("@label:checkbox", "Publish on external keyserver %1 afterwards", server));
+ mPublishCB->setText(i18nc("@label:checkbox", "Publish to %1", keyserver()));
}
mPublishCB->setToolTip(xi18nc("@info:tooltip",
- "Check this option, if you want to upload your certifications to a certificate "
+ "Check this option if you want to upload your certifications to a certificate "
"directory after successful certification."));
mPublishCB->setEnabled(mExportCB->isChecked());
layout->addSpacing(checkBoxSize(mExportCB).width());
layout->addWidget(mPublishCB);
advLay->addLayout(layout);
}
{
auto tagsLay = new QHBoxLayout;
auto label = new QLabel{i18n("Tags:"), q};
mTagsLE = new QLineEdit{q};
label->setBuddy(mTagsLE);
const auto tooltip = i18n("You can use this to add additional info to a certification.") + QStringLiteral("<br/><br/>")
+ i18n("Tags created by anyone with full certification trust "
"are shown in the keylist and can be searched.");
label->setToolTip(tooltip);
mTagsLE->setToolTip(tooltip);
tagsLay->addWidget(label);
tagsLay->addWidget(mTagsLE, 1);
advLay->addLayout(tagsLay);
}
{
auto layout = new QHBoxLayout;
mExpirationCheckBox = new QCheckBox{q};
mExpirationCheckBox->setText(i18n("Expiration:"));
mExpirationDateEdit = new KDateComboBox{q};
Kleo::setUpExpirationDateComboBox(mExpirationDateEdit, {QDate::currentDate().addDays(1), QDate{}});
mExpirationDateEdit->setDate(Kleo::defaultExpirationDate(ExpirationOnUnlimitedValidity::InternalDefaultExpiration));
mExpirationDateEdit->setEnabled(mExpirationCheckBox->isChecked());
const auto tooltip = i18n("You can use this to set an expiration date for a certification.") + QStringLiteral("<br/><br/>")
+ i18n("By setting an expiration date, you can limit the validity of "
"your certification to a certain amount of time. Once the expiration "
"date has passed, your certification is no longer valid.");
mExpirationCheckBox->setToolTip(tooltip);
mExpirationDateEdit->setToolTip(tooltip);
layout->addWidget(mExpirationCheckBox);
layout->addWidget(mExpirationDateEdit, 1);
advLay->addLayout(layout);
}
{
mTrustSignatureCB = new QCheckBox{q};
mTrustSignatureWidgets.addWidget(mTrustSignatureCB);
mTrustSignatureCB->setText(i18n("Certify as trusted introducer"));
const auto tooltip = i18n("You can use this to certify a trusted introducer for a domain.") + QStringLiteral("<br/><br/>")
+ i18n("All certificates with email addresses belonging to the domain "
"that have been certified by the trusted introducer are treated "
"as certified, i.e. a trusted introducer acts as a kind of "
"intermediate CA for a domain.");
mTrustSignatureCB->setToolTip(tooltip);
advLay->addWidget(mTrustSignatureCB);
}
{
auto layout = new QHBoxLayout;
auto label = new QLabel{i18n("Domain:"), q};
mTrustSignatureWidgets.addWidget(label);
mTrustSignatureDomainLE = new QLineEdit{q};
mTrustSignatureWidgets.addWidget(mTrustSignatureDomainLE);
mTrustSignatureDomainLE->setEnabled(mTrustSignatureCB->isChecked());
label->setBuddy(mTrustSignatureDomainLE);
layout->addSpacing(checkBoxSize(mTrustSignatureCB).width());
layout->addWidget(label);
layout->addWidget(mTrustSignatureDomainLE);
advLay->addLayout(layout);
}
mAdvancedOptionsExpander->setContentLayout(advLay);
connect(userIdListView, &QTreeWidget::itemChanged, q, [this](auto item, auto) {
onItemChanged(item);
});
connect(mExportCB, &QCheckBox::toggled, q, [this](bool on) {
mPublishCB->setEnabled(on);
});
connect(mSecKeySelect, &KeySelectionCombo::currentKeyChanged, q, [this](const GpgME::Key &) {
updateSelectedUserIds();
updateTags();
checkOwnerTrust();
Q_EMIT q->changed();
});
connect(mExpirationCheckBox, &QCheckBox::toggled, q, [this](bool checked) {
mExpirationDateEdit->setEnabled(checked);
Q_EMIT q->changed();
});
connect(mExpirationDateEdit, &KDateComboBox::dateChanged, q, &CertifyWidget::changed);
connect(mTrustSignatureCB, &QCheckBox::toggled, q, [this](bool on) {
mTrustSignatureDomainLE->setEnabled(on);
Q_EMIT q->changed();
});
connect(mTrustSignatureDomainLE, &QLineEdit::textChanged, q, &CertifyWidget::changed);
loadConfig(true);
}
~Private() = default;
void loadConfig(bool loadAll = false)
{
const KConfigGroup conf(KSharedConfig::openConfig(), QStringLiteral("CertifySettings"));
if (loadAll) {
const Settings settings;
mExpirationCheckBox->setChecked(settings.certificationValidityInDays() > 0);
if (settings.certificationValidityInDays() > 0) {
const QDate expirationDate = QDate::currentDate().addDays(settings.certificationValidityInDays());
mExpirationDateEdit->setDate(expirationDate > mExpirationDateEdit->maximumDate() //
? mExpirationDateEdit->maximumDate() //
: expirationDate);
}
mSecKeySelect->setDefaultKey(conf.readEntry("LastKey", QString()));
}
switch (mMode) {
case SingleCertification: {
mExportCB->setChecked(conf.readEntry("ExportCheckState", false));
mPublishCB->setChecked(conf.readEntry("PublishCheckState", false));
mAdvancedOptionsExpander->setExpanded(conf.readEntry("AdvancedOptionsExpanded", false));
break;
}
case BulkCertification: {
mExportCB->setChecked(conf.readEntry("BulkExportCheckState", true));
mPublishCB->setChecked(conf.readEntry("BulkPublishCheckState", false));
mAdvancedOptionsExpander->setExpanded(conf.readEntry("BulkAdvancedOptionsExpanded", true));
break;
}
}
}
void saveConfig()
{
KConfigGroup conf{KSharedConfig::openConfig(), QLatin1StringView("CertifySettings")};
if (!secKey().isNull()) {
conf.writeEntry("LastKey", secKey().primaryFingerprint());
}
switch (mMode) {
case SingleCertification: {
conf.writeEntry("ExportCheckState", mExportCB->isChecked());
conf.writeEntry("PublishCheckState", mPublishCB->isChecked());
conf.writeEntry("AdvancedOptionsExpanded", mAdvancedOptionsExpander->isExpanded());
break;
}
case BulkCertification: {
conf.writeEntry("BulkExportCheckState", mExportCB->isChecked());
conf.writeEntry("BulkPublishCheckState", mPublishCB->isChecked());
conf.writeEntry("BulkAdvancedOptionsExpanded", mAdvancedOptionsExpander->isExpanded());
break;
}
}
conf.sync();
}
void setMode(Mode mode)
{
mMode = mode;
switch (mMode) {
case SingleCertification:
break;
case BulkCertification: {
mInfoLabel->setText(i18nc("@info",
"Verify the fingerprints, mark the user IDs you want to certify, "
"and select the certificate you want to certify the user IDs with.<br>"
"<i>Note: Only the fingerprints clearly identify the certificate and its owner.</i>"));
mFprField->setVisible(false);
mTrustSignatureWidgets.setVisible(false);
break;
}
}
loadConfig();
}
void setUpUserIdList(const std::vector<GpgME::UserID> &uids = {})
{
userIdListView->clear();
if (mMode == SingleCertification) {
userIdListView->setColumnCount(1);
userIdListView->setHeaderHidden(true);
// set header labels for accessibility tools to overwrite the default "1"
userIdListView->setHeaderLabels({i18nc("@title:column", "User ID")});
for (const auto &uid : uids) {
if (uid.isInvalid() || Kleo::isRevokedOrExpired(uid)) {
// Skip user IDs that cannot really be certified.
continue;
}
auto item = new QTreeWidgetItem;
item->setData(0, UserIdRole, QVariant::fromValue(uid));
item->setData(0, Qt::DisplayRole, Kleo::Formatting::prettyUserID(uid));
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
item->setCheckState(0, Qt::Checked);
userIdListView->addTopLevelItem(item);
}
} else {
const QStringList headers = {i18nc("@title:column", "User ID"), i18nc("@title:column", "Fingerprint")};
userIdListView->setColumnCount(headers.count());
userIdListView->setHeaderHidden(false);
userIdListView->setHeaderLabels(headers);
for (const auto &key : mKeys) {
const auto &uid = key.userID(0);
auto item = new QTreeWidgetItem;
item->setData(0, UserIdRole, QVariant::fromValue(uid));
item->setData(0, Qt::DisplayRole, Kleo::Formatting::prettyUserID(uid));
item->setData(1, Qt::DisplayRole, Kleo::Formatting::prettyID(key.primaryFingerprint()));
item->setData(1, Qt::AccessibleTextRole, Kleo::Formatting::accessibleHexID(key.primaryFingerprint()));
if ((key.protocol() != OpenPGP) || uid.isInvalid() || Kleo::isRevokedOrExpired(uid)) {
item->setFlags(Qt::NoItemFlags);
item->setCheckState(0, Qt::Unchecked);
if (key.protocol() == CMS) {
item->setData(0, Qt::ToolTipRole, i18nc("@info:tooltip", "S/MIME certificates cannot be certified."));
item->setData(1, Qt::ToolTipRole, i18nc("@info:tooltip", "S/MIME certificates cannot be certified."));
} else {
item->setData(0, Qt::ToolTipRole, i18nc("@info:tooltip", "Expired or revoked certificates cannot be certified."));
item->setData(1, Qt::ToolTipRole, i18nc("@info:tooltip", "Expired or revoked certificates cannot be certified."));
}
} else {
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
item->setCheckState(0, Qt::Checked);
}
userIdListView->addTopLevelItem(item);
}
userIdListView->sortItems(0, Qt::AscendingOrder);
userIdListView->resizeColumnToContents(0);
userIdListView->resizeColumnToContents(1);
}
}
void updateSelectedUserIds()
{
if (mMode == SingleCertification) {
return;
}
if (userIdListView->topLevelItemCount() == 0) {
return;
}
// restore check state of primary user ID of previous certification key
if (!mCertificationKey.isNull()) {
for (int i = 0, end = userIdListView->topLevelItemCount(); i < end; ++i) {
const auto uidItem = userIdListView->topLevelItem(i);
const auto itemUserId = getUserId(uidItem);
if (userIDBelongsToKey(itemUserId, mCertificationKey)) {
uidItem->setCheckState(0, mCertificationKeyUserIDCheckState);
uidItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
break; // we only show the primary user IDs
}
}
}
mCertificationKey = mSecKeySelect->currentKey();
// save and unset check state of primary user ID of current certification key
if (!mCertificationKey.isNull()) {
for (int i = 0, end = userIdListView->topLevelItemCount(); i < end; ++i) {
const auto uidItem = userIdListView->topLevelItem(i);
const auto itemUserId = getUserId(uidItem);
if (userIDBelongsToKey(itemUserId, mCertificationKey)) {
mCertificationKeyUserIDCheckState = uidItem->checkState(0);
if (mCertificationKeyUserIDCheckState) {
uidItem->setCheckState(0, Qt::Unchecked);
}
uidItem->setFlags(Qt::ItemIsSelectable);
break; // we only show the primary user IDs
}
}
}
}
void updateTags()
{
struct ItemAndRemark {
QTreeWidgetItem *item;
QString remark;
};
if (mTagsState != TagsLoaded) {
return;
}
if (mTagsLE->isModified()) {
return;
}
GpgME::Key remarkKey = mSecKeySelect->currentKey();
if (!remarkKey.isNull()) {
std::vector<ItemAndRemark> itemsAndRemarks;
// first choose the remark we want to prefill the Tags field with
QString remark;
for (int i = 0, end = userIdListView->topLevelItemCount(); i < end; ++i) {
const auto item = userIdListView->topLevelItem(i);
if (item->isDisabled()) {
continue;
}
const auto uid = getUserId(item);
GpgME::Error err;
const char *c_remark = uid.remark(remarkKey, err);
const QString itemRemark = (!err && c_remark) ? QString::fromUtf8(c_remark) : QString{};
if (!itemRemark.isEmpty() && (itemRemark != remark)) {
if (!remark.isEmpty()) {
qCDebug(KLEOPATRA_LOG) << "Different remarks on user IDs. Taking last.";
}
remark = itemRemark;
}
itemsAndRemarks.push_back({item, itemRemark});
}
// then select the user IDs with the chosen remark; this prevents overwriting existing
// different remarks on the other user IDs (as long as the user doesn't select any of
// the unselected user IDs with a different remark)
if (!remark.isEmpty()) {
for (const auto &[item, itemRemark] : itemsAndRemarks) {
item->setCheckState(0, itemRemark == remark ? Qt::Checked : Qt::Unchecked);
}
}
mTagsLE->setText(remark);
}
}
void updateTrustSignatureDomain()
{
if (mMode == SingleCertification) {
if (mTrustSignatureDomainLE->text().isEmpty() && certificate().numUserIDs() == 1) {
// try to guess the domain to use for the trust signature
const auto address = certificate().userID(0).addrSpec();
const auto atPos = address.find('@');
if (atPos != std::string::npos) {
const auto domain = address.substr(atPos + 1);
mTrustSignatureDomainLE->setText(QString::fromUtf8(domain.c_str(), domain.size()));
}
}
}
}
void loadAllTags()
{
const auto keyWithoutTags = std::find_if(mKeys.cbegin(), mKeys.cend(), [](const auto &key) {
return (key.protocol() == GpgME::OpenPGP) && !(key.keyListMode() & GpgME::SignatureNotations);
});
const auto indexOfKeyWithoutTags = std::distance(mKeys.cbegin(), keyWithoutTags);
if (indexOfKeyWithoutTags < signed(mKeys.size())) {
auto loadTags = [this, indexOfKeyWithoutTags]() {
Q_ASSERT(indexOfKeyWithoutTags < signed(mKeys.size()));
// call update() on the reference to the vector element because it swaps key with the updated key
mKeys[indexOfKeyWithoutTags].update();
loadAllTags();
};
QMetaObject::invokeMethod(q, loadTags, Qt::QueuedConnection);
return;
}
mTagsState = TagsLoaded;
QMetaObject::invokeMethod(
q,
[this]() {
setUpWidget();
},
Qt::QueuedConnection);
}
bool ensureTagsLoaded()
{
Q_ASSERT(mTagsState != TagsLoading);
if (mTagsState == TagsLoaded) {
return true;
}
const auto allTagsAreLoaded = std::ranges::all_of(mKeys, [](const auto &key) {
return (key.protocol() != GpgME::OpenPGP) || (key.keyListMode() & GpgME::SignatureNotations);
});
if (allTagsAreLoaded) {
mTagsState = TagsLoaded;
} else {
mTagsState = TagsLoading;
QMetaObject::invokeMethod(
q,
[this]() {
loadAllTags();
},
Qt::QueuedConnection);
}
return mTagsState == TagsLoaded;
}
void setUpWidget()
{
if (!ensureTagsLoaded()) {
return;
}
if (mMode == SingleCertification) {
const auto key = certificate();
mFprField->setValue(QStringLiteral("<b>") + Formatting::prettyID(key.primaryFingerprint()) + QStringLiteral("</b>"),
Formatting::accessibleHexID(key.primaryFingerprint()));
setUpUserIdList(mUserIds.empty() ? key.userIDs() : mUserIds);
auto keyFilter = std::make_shared<SecKeyFilter>();
keyFilter->setExcludedKey(key);
mSecKeySelect->setKeyFilter(keyFilter);
updateTrustSignatureDomain();
} else {
// check for certificates that cannot be certified
const auto haveBadCertificates = std::ranges::any_of(mKeys, [](const auto &key) {
const auto &uid = key.userID(0);
return (key.protocol() != OpenPGP) || uid.isInvalid() || Kleo::isRevokedOrExpired(uid);
});
if (haveBadCertificates) {
mBadCertificatesInfo->animatedShow();
}
setUpUserIdList();
}
updateTags();
updateSelectedUserIds();
Q_EMIT q->changed();
}
GpgME::Key certificate() const
{
Q_ASSERT(mMode == SingleCertification);
return !mKeys.empty() ? mKeys.front() : Key{};
}
void setCertificates(const std::vector<GpgME::Key> &keys, const std::vector<GpgME::UserID> &uids)
{
mKeys = keys;
mUserIds = uids;
mTagsState = TagsMustBeChecked;
setUpWidget();
}
std::vector<GpgME::Key> certificates() const
{
Q_ASSERT(mMode != SingleCertification);
return mKeys;
}
GpgME::Key secKey() const
{
return mSecKeySelect->currentKey();
}
GpgME::UserID getUserId(const QTreeWidgetItem *item) const
{
return item ? item->data(0, UserIdRole).value<UserID>() : UserID{};
}
void selectUserIDs(const std::vector<GpgME::UserID> &uids)
{
for (int i = 0, end = userIdListView->topLevelItemCount(); i < end; ++i) {
const auto uidItem = userIdListView->topLevelItem(i);
const auto itemUserId = getUserId(uidItem);
const bool userIdIsInList = std::ranges::any_of(uids, [itemUserId](const auto &uid) {
return Kleo::userIDsAreEqual(itemUserId, uid);
});
uidItem->setCheckState(0, userIdIsInList ? Qt::Checked : Qt::Unchecked);
}
}
std::vector<GpgME::UserID> selectedUserIDs() const
{
std::vector<GpgME::UserID> userIds;
userIds.reserve(userIdListView->topLevelItemCount());
for (int i = 0, end = userIdListView->topLevelItemCount(); i < end; ++i) {
const auto *const uidItem = userIdListView->topLevelItem(i);
if (uidItem->checkState(0) == Qt::Checked) {
userIds.push_back(getUserId(uidItem));
}
}
qCDebug(KLEOPATRA_LOG) << "Checked user IDs:" << userIds;
return userIds;
}
bool exportableSelected() const
{
return mExportCB->isChecked();
}
bool publishSelected() const
{
return mPublishCB->isChecked();
}
QString tags() const
{
return mTagsLE->text().trimmed();
}
bool isValid() const
{
static const QRegularExpression domainNameRegExp{QStringLiteral(R"(^\s*((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}\s*$)"),
QRegularExpression::CaseInsensitiveOption};
if (mTagsState != TagsLoaded) {
return false;
}
// do not accept null keys
if (mKeys.empty() || mSecKeySelect->currentKey().isNull()) {
return false;
}
// do not accept empty list of user IDs
const auto userIds = selectedUserIDs();
if (userIds.empty()) {
return false;
}
// do not accept if any of the selected user IDs belongs to the certification key
const auto certificationKey = mSecKeySelect->currentKey();
const auto userIdToCertifyBelongsToCertificationKey = std::any_of(userIds.cbegin(), userIds.cend(), [certificationKey](const auto &userId) {
return Kleo::userIDBelongsToKey(userId, certificationKey);
});
if (userIdToCertifyBelongsToCertificationKey) {
return false;
}
if (mExpirationCheckBox->isChecked() && !mExpirationDateEdit->isValid()) {
return false;
}
if (mTrustSignatureCB->isChecked() && !domainNameRegExp.match(mTrustSignatureDomainLE->text()).hasMatch()) {
return false;
}
return true;
}
void checkOwnerTrust()
{
const auto secretKey = secKey();
if (secretKey.ownerTrust() != GpgME::Key::Ultimate) {
mMissingOwnerTrustInfo->setMessageType(KMessageWidget::Information);
mMissingOwnerTrustInfo->setIcon(QIcon::fromTheme(QStringLiteral("question")));
mMissingOwnerTrustInfo->setText(i18n("Is this your own key?"));
mSetOwnerTrustAction->setEnabled(true);
mMissingOwnerTrustInfo->animatedShow();
} else {
mMissingOwnerTrustInfo->animatedHide();
}
}
void setOwnerTrust()
{
mSetOwnerTrustAction->setEnabled(false);
QGpgME::ChangeOwnerTrustJob *const j = QGpgME::openpgp()->changeOwnerTrustJob();
connect(j, &QGpgME::ChangeOwnerTrustJob::result, q, [this](const GpgME::Error &err) {
if (err) {
KMessageBox::error(q,
i18n("<p>Changing the certification trust of the key <b>%1</b> failed:</p><p>%2</p>",
Formatting::formatForComboBox(secKey()),
Formatting::errorAsString(err)),
i18nc("@title:window", "Certification Trust Change Failed"));
}
if (err || err.isCanceled()) {
mSetOwnerTrustAction->setEnabled(true);
} else {
mMissingOwnerTrustInfo->setMessageType(KMessageWidget::Positive);
mMissingOwnerTrustInfo->setIcon(QIcon::fromTheme(QStringLiteral("checkmark")));
mMissingOwnerTrustInfo->setText(i18n("Owner trust set successfully."));
}
});
j->start(secKey(), GpgME::Key::Ultimate);
}
void onItemChanged(QTreeWidgetItem *item)
{
Q_EMIT q->changed();
#ifndef QT_NO_ACCESSIBILITY
if (item) {
// assume that the checked state changed
QAccessible::State st;
st.checked = true;
QAccessibleStateChangeEvent e(userIdListView, st);
e.setChild(userIdListView->indexOfTopLevelItem(item));
QAccessible::updateAccessibility(&e);
}
#endif
}
public:
CertifyWidget *const q;
QLabel *mInfoLabel = nullptr;
std::unique_ptr<InfoField> mFprField;
KeySelectionCombo *mSecKeySelect = nullptr;
KMessageWidget *mMissingOwnerTrustInfo = nullptr;
KMessageWidget *mBadCertificatesInfo = nullptr;
TreeWidget *userIdListView = nullptr;
AnimatedExpander *mAdvancedOptionsExpander = nullptr;
QCheckBox *mExportCB = nullptr;
QCheckBox *mPublishCB = nullptr;
QLineEdit *mTagsLE = nullptr;
BulkStateChanger mTrustSignatureWidgets;
QCheckBox *mTrustSignatureCB = nullptr;
QLineEdit *mTrustSignatureDomainLE = nullptr;
QCheckBox *mExpirationCheckBox = nullptr;
KDateComboBox *mExpirationDateEdit = nullptr;
QAction *mSetOwnerTrustAction = nullptr;
LabelHelper labelHelper;
Mode mMode = SingleCertification;
std::vector<GpgME::Key> mKeys;
std::vector<GpgME::UserID> mUserIds;
TagsState mTagsState = TagsMustBeChecked;
GpgME::Key mCertificationKey;
Qt::CheckState mCertificationKeyUserIDCheckState;
};
CertifyWidget::CertifyWidget(QWidget *parent)
: QWidget{parent}
, d{std::make_unique<Private>(this)}
{
}
Kleo::CertifyWidget::~CertifyWidget() = default;
void CertifyWidget::setCertificate(const GpgME::Key &key, const std::vector<GpgME::UserID> &uids)
{
Q_ASSERT(!key.isNull());
d->setMode(Private::SingleCertification);
d->setCertificates({key}, uids);
}
GpgME::Key CertifyWidget::certificate() const
{
return d->certificate();
}
void CertifyWidget::setCertificates(const std::vector<GpgME::Key> &keys)
{
d->setMode(Private::BulkCertification);
d->setCertificates(keys, {});
}
std::vector<GpgME::Key> CertifyWidget::certificates() const
{
return d->certificates();
}
void CertifyWidget::selectUserIDs(const std::vector<GpgME::UserID> &uids)
{
d->selectUserIDs(uids);
}
std::vector<GpgME::UserID> CertifyWidget::selectedUserIDs() const
{
return d->selectedUserIDs();
}
GpgME::Key CertifyWidget::secKey() const
{
return d->secKey();
}
bool CertifyWidget::exportableSelected() const
{
return d->exportableSelected();
}
QString CertifyWidget::tags() const
{
return d->tags();
}
bool CertifyWidget::publishSelected() const
{
return d->publishSelected();
}
bool CertifyWidget::trustSignatureSelected() const
{
return d->mTrustSignatureCB->isChecked();
}
QString CertifyWidget::trustSignatureDomain() const
{
return d->mTrustSignatureDomainLE->text().trimmed();
}
QDate CertifyWidget::expirationDate() const
{
return d->mExpirationCheckBox->isChecked() ? d->mExpirationDateEdit->date() : QDate{};
}
bool CertifyWidget::isValid() const
{
return d->isValid();
}
void CertifyWidget::saveState() const
{
d->saveConfig();
}
#include "certifywidget.moc"
#include "moc_certifywidget.cpp"
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Nov 17, 9:30 PM (1 h, 5 s)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
a6/6a/cbe3584125ba6d717961c0fdee1b
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment