diff --git a/src/dialogs/certifycertificatedialog.cpp b/src/dialogs/certifycertificatedialog.cpp index cb9d16842..a657fb740 100644 --- a/src/dialogs/certifycertificatedialog.cpp +++ b/src/dialogs/certifycertificatedialog.cpp @@ -1,150 +1,151 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/signcertificatedialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2019 g10code GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "kleopatra_debug.h" #include "certifycertificatedialog.h" #include "certifywidget.h" #include #include #include #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; CertifyCertificateDialog::CertifyCertificateDialog(QWidget *p, Qt::WindowFlags f) : QDialog(p, f) { setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); // Setup GUI auto mainLay = new QVBoxLayout(this); mCertWidget = new CertifyWidget(this); mainLay->addWidget(mCertWidget); - auto buttonBox = new QDialogButtonBox(); + auto buttonBox = new QDialogButtonBox{this}; buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); - KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok()); + const auto okButton = buttonBox->button(QDialogButtonBox::Ok); + KGuiItem::assign(okButton, KStandardGuiItem::ok()); + okButton->setText(i18n("Certify")); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel()); + connect(buttonBox, &QDialogButtonBox::accepted, this, &CertifyCertificateDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::close); - buttonBox->button(QDialogButtonBox::Ok)->setText(i18n("Certify")); - connect(buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, - this, [this] () { - KConfigGroup conf(KSharedConfig::openConfig(), "CertifySettings"); - const auto lastKey = mCertWidget->secKey(); - // Do not accept if the keys are the same. - if (!lastKey.isNull() && !mCertWidget->target().isNull() && - !strcmp(lastKey.primaryFingerprint(), - mCertWidget->target().primaryFingerprint())) { - KMessageBox::error(this, i18n("You cannot certify using the same key."), - i18n("Invalid Selection"), KMessageBox::Notify); - return; - } - - if (!lastKey.isNull()) { - conf.writeEntry("LastKey", lastKey.primaryFingerprint()); - } - conf.writeEntry("ExportCheckState", mCertWidget->exportableSelected()); - conf.writeEntry("PublishCheckState", mCertWidget->publishSelected()); - accept(); - }); - connect(buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, - this, [this] () { - close(); + mainLay->addWidget(buttonBox); + + okButton->setEnabled(mCertWidget->isValid()); + connect(mCertWidget, &CertifyWidget::changed, this, [this, okButton] () { + okButton->setEnabled(mCertWidget->isValid()); }); - mainLay->addWidget(buttonBox); KConfigGroup cfgGroup(KSharedConfig::openStateConfig(), "CertifyDialog"); const QByteArray geom = cfgGroup.readEntry("geometry", QByteArray()); if (!geom.isEmpty()) { restoreGeometry(geom); return; } resize(QSize(640, 480)); } CertifyCertificateDialog::~CertifyCertificateDialog() { KConfigGroup cfgGroup(KSharedConfig::openStateConfig(), "CertifyDialog"); cfgGroup.writeEntry("geometry", saveGeometry()); cfgGroup.sync(); } void CertifyCertificateDialog::setCertificateToCertify(const Key &key) { setWindowTitle(i18nc("@title:window arg is name, email of certificate holder", "Certify Certificate: %1", Formatting::prettyName(key))); mCertWidget->setTarget(key); } bool CertifyCertificateDialog::exportableCertificationSelected() const { return mCertWidget->exportableSelected(); } bool CertifyCertificateDialog::trustCertificationSelected() const { return false; } bool CertifyCertificateDialog::nonRevocableCertificationSelected() const { return false; } Key CertifyCertificateDialog::selectedSecretKey() const { return mCertWidget->secKey(); } bool CertifyCertificateDialog::sendToServer() const { return mCertWidget->publishSelected(); } unsigned int CertifyCertificateDialog::selectedCheckLevel() const { //PENDING #ifdef KLEO_SIGN_KEY_CERTLEVEL_SUPPORT return d->selectCheckLevelPage->checkLevel(); #endif return 0; } void CertifyCertificateDialog::setSelectedUserIDs(const std::vector &uids) { mCertWidget->selectUserIDs(uids); } std::vector CertifyCertificateDialog::selectedUserIDs() const { return mCertWidget->selectedUserIDs(); } QString CertifyCertificateDialog::tags() const { return mCertWidget->tags(); } + +void CertifyCertificateDialog::accept() +{ + if (!mCertWidget->isValid()) { + return; + } + + KConfigGroup conf(KSharedConfig::openConfig(), "CertifySettings"); + const auto lastKey = mCertWidget->secKey(); + if (!lastKey.isNull()) { + conf.writeEntry("LastKey", lastKey.primaryFingerprint()); + } + conf.writeEntry("ExportCheckState", mCertWidget->exportableSelected()); + conf.writeEntry("PublishCheckState", mCertWidget->publishSelected()); + + QDialog::accept(); +} diff --git a/src/dialogs/certifycertificatedialog.h b/src/dialogs/certifycertificatedialog.h index e1157c7ca..5449cf0d3 100644 --- a/src/dialogs/certifycertificatedialog.h +++ b/src/dialogs/certifycertificatedialog.h @@ -1,57 +1,60 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/signcertificatedialog.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 #include #include namespace Kleo { class CertifyWidget; class CertifyCertificateDialog : public QDialog { Q_OBJECT public: explicit CertifyCertificateDialog(QWidget *parent = nullptr, Qt::WindowFlags f = {}); ~CertifyCertificateDialog(); bool exportableCertificationSelected() const; bool trustCertificationSelected() const; bool nonRevocableCertificationSelected() const; void setSelectedUserIDs(const std::vector &uids); std::vector selectedUserIDs() const; GpgME::Key selectedSecretKey() const; bool sendToServer() const; unsigned int selectedCheckLevel() const; void setCertificateToCertify(const GpgME::Key &key); QString tags() const; +public Q_SLOTS: + void accept() override; + private: CertifyWidget *mCertWidget; }; } diff --git a/src/dialogs/certifywidget.cpp b/src/dialogs/certifywidget.cpp index d5e691087..0d5c613ea 100644 --- a/src/dialogs/certifywidget.cpp +++ b/src/dialogs/certifywidget.cpp @@ -1,467 +1,490 @@ /* dialogs/certifywidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2019 g 10code GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "certifywidget.h" #include "kleopatra_debug.h" #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if GPGMEPP_VERSION >= 0x10E00 // 1.14.0 # define GPGME_HAS_REMARKS #endif using namespace Kleo; namespace { // Maybe move this in its own file // based on code from StackOverflow class AnimatedExpander: public QWidget { Q_OBJECT public: explicit AnimatedExpander(const QString &title = QString(), const int animationDuration = 300, QWidget *parent = nullptr); void setContentLayout(QLayout *contentLayout); private: QGridLayout mainLayout; QToolButton toggleButton; QFrame headerLine; QParallelAnimationGroup toggleAnimation; QScrollArea contentArea; int animationDuration{300}; }; AnimatedExpander::AnimatedExpander(const QString &title, const int animationDuration, QWidget *parent): QWidget(parent), animationDuration(animationDuration) { toggleButton.setStyleSheet(QStringLiteral("QToolButton { border: none; }")); toggleButton.setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toggleButton.setArrowType(Qt::ArrowType::RightArrow); toggleButton.setText(title); toggleButton.setCheckable(true); toggleButton.setChecked(false); headerLine.setFrameShape(QFrame::HLine); headerLine.setFrameShadow(QFrame::Sunken); headerLine.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); contentArea.setStyleSheet(QStringLiteral("QScrollArea { border: none; }")); contentArea.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); // start out collapsed contentArea.setMaximumHeight(0); contentArea.setMinimumHeight(0); // let the entire widget grow and shrink with its content toggleAnimation.addAnimation(new QPropertyAnimation(this, "minimumHeight")); toggleAnimation.addAnimation(new QPropertyAnimation(this, "maximumHeight")); toggleAnimation.addAnimation(new QPropertyAnimation(&contentArea, "maximumHeight")); mainLayout.setVerticalSpacing(0); mainLayout.setContentsMargins(0, 0, 0, 0); int row = 0; mainLayout.addWidget(&toggleButton, row, 0, 1, 1, Qt::AlignLeft); mainLayout.addWidget(&headerLine, row++, 2, 1, 1); mainLayout.addWidget(&contentArea, row, 0, 1, 3); setLayout(&mainLayout); QObject::connect(&toggleButton, &QToolButton::clicked, [this](const bool checked) { toggleButton.setArrowType(checked ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow); toggleAnimation.setDirection(checked ? QAbstractAnimation::Forward : QAbstractAnimation::Backward); toggleAnimation.start(); }); } void AnimatedExpander::setContentLayout(QLayout *contentLayout) { delete contentArea.layout(); contentArea.setLayout(contentLayout); const auto collapsedHeight = sizeHint().height() - contentArea.maximumHeight(); auto contentHeight = contentLayout->sizeHint().height(); for (int i = 0; i < toggleAnimation.animationCount() - 1; ++i) { auto expanderAnimation = static_cast(toggleAnimation.animationAt(i)); expanderAnimation->setDuration(animationDuration); expanderAnimation->setStartValue(collapsedHeight); expanderAnimation->setEndValue(collapsedHeight + contentHeight); } auto contentAnimation = static_cast(toggleAnimation.animationAt(toggleAnimation.animationCount() - 1)); contentAnimation->setDuration(animationDuration); contentAnimation->setStartValue(0); contentAnimation->setEndValue(contentHeight); } class SecKeyFilter: public DefaultKeyFilter { public: SecKeyFilter() : DefaultKeyFilter() { setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setHasSecret(DefaultKeyFilter::Set); setCanCertify(DefaultKeyFilter::Set); setIsOpenPGP(DefaultKeyFilter::Set); } }; class UserIDModel : public QStandardItemModel { Q_OBJECT public: enum Role { UserIDIndex = Qt::UserRole }; explicit UserIDModel(QObject *parent = nullptr) : QStandardItemModel(parent) {} GpgME::Key certificateToCertify() const { return m_key; } void setKey(const GpgME::Key &key) { m_key = key; clear(); const std::vector ids = key.userIDs(); int i = 0; for (const auto &uid: key.userIDs()) { if (uid.isRevoked() || uid.isInvalid()) { // Skip user ID's that cannot really be certified. i++; continue; } auto const item = new QStandardItem; item->setText(Formatting::prettyUserID(uid)); item->setData(i, UserIDIndex); item->setCheckable(true); item->setEditable(false); item->setCheckState(Qt::Checked); appendRow(item); i++; } } void setCheckedUserIDs(const std::vector &uids) { std::vector sorted = uids; std::sort(sorted.begin(), sorted.end()); for (int i = 0, end = rowCount(); i != end; ++i) { item(i)->setCheckState(std::binary_search(sorted.begin(), sorted.end(), i) ? Qt::Checked : Qt::Unchecked); } } std::vector checkedUserIDs() const { std::vector ids; for (int i = 0; i < rowCount(); ++i) { if (item(i)->checkState() == Qt::Checked) { ids.push_back(item(i)->data(UserIDIndex).toUInt()); } } qCDebug(KLEOPATRA_LOG) << "Checked uids are: " << ids; return ids; } private: GpgME::Key m_key; }; static bool uidEqual(const GpgME::UserID &lhs, const GpgME::UserID &rhs) { return qstrcmp(lhs.parent().primaryFingerprint(), rhs.parent().primaryFingerprint()) == 0 && qstrcmp(lhs.id(), rhs.id()) == 0; } -} // anonymous namespace - +} -// Use of pimpl as this might be moved to libkleo class CertifyWidget::Private { public: - Private(CertifyWidget *qq) : q(qq), - mFprLabel(new QLabel) + Private(CertifyWidget *qq) + : q{qq} + , mFprLabel{new QLabel{q}} { auto mainLay = new QVBoxLayout(q); mainLay->addWidget(mFprLabel); auto secKeyLay = new QHBoxLayout; secKeyLay->addWidget(new QLabel(i18n("Certify with:"))); mSecKeySelect = new KeySelectionCombo(true); mSecKeySelect->setKeyFilter(std::shared_ptr(new SecKeyFilter())); secKeyLay->addWidget(mSecKeySelect, 1); mainLay->addLayout(secKeyLay); auto splitLine = new QFrame; splitLine->setFrameShape(QFrame::HLine); splitLine->setFrameShadow(QFrame::Sunken); splitLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); mainLay->addWidget(splitLine); auto listView = new QListView; listView->setModel(&mUserIDModel); mainLay->addWidget(listView, 1); // Setup the advanced area auto expander = new AnimatedExpander(i18n("Advanced")); mainLay->addWidget(expander); auto advLay = new QVBoxLayout; - mExportCB = new QCheckBox(i18n("Certify for everyone to see. (exportable)")); - mPublishCB = new QCheckBox(i18n("Publish on keyserver afterwards.")); + mExportCB = new QCheckBox(i18n("Certify for everyone to see (exportable)")); + mPublishCB = new QCheckBox(i18n("Publish on keyserver afterwards")); auto publishLay = new QHBoxLayout; publishLay->addSpacing(20); publishLay->addWidget(mPublishCB); mTagsLE = new QLineEdit; mTagsLE->setPlaceholderText(i18n("Tags")); auto infoBtn = new QPushButton; infoBtn->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual"))); infoBtn->setFlat(true); - connect(infoBtn, &QPushButton::clicked, q, [this, infoBtn] () { + connect(infoBtn, &QPushButton::clicked, q, [infoBtn] () { const QString msg = i18n("You can use this to add additional info to a " "certification.") + QStringLiteral("

") + i18n("Tags created by anyone with full certification trust " "are shown in the keylist and can be searched."); QToolTip::showText(infoBtn->mapToGlobal(QPoint()) + QPoint(infoBtn->width(), 0), msg, infoBtn, QRect(), 30000); }); auto tagsLay = new QHBoxLayout; tagsLay->addWidget(infoBtn); tagsLay->addWidget(mTagsLE); advLay->addWidget(mExportCB); advLay->addLayout(publishLay); advLay->addLayout(tagsLay); #ifndef GPGME_HAS_REMARKS // Hide it if we do not have remark support mTagsLE->setVisible(false); infoBtn->setVisible(false); #endif expander->setContentLayout(advLay); mPublishCB->setEnabled(false); + connect(&mUserIDModel, &QStandardItemModel::itemChanged, q, &CertifyWidget::changed); + connect(mExportCB, &QCheckBox::toggled, [this] (bool on) { mPublishCB->setEnabled(on); }); connect(mSecKeySelect, &KeySelectionCombo::currentKeyChanged, [this] (const GpgME::Key &) { #ifdef GPGME_HAS_REMARKS updateTags(); #endif + Q_EMIT q->changed(); }); loadConfig(); } - ~Private() - { - } + ~Private() = default; void loadConfig() { const KConfigGroup conf(KSharedConfig::openConfig(), "CertifySettings"); mSecKeySelect->setDefaultKey(conf.readEntry("LastKey", QString())); mExportCB->setChecked(conf.readEntry("ExportCheckState", false)); mPublishCB->setChecked(conf.readEntry("PublishCheckState", false)); } void updateTags() { if (mTagsLE->isModified()) { return; } #ifdef GPGME_HAS_REMARKS GpgME::Key remarkKey = mSecKeySelect->currentKey(); if (!remarkKey.isNull()) { std::vector uidsWithRemark; QString remark; for (const auto &uid: mTarget.userIDs()) { GpgME::Error err; const char *c_remark = uid.remark(remarkKey, err); if (c_remark) { const QString candidate = QString::fromUtf8(c_remark); if (candidate != remark) { qCDebug(KLEOPATRA_LOG) << "Different remarks on user ids. Taking last."; remark = candidate; uidsWithRemark.clear(); } uidsWithRemark.push_back(uid); } } // Only select the user ids with the correct remark if (!remark.isEmpty()) { selectUserIDs(uidsWithRemark); } mTagsLE->setText(remark); } #endif } void setTarget(const GpgME::Key &key) { mFprLabel->setText(i18n("Fingerprint: %1", Formatting::prettyID(key.primaryFingerprint())) + QStringLiteral("
") + i18n("Only the fingerprint clearly identifies the key and its owner.")); mUserIDModel.setKey(key); mTarget = key; updateTags(); } GpgME::Key secKey() const { return mSecKeySelect->currentKey(); } void selectUserIDs(const std::vector &uids) { const auto all = mTarget.userIDs(); std::vector indexes; indexes.reserve(uids.size()); for (const auto &uid: uids) { const unsigned int idx = std::distance(all.cbegin(), std::find_if(all.cbegin(), all.cend(), [uid](const GpgME::UserID &other) { return uidEqual(uid, other); })); if (idx < all.size()) { indexes.push_back(idx); } } mUserIDModel.setCheckedUserIDs(indexes); } std::vector selectedUserIDs() const { return mUserIDModel.checkedUserIDs(); } bool exportableSelected() const { return mExportCB->isChecked(); } bool publishSelected() const { return mPublishCB->isChecked(); } QString tags() const { return mTagsLE->text().trimmed(); } GpgME::Key target() const { return mTarget; } + bool isValid() const + { + // do not accept null keys + if (mTarget.isNull() || mSecKeySelect->currentKey().isNull()) { + return false; + } + // do not accept empty list of user ids + if (selectedUserIDs().empty()) { + return false; + } + // do not accept if the keys are the same + if (_detail::ByFingerprint()(mTarget, mSecKeySelect->currentKey())) { + return false; + } + return true; + } + private: CertifyWidget *const q; QLabel *mFprLabel = nullptr; KeySelectionCombo *mSecKeySelect = nullptr; QCheckBox *mExportCB = nullptr; QCheckBox *mPublishCB = nullptr; QLineEdit *mTagsLE = nullptr; UserIDModel mUserIDModel; GpgME::Key mTarget; }; CertifyWidget::CertifyWidget(QWidget *parent) : QWidget{parent} , d{std::make_unique(this)} { } Kleo::CertifyWidget::~CertifyWidget() = default; void CertifyWidget::setTarget(const GpgME::Key &key) { d->setTarget(key); } GpgME::Key CertifyWidget::target() const { return d->target(); } void CertifyWidget::selectUserIDs(const std::vector &uids) { d->selectUserIDs(uids); } std::vector 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::isValid() const +{ + return d->isValid(); +} + // For UserID model #include "certifywidget.moc" diff --git a/src/dialogs/certifywidget.h b/src/dialogs/certifywidget.h index 1cba14430..1c04f5c02 100644 --- a/src/dialogs/certifywidget.h +++ b/src/dialogs/certifywidget.h @@ -1,62 +1,67 @@ #pragma once /* dialogs/certifywidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2019 g 10code GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include namespace GpgME { class Key; class UserID; } // namespace GpgME namespace Kleo { /** Widget for OpenPGP certification. */ class CertifyWidget : public QWidget { Q_OBJECT public: explicit CertifyWidget(QWidget *parent = nullptr); ~CertifyWidget() override; /* Set the key to certify */ void setTarget(const GpgME::Key &key); /* Get the key to certify */ GpgME::Key target() const; /* Select specific user ids. Default: all */ void selectUserIDs(const std::vector &uids); /* The user ids that should be signed */ std::vector selectedUserIDs() const; /* The secret key selected */ GpgME::Key secKey() const; /* Should the signature be exportable */ bool exportableSelected() const; /* Additional tags for the key */ QString tags() const; /* Should the signed key be be published */ bool publishSelected() const; + bool isValid() const; + +Q_SIGNALS: + void changed() const; + private: class Private; std::unique_ptr d; }; } // namespace Kleo