diff --git a/src/newcertificatewizard/chooseprotocolpage.cpp b/src/newcertificatewizard/chooseprotocolpage.cpp index f8f35b8bb..f069c81ee 100644 --- a/src/newcertificatewizard/chooseprotocolpage.cpp +++ b/src/newcertificatewizard/chooseprotocolpage.cpp @@ -1,127 +1,128 @@ /* -*- mode: c++; c-basic-offset:4 -*- newcertificatewizard/chooseprotocolpage.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016, 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2022 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 "chooseprotocolpage_p.h" #include "utils/scrollarea.h" #include <KLocalizedString> #include <QCommandLinkButton> #include <QVBoxLayout> using namespace Kleo; using namespace Kleo::NewCertificateUi; namespace { static void force_set_checked(QAbstractButton *b, bool on) { // work around Qt bug (tested: 4.1.4, 4.2.3, 4.3.4) const bool autoExclusive = b->autoExclusive(); b->setAutoExclusive(false); b->setChecked(b->isEnabled() && on); b->setAutoExclusive(autoExclusive); } } struct ChooseProtocolPage::UI { QCommandLinkButton *pgpCLB = nullptr; QCommandLinkButton *x509CLB = nullptr; UI(QWizardPage *parent) { parent->setTitle(i18nc("@title", "Choose Type of Key Pair")); parent->setSubTitle(i18n("Please choose which type of key pair you want to create.")); auto mainLayout = new QVBoxLayout{parent}; - mainLayout->setContentsMargins(0, 0, 0, 0); + const auto margins = mainLayout->contentsMargins(); + mainLayout->setContentsMargins(margins.left(), 0, margins.right(), 0); auto scrollArea = new ScrollArea{parent}; scrollArea->setFocusPolicy(Qt::NoFocus); scrollArea->setFrameStyle(QFrame::NoFrame); scrollArea->setBackgroundRole(parent->backgroundRole()); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); auto scrollAreaLayout = qobject_cast<QBoxLayout *>(scrollArea->widget()->layout()); - scrollAreaLayout->setContentsMargins(0, 0, 0, 0); + scrollAreaLayout->setContentsMargins(0, margins.top(), 0, margins.bottom()); pgpCLB = new QCommandLinkButton{parent}; pgpCLB->setText(i18n("Create a personal OpenPGP key pair")); pgpCLB->setDescription(i18n("OpenPGP key pairs are certified by confirming the fingerprint of the public key.")); pgpCLB->setAccessibleDescription(pgpCLB->description()); pgpCLB->setCheckable(true); pgpCLB->setAutoExclusive(true); scrollAreaLayout->addWidget(pgpCLB); x509CLB = new QCommandLinkButton{parent}; x509CLB->setText(i18n("Create a personal X.509 key pair and certification request")); x509CLB->setDescription(i18n("X.509 key pairs are certified by a certification authority (CA). The generated request needs to be sent to a CA to finalize creation.")); x509CLB->setAccessibleDescription(x509CLB->description()); x509CLB->setCheckable(true); x509CLB->setAutoExclusive(true); scrollAreaLayout->addWidget(x509CLB); scrollAreaLayout->addStretch(1); mainLayout->addWidget(scrollArea); } }; ChooseProtocolPage::ChooseProtocolPage(QWidget *parent) : WizardPage{parent} , ui{new UI{this}} { setObjectName(QStringLiteral("Kleo__NewCertificateUi__ChooseProtocolPage")); registerField(QStringLiteral("pgp"), ui->pgpCLB); } ChooseProtocolPage::~ChooseProtocolPage() = default; void ChooseProtocolPage::setProtocol(GpgME::Protocol proto) { if (proto == GpgME::OpenPGP) { ui->pgpCLB->setChecked(true); } else if (proto == GpgME::CMS) { ui->x509CLB->setChecked(true); } else { force_set_checked(ui->pgpCLB, false); force_set_checked(ui->x509CLB, false); } } GpgME::Protocol ChooseProtocolPage::protocol() const { return ui->pgpCLB->isChecked() ? GpgME::OpenPGP : ui->x509CLB->isChecked() ? GpgME::CMS : GpgME::UnknownProtocol; } void ChooseProtocolPage::initializePage() { if (!initialized) { connect(ui->pgpCLB, &QAbstractButton::clicked, wizard(), &QWizard::next, Qt::QueuedConnection); connect(ui->x509CLB, &QAbstractButton::clicked, wizard(), &QWizard::next, Qt::QueuedConnection); } initialized = true; } bool ChooseProtocolPage::isComplete() const { return protocol() != GpgME::UnknownProtocol; } // #include "chooseprotocolpage.moc" diff --git a/src/newcertificatewizard/enterdetailspage.cpp b/src/newcertificatewizard/enterdetailspage.cpp index d398701e4..9ca2be7e4 100644 --- a/src/newcertificatewizard/enterdetailspage.cpp +++ b/src/newcertificatewizard/enterdetailspage.cpp @@ -1,537 +1,539 @@ /* -*- mode: c++; c-basic-offset:4 -*- newcertificatewizard/enterdetailspage.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016, 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include <config-kleopatra.h> #include "enterdetailspage_p.h" #include "advancedsettingsdialog_p.h" #include "utils/scrollarea.h" #include "utils/userinfo.h" #include "utils/validation.h" #include <settings.h> #include <Libkleo/Compat> #include <Libkleo/Dn> #include <Libkleo/Formatting> #include <Libkleo/OidMap> #include <Libkleo/Stl_Util> #include <KLocalizedString> #include <QGpgME/CryptoConfig> #include <QGpgME/Protocol> #include <QCheckBox> #include <QHBoxLayout> #include <QLabel> #include <QLineEdit> #include <QMetaProperty> #include <QPushButton> #include <QSpacerItem> #include <QValidator> #include <QVBoxLayout> #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::NewCertificateUi; using namespace GpgME; static void set_tab_order(const QList<QWidget *> &wl) { kdtools::for_each_adjacent_pair(wl.begin(), wl.end(), &QWidget::setTabOrder); } static QString pgpLabel(const QString &attr) { if (attr == QLatin1String("NAME")) { return i18n("Name"); } if (attr == QLatin1String("EMAIL")) { return i18n("EMail"); } return QString(); } static QString attributeLabel(const QString &attr, bool pgp) { if (attr.isEmpty()) { return QString(); } const QString label = pgp ? pgpLabel(attr) : Kleo::DN::attributeNameToLabel(attr); if (!label.isEmpty()) if (pgp) { return label; } else return i18nc("Format string for the labels in the \"Your Personal Data\" page", "%1 (%2)", label, attr); else { return attr; } } static QString attributeFromKey(QString key) { return key.remove(QLatin1Char('!')); } struct EnterDetailsPage::UI { QGridLayout *gridLayout = nullptr; QLabel *nameLB = nullptr; QLineEdit *nameLE = nullptr; QLabel *nameRequiredLB = nullptr; QLabel *emailLB = nullptr; QLineEdit *emailLE = nullptr; QLabel *emailRequiredLB = nullptr; QCheckBox *withPassCB = nullptr; QLineEdit *resultLE = nullptr; QLabel *errorLB = nullptr; QPushButton *advancedPB = nullptr; UI(QWizardPage *parent) { parent->setTitle(i18nc("@title", "Enter Details")); auto mainLayout = new QVBoxLayout{parent}; + const auto margins = mainLayout->contentsMargins(); + mainLayout->setContentsMargins(margins.left(), 0, margins.right(), 0); auto scrollArea = new ScrollArea{parent}; scrollArea->setFocusPolicy(Qt::NoFocus); scrollArea->setFrameStyle(QFrame::NoFrame); scrollArea->setBackgroundRole(parent->backgroundRole()); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents); auto scrollAreaLayout = qobject_cast<QBoxLayout *>(scrollArea->widget()->layout()); - scrollAreaLayout->setContentsMargins(0, 0, 0, 0); + scrollAreaLayout->setContentsMargins(0, margins.top(), 0, margins.bottom()); gridLayout = new QGridLayout; int row = 0; nameLB = new QLabel{i18n("Real name:"), parent}; nameLE = new QLineEdit{parent}; nameRequiredLB = new QLabel{i18n("(required)"), parent}; gridLayout->addWidget(nameLB, row, 0, 1, 1); gridLayout->addWidget(nameLE, row, 1, 1, 1); gridLayout->addWidget(nameRequiredLB, row, 2, 1, 1); row++; emailLB = new QLabel{i18n("EMail address:"), parent}; emailLE = new QLineEdit{parent}; emailRequiredLB = new QLabel{i18n("(required)"), parent}; gridLayout->addWidget(emailLB, row, 0, 1, 1); gridLayout->addWidget(emailLE, row, 1, 1, 1); gridLayout->addWidget(emailRequiredLB, row, 2, 1, 1); row++; withPassCB = new QCheckBox{i18n("Protect the generated key with a passphrase."), parent}; withPassCB->setToolTip(i18n("Encrypts the secret key with an unrecoverable passphrase. You will be asked for the passphrase during key generation.")); gridLayout->addWidget(withPassCB, row, 1, 1, 2); scrollAreaLayout->addLayout(gridLayout); auto verticalSpacer = new QSpacerItem{20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding}; scrollAreaLayout->addItem(verticalSpacer); resultLE = new QLineEdit{parent}; resultLE->setFrame(false); resultLE->setAlignment(Qt::AlignCenter); resultLE->setReadOnly(true); scrollAreaLayout->addWidget(resultLE); auto horizontalLayout = new QHBoxLayout; errorLB = new QLabel{parent}; QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(0); sizePolicy.setHeightForWidth(errorLB->sizePolicy().hasHeightForWidth()); errorLB->setSizePolicy(sizePolicy); QPalette palette; QBrush brush(QColor(255, 0, 0, 255)); brush.setStyle(Qt::SolidPattern); palette.setBrush(QPalette::Active, QPalette::WindowText, brush); palette.setBrush(QPalette::Inactive, QPalette::WindowText, brush); QBrush brush1(QColor(114, 114, 114, 255)); brush1.setStyle(Qt::SolidPattern); palette.setBrush(QPalette::Disabled, QPalette::WindowText, brush1); errorLB->setPalette(palette); errorLB->setTextFormat(Qt::RichText); horizontalLayout->addWidget(errorLB); advancedPB = new QPushButton{i18n("Advanced Settings..."), parent}; advancedPB->setAutoDefault(false); horizontalLayout->addWidget(advancedPB); scrollAreaLayout->addLayout(horizontalLayout); mainLayout->addWidget(scrollArea); } }; EnterDetailsPage::EnterDetailsPage(QWidget *p) : WizardPage{p} , ui{new UI{this}} , dialog{new AdvancedSettingsDialog{this}} { setObjectName(QStringLiteral("Kleo__NewCertificateUi__EnterDetailsPage")); Settings settings; if (settings.hideAdvanced()) { setSubTitle(i18n("Please enter your personal details below.")); } else { setSubTitle(i18n("Please enter your personal details below. If you want more control over the parameters, click on the Advanced Settings button.")); } ui->advancedPB->setVisible(!settings.hideAdvanced()); ui->resultLE->setFocusPolicy(Qt::NoFocus); // set errorLB to have a fixed height of two lines: ui->errorLB->setText(QStringLiteral("2<br>1")); ui->errorLB->setFixedHeight(ui->errorLB->minimumSizeHint().height()); ui->errorLB->clear(); connect(ui->advancedPB, &QPushButton::clicked, this, &EnterDetailsPage::slotAdvancedSettingsClicked); connect(ui->resultLE, &QLineEdit::textChanged, this, &QWizardPage::completeChanged); // The email doesn't necessarily show up in ui->resultLE: connect(ui->emailLE, &QLineEdit::textChanged, this, &QWizardPage::completeChanged); registerDialogPropertiesAsFields(); registerField(QStringLiteral("dn"), ui->resultLE); registerField(QStringLiteral("name"), ui->nameLE); registerField(QStringLiteral("email"), ui->emailLE); registerField(QStringLiteral("protectedKey"), ui->withPassCB); updateForm(); setCommitPage(true); setButtonText(QWizard::CommitButton, i18nc("@action", "Create")); const auto conf = QGpgME::cryptoConfig(); if (!conf) { qCWarning(KLEOPATRA_LOG) << "Failed to obtain cryptoConfig."; return; } const auto entry = getCryptoConfigEntry(conf, "gpg-agent", "enforce-passphrase-constraints"); if (entry && entry->boolValue()) { qCDebug(KLEOPATRA_LOG) << "Disabling passphrace cb because of agent config."; ui->withPassCB->setEnabled(false); ui->withPassCB->setChecked(true); } else { const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); ui->withPassCB->setChecked(config.readEntry("WithPassphrase", false)); ui->withPassCB->setEnabled(!config.isEntryImmutable("WithPassphrase")); } } EnterDetailsPage::~EnterDetailsPage() = default; void EnterDetailsPage::initializePage() { updateForm(); ui->withPassCB->setVisible(pgp()); dialog->setProtocol(pgp() ? OpenPGP : CMS); } void EnterDetailsPage::cleanupPage() { saveValues(); // reset protocol when navigating back to "Choose Protocol" page resetProtocol(); } void EnterDetailsPage::registerDialogPropertiesAsFields() { const QMetaObject *const mo = dialog->metaObject(); for (unsigned int i = mo->propertyOffset(), end = i + mo->propertyCount(); i != end; ++i) { const QMetaProperty mp = mo->property(i); if (mp.isValid()) { registerField(QLatin1String(mp.name()), dialog, mp.name(), SIGNAL(accepted())); } } } void EnterDetailsPage::saveValues() { for (const Line &line : std::as_const(lineList)) { savedValues[ attributeFromKey(line.attr) ] = line.edit->text().trimmed(); } } void EnterDetailsPage::clearForm() { qDeleteAll(dynamicWidgets); dynamicWidgets.clear(); lineList.clear(); ui->nameLE->hide(); ui->nameLE->clear(); ui->nameLB->hide(); ui->nameRequiredLB->hide(); ui->emailLE->hide(); ui->emailLE->clear(); ui->emailLB->hide(); ui->emailRequiredLB->hide(); } static int row_index_of(QWidget *w, QGridLayout *l) { const int idx = l->indexOf(w); int r, c, rs, cs; l->getItemPosition(idx, &r, &c, &rs, &cs); return r; } static QLineEdit *adjust_row(QGridLayout *l, int row, const QString &label, const QString &preset, QValidator *validator, bool readonly, bool required) { Q_ASSERT(l); Q_ASSERT(row >= 0); Q_ASSERT(row < l->rowCount()); auto lb = qobject_cast<QLabel *>(l->itemAtPosition(row, 0)->widget()); Q_ASSERT(lb); auto le = qobject_cast<QLineEdit *>(l->itemAtPosition(row, 1)->widget()); Q_ASSERT(le); lb->setBuddy(le); // For better accessibility auto reqLB = qobject_cast<QLabel *>(l->itemAtPosition(row, 2)->widget()); Q_ASSERT(reqLB); lb->setText(i18nc("interpunctation for labels", "%1:", label)); le->setText(preset); reqLB->setText(required ? i18n("(required)") : i18n("(optional)")); delete le->validator(); if (validator) { if (!validator->parent()) { validator->setParent(le); } le->setValidator(validator); } le->setReadOnly(readonly && le->hasAcceptableInput()); lb->show(); le->show(); reqLB->show(); return le; } static int add_row(QGridLayout *l, QList<QWidget *> *wl) { Q_ASSERT(l); Q_ASSERT(wl); const int row = l->rowCount(); QWidget *w1, *w2, *w3; l->addWidget(w1 = new QLabel(l->parentWidget()), row, 0); l->addWidget(w2 = new QLineEdit(l->parentWidget()), row, 1); l->addWidget(w3 = new QLabel(l->parentWidget()), row, 2); wl->push_back(w1); wl->push_back(w2); wl->push_back(w3); return row; } void EnterDetailsPage::updateForm() { clearForm(); const auto settings = Kleo::Settings{}; const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); QStringList attrOrder = config.readEntry(pgp() ? "OpenPGPAttributeOrder" : "DNAttributeOrder", QStringList()); if (attrOrder.empty()) { if (pgp()) { attrOrder << QStringLiteral("NAME") << QStringLiteral("EMAIL"); } else { attrOrder << QStringLiteral("CN!") << QStringLiteral("L") << QStringLiteral("OU") << QStringLiteral("O") << QStringLiteral("C") << QStringLiteral("EMAIL!"); } } QList<QWidget *> widgets; widgets.push_back(ui->nameLE); widgets.push_back(ui->emailLE); QMap<int, Line> lines; for (const QString &rawKey : std::as_const(attrOrder)) { const QString key = rawKey.trimmed().toUpper(); const QString attr = attributeFromKey(key); if (attr.isEmpty()) { continue; } const QString preset = savedValues.value(attr, config.readEntry(attr, QString())); const bool required = key.endsWith(QLatin1Char('!')); const bool readonly = config.isEntryImmutable(attr); const QString label = config.readEntry(attr + QLatin1String("_label"), attributeLabel(attr, pgp())); const QString regex = config.readEntry(attr + QLatin1String("_regex")); const QString placeholder = config.readEntry(attr + QLatin1String{"_placeholder"}); int row; bool known = true; QValidator *validator = nullptr; if (attr == QLatin1String("EMAIL")) { row = row_index_of(ui->emailLE, ui->gridLayout); validator = regex.isEmpty() ? Validation::email() : Validation::email(regex); } else if (attr == QLatin1String("NAME") || attr == QLatin1String("CN")) { if ((pgp() && attr == QLatin1String("CN")) || (!pgp() && attr == QLatin1String("NAME"))) { continue; } if (pgp()) { validator = regex.isEmpty() ? Validation::pgpName() : Validation::pgpName(regex); } row = row_index_of(ui->nameLE, ui->gridLayout); } else { known = false; row = add_row(ui->gridLayout, &dynamicWidgets); } if (!validator && !regex.isEmpty()) { validator = new QRegularExpressionValidator{QRegularExpression{regex}, nullptr}; } QLineEdit *le = adjust_row(ui->gridLayout, row, label, preset, validator, readonly, required); le->setPlaceholderText(placeholder); const Line line = { key, label, regex, le }; lines[row] = line; if (!known) { widgets.push_back(le); } // don't connect twice: disconnect(le, &QLineEdit::textChanged, this, &EnterDetailsPage::slotUpdateResultLabel); connect(le, &QLineEdit::textChanged, this, &EnterDetailsPage::slotUpdateResultLabel); } // create lineList in visual order, so requirementsAreMet() // complains from top to bottom: lineList.reserve(lines.count()); std::copy(lines.cbegin(), lines.cend(), std::back_inserter(lineList)); widgets.push_back(ui->withPassCB); widgets.push_back(ui->advancedPB); const bool prefillName = (pgp() && settings.prefillName()) || (!pgp() && settings.prefillCN()); if (ui->nameLE->text().isEmpty() && prefillName) { ui->nameLE->setText(userFullName()); } if (ui->emailLE->text().isEmpty() && settings.prefillEmail()) { ui->emailLE->setText(userEmailAddress()); } slotUpdateResultLabel(); set_tab_order(widgets); } QString EnterDetailsPage::cmsDN() const { DN dn; for (QVector<Line>::const_iterator it = lineList.begin(), end = lineList.end(); it != end; ++it) { const QString text = it->edit->text().trimmed(); if (text.isEmpty()) { continue; } QString attr = attributeFromKey(it->attr); if (attr == QLatin1String("EMAIL")) { continue; } if (const char *const oid = oidForAttributeName(attr)) { attr = QString::fromUtf8(oid); } dn.append(DN::Attribute(attr, text)); } return dn.dn(); } QString EnterDetailsPage::pgpUserID() const { return Formatting::prettyNameAndEMail(OpenPGP, QString(), ui->nameLE->text().trimmed(), ui->emailLE->text().trimmed(), QString()); } static bool has_intermediate_input(const QLineEdit *le) { QString text = le->text(); int pos = le->cursorPosition(); const QValidator *const v = le->validator(); return v && v->validate(text, pos) == QValidator::Intermediate; } static bool requirementsAreMet(const QVector<EnterDetailsPage::Line> &list, QString &error) { bool allEmpty = true; for (const auto &line : list) { const QLineEdit *le = line.edit; if (!le) { continue; } const QString key = line.attr; qCDebug(KLEOPATRA_LOG) << "requirementsAreMet(): checking" << key << "against" << le->text() << ":"; if (le->text().trimmed().isEmpty()) { if (key.endsWith(QLatin1Char('!'))) { if (line.regex.isEmpty()) { error = xi18nc("@info", "<interface>%1</interface> is required, but empty.", line.label); } else error = xi18nc("@info", "<interface>%1</interface> is required, but empty.<nl/>" "Local Admin rule: <icode>%2</icode>", line.label, line.regex); return false; } } else if (has_intermediate_input(le)) { if (line.regex.isEmpty()) { error = xi18nc("@info", "<interface>%1</interface> is incomplete.", line.label); } else error = xi18nc("@info", "<interface>%1</interface> is incomplete.<nl/>" "Local Admin rule: <icode>%2</icode>", line.label, line.regex); return false; } else if (!le->hasAcceptableInput()) { if (line.regex.isEmpty()) { error = xi18nc("@info", "<interface>%1</interface> is invalid.", line.label); } else error = xi18nc("@info", "<interface>%1</interface> is invalid.<nl/>" "Local Admin rule: <icode>%2</icode>", line.label, line.regex); return false; } else { allEmpty = false; } } // Ensure that at least one value is acceptable return !allEmpty; } bool EnterDetailsPage::isComplete() const { QString error; const bool ok = requirementsAreMet(lineList, error); ui->errorLB->setText(error); return ok; } void EnterDetailsPage::slotAdvancedSettingsClicked() { dialog->exec(); } void EnterDetailsPage::slotUpdateResultLabel() { ui->resultLE->setText(pgp() ? pgpUserID() : cmsDN()); } diff --git a/src/newcertificatewizard/resultpage.cpp b/src/newcertificatewizard/resultpage.cpp index 9a9f34776..82baae668 100644 --- a/src/newcertificatewizard/resultpage.cpp +++ b/src/newcertificatewizard/resultpage.cpp @@ -1,389 +1,391 @@ /* -*- mode: c++; c-basic-offset:4 -*- newcertificatewizard/resultpage.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016, 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include <config-kleopatra.h> #include "resultpage_p.h" #include "commands/exportcertificatecommand.h" #include "commands/exportopenpgpcertstoservercommand.h" #ifdef QGPGME_SUPPORTS_SECRET_KEY_EXPORT # include "commands/exportsecretkeycommand.h" #else # include "commands/exportsecretkeycommand_old.h" #endif #include "utils/dragqueen.h" #include "utils/filedialog.h" #include "utils/scrollarea.h" #include <Libkleo/KeyCache> #include <KConfigGroup> #include <KLocalizedString> #include <KMessageBox> #include <KSharedConfig> #include <QDesktopServices> #include <QGroupBox> #include <QHBoxLayout> #include <QLineEdit> #include <QPushButton> #include <QTextBrowser> #include <QUrlQuery> #include <QVBoxLayout> #include <gpgme++/key.h> #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::NewCertificateUi; using namespace GpgME; #ifndef QGPGME_SUPPORTS_SECRET_KEY_EXPORT using Kleo::Commands::Compat::ExportSecretKeyCommand; #endif struct ResultPage::UI { QTextBrowser *resultTB = nullptr; QTextBrowser *errorTB = nullptr; DragQueen *dragQueen = nullptr; QPushButton *restartWizardPB = nullptr; QGroupBox *nextStepsGB = nullptr; QPushButton *saveRequestToFilePB = nullptr; QPushButton *sendRequestByEMailPB = nullptr; QPushButton *makeBackupPB = nullptr; QPushButton *sendCertificateByEMailPB = nullptr; QPushButton *uploadToKeyserverPB = nullptr; QPushButton *createRevocationRequestPB = nullptr; QPushButton *createSigningCertificatePB = nullptr; QPushButton *createEncryptionCertificatePB = nullptr; UI(QWizardPage *parent) { auto mainLayout = new QVBoxLayout{parent}; + const auto margins = mainLayout->contentsMargins(); + mainLayout->setContentsMargins(margins.left(), 0, margins.right(), 0); auto scrollArea = new ScrollArea{parent}; scrollArea->setFocusPolicy(Qt::NoFocus); scrollArea->setFrameStyle(QFrame::NoFrame); scrollArea->setBackgroundRole(parent->backgroundRole()); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents); auto scrollAreaLayout = qobject_cast<QBoxLayout *>(scrollArea->widget()->layout()); - scrollAreaLayout->setContentsMargins(0, 0, 0, 0); + scrollAreaLayout->setContentsMargins(0, margins.top(), 0, margins.bottom()); auto resultGB = new QGroupBox{i18nc("@title:group", "Result"), parent}; auto resultGBLayout = new QHBoxLayout{resultGB}; resultTB = new QTextBrowser{resultGB}; resultGBLayout->addWidget(resultTB); errorTB = new QTextBrowser{resultGB}; resultGBLayout->addWidget(errorTB); dragQueen = new Kleo::DragQueen{resultGB}; dragQueen->setToolTip(i18n("Drag this icon to your mail application's composer to attach the request to a mail.")); dragQueen->setAlignment(Qt::AlignCenter); resultGBLayout->addWidget(dragQueen); scrollAreaLayout->addWidget(resultGB); restartWizardPB = new QPushButton{i18n("Restart This Wizard (Keeps Your Parameters)"), parent}; scrollAreaLayout->addWidget(restartWizardPB); nextStepsGB = new QGroupBox{i18nc("@title:group", "Next Steps"), parent}; auto nextStepsGBLayout = new QVBoxLayout{nextStepsGB}; saveRequestToFilePB = new QPushButton{i18n("Save Certificate Request To File..."), nextStepsGB}; nextStepsGBLayout->addWidget(saveRequestToFilePB); sendRequestByEMailPB = new QPushButton{i18n("Send Certificate Request By EMail..."), nextStepsGB}; nextStepsGBLayout->addWidget(sendRequestByEMailPB); makeBackupPB = new QPushButton{i18n("Make a Backup Of Your Key Pair..."), nextStepsGB}; nextStepsGBLayout->addWidget(makeBackupPB); sendCertificateByEMailPB = new QPushButton{i18n("Send Public Key By EMail..."), nextStepsGB}; nextStepsGBLayout->addWidget(sendCertificateByEMailPB); uploadToKeyserverPB = new QPushButton{i18n("Upload Public Key To Directory Service..."), nextStepsGB}; nextStepsGBLayout->addWidget(uploadToKeyserverPB); createRevocationRequestPB = new QPushButton{i18n("Create Revocation Request..."), nextStepsGB}; nextStepsGBLayout->addWidget(createRevocationRequestPB); createSigningCertificatePB = new QPushButton{i18n("Create Signing Certificate With Same Parameters"), nextStepsGB}; nextStepsGBLayout->addWidget(createSigningCertificatePB); createEncryptionCertificatePB = new QPushButton{i18n("Create Encryption Certificate With Same Parameters"), nextStepsGB}; nextStepsGBLayout->addWidget(createEncryptionCertificatePB); scrollAreaLayout->addWidget(nextStepsGB); mainLayout->addWidget(scrollArea); } }; ResultPage::ResultPage(QWidget *p) : WizardPage{p} , ui{new UI{this}} , initialized{false} , successfullyCreatedSigningCertificate{false} , successfullyCreatedEncryptionCertificate{false} { setObjectName(QString::fromUtf8("Kleo__NewCertificateUi__ResultPage")); connect(ui->saveRequestToFilePB, &QPushButton::clicked, this, &ResultPage::slotSaveRequestToFile); connect(ui->sendRequestByEMailPB, &QPushButton::clicked, this, &ResultPage::slotSendRequestByEMail); connect(ui->sendCertificateByEMailPB, &QPushButton::clicked, this, &ResultPage::slotSendCertificateByEMail); connect(ui->uploadToKeyserverPB, &QPushButton::clicked, this, &ResultPage::slotUploadCertificateToDirectoryServer); connect(ui->makeBackupPB, &QPushButton::clicked, this, &ResultPage::slotBackupCertificate); connect(ui->createRevocationRequestPB, &QPushButton::clicked, this, &ResultPage::slotCreateRevocationRequest); connect(ui->createSigningCertificatePB, &QPushButton::clicked, this, &ResultPage::slotCreateSigningCertificate); connect(ui->createEncryptionCertificatePB, &QPushButton::clicked, this, &ResultPage::slotCreateEncryptionCertificate); ui->dragQueen->setPixmap(QIcon::fromTheme(QStringLiteral("kleopatra")).pixmap(64, 64)); registerField(QStringLiteral("error"), ui->errorTB, "plainText"); registerField(QStringLiteral("result"), ui->resultTB, "plainText"); registerField(QStringLiteral("url"), ui->dragQueen, "url"); // hidden field, since QWizard can't deal with non-widget-backed fields... auto le = new QLineEdit(this); le->hide(); registerField(QStringLiteral("fingerprint"), le); } ResultPage::~ResultPage() = default; void ResultPage::initializePage() { const bool error = isError(); if (error) { setTitle(i18nc("@title", "Key Creation Failed")); setSubTitle(i18n("Key pair creation failed. Please find details about the failure below.")); } else { setTitle(i18nc("@title", "Key Pair Successfully Created")); setSubTitle(i18n("Your new key pair was created successfully. Please find details on the result and some suggested next steps below.")); } ui->resultTB ->setVisible(!error); ui->errorTB ->setVisible(error); ui->dragQueen ->setVisible(!error &&!pgp()); ui->restartWizardPB ->setVisible(error); ui->nextStepsGB ->setVisible(!error); ui->saveRequestToFilePB ->setVisible(!pgp()); ui->makeBackupPB ->setVisible(pgp()); ui->createRevocationRequestPB->setVisible(pgp() &&false); // not implemented ui->sendCertificateByEMailPB ->setVisible(pgp()); ui->sendRequestByEMailPB ->setVisible(!pgp()); ui->uploadToKeyserverPB ->setVisible(pgp()); if (!error && !pgp()) { if (signingAllowed() && !encryptionAllowed()) { successfullyCreatedSigningCertificate = true; } else if (!signingAllowed() && encryptionAllowed()) { successfullyCreatedEncryptionCertificate = true; } else { successfullyCreatedEncryptionCertificate = successfullyCreatedSigningCertificate = true; } } ui->createSigningCertificatePB->setVisible(successfullyCreatedEncryptionCertificate &&!successfullyCreatedSigningCertificate); ui->createEncryptionCertificatePB->setVisible(successfullyCreatedSigningCertificate &&!successfullyCreatedEncryptionCertificate); if (error) { wizard()->setOptions(wizard()->options() & ~QWizard::NoCancelButtonOnLastPage); } else { wizard()->setOptions(wizard()->options() | QWizard::NoCancelButtonOnLastPage); } if (!initialized) { connect(ui->restartWizardPB, &QAbstractButton::clicked, this, [this]() { restartAtEnterDetailsPage(); }); } initialized = true; } bool ResultPage::isError() const { return !ui->errorTB->document()->isEmpty(); } bool ResultPage::isComplete() const { return !isError(); } Key ResultPage::key() const { return KeyCache::instance()->findByFingerprint(fingerprint().toLatin1().constData()); } void ResultPage::slotSaveRequestToFile() { QString fileName = FileDialog::getSaveFileName(this, i18nc("@title", "Save Request"), QStringLiteral("imp"), i18n("PKCS#10 Requests (*.p10)")); if (fileName.isEmpty()) { return; } if (!fileName.endsWith(QLatin1String(".p10"), Qt::CaseInsensitive)) { fileName += QLatin1String(".p10"); } QFile src(QUrl(url()).toLocalFile()); if (!src.copy(fileName)) KMessageBox::error(this, xi18nc("@info", "Could not copy temporary file <filename>%1</filename> " "to file <filename>%2</filename>: <message>%3</message>", src.fileName(), fileName, src.errorString()), i18nc("@title", "Error Saving Request")); else KMessageBox::information(this, xi18nc("@info", "<para>Successfully wrote request to <filename>%1</filename>.</para>" "<para>You should now send the request to the Certification Authority (CA).</para>", fileName), i18nc("@title", "Request Saved")); } void ResultPage::slotSendRequestByEMail() { if (pgp()) { return; } const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); invokeMailer(config.readEntry("CAEmailAddress"), // to i18n("Please process this certificate."), // subject i18n("Please process this certificate and inform the sender about the location to fetch the resulting certificate.\n\nThanks,\n"), // body QUrl(url()).toLocalFile()); // attachment } void ResultPage::slotSendCertificateByEMail() { if (!pgp() || exportCertificateCommand) { return; } auto cmd = new ExportCertificateCommand(key()); connect(cmd, &ExportCertificateCommand::finished, this, &ResultPage::slotSendCertificateByEMailContinuation); cmd->setOpenPGPFileName(tmpDir().absoluteFilePath(fingerprint() + QLatin1String(".asc"))); cmd->start(); exportCertificateCommand = cmd; } void ResultPage::slotSendCertificateByEMailContinuation() { if (!exportCertificateCommand) { return; } // ### better error handling? const QString fileName = exportCertificateCommand->openPGPFileName(); qCDebug(KLEOPATRA_LOG) << "fileName" << fileName; exportCertificateCommand = nullptr; if (fileName.isEmpty()) { return; } invokeMailer(QString(), // to i18n("My new public OpenPGP key"), // subject i18n("Please find attached my new public OpenPGP key."), // body fileName); } void ResultPage::invokeMailer(const QString &to, const QString &subject, const QString &body, const QString &attachment) { qCDebug(KLEOPATRA_LOG) << "to:" << to << "subject:" << subject << "body:" << body << "attachment:" << attachment; // RFC 2368 says body's linebreaks need to be encoded as // "%0D%0A", so normalize body to CRLF: //body.replace(QLatin1Char('\n'), QStringLiteral("\r\n")).remove(QStringLiteral("\r\r")); QUrlQuery query; query.addQueryItem(QStringLiteral("subject"), subject); query.addQueryItem(QStringLiteral("body"), body); if (!attachment.isEmpty()) { query.addQueryItem(QStringLiteral("attach"), attachment); } QUrl url; url.setScheme(QStringLiteral("mailto")); url.setQuery(query); qCDebug(KLEOPATRA_LOG) << "openUrl" << url; QDesktopServices::openUrl(url); KMessageBox::information(this, xi18nc("@info", "<para><application>Kleopatra</application> tried to send a mail via your default mail client.</para>" "<para>Some mail clients are known not to support attachments when invoked this way.</para>" "<para>If your mail client does not have an attachment, then drag the <application>Kleopatra</application> icon and drop it on the message compose window of your mail client.</para>" "<para>If that does not work, either, save the request to a file, and then attach that.</para>"), i18nc("@title", "Sending Mail"), QStringLiteral("newcertificatewizard-mailto-troubles")); } void ResultPage::slotUploadCertificateToDirectoryServer() { if (pgp()) { (new ExportOpenPGPCertsToServerCommand(key()))->start(); } } void ResultPage::slotBackupCertificate() { if (pgp()) { (new ExportSecretKeyCommand(key()))->start(); } } void ResultPage::slotCreateRevocationRequest() { } void ResultPage::slotCreateSigningCertificate() { if (successfullyCreatedSigningCertificate) { return; } toggleSignEncryptAndRestart(); } void ResultPage::slotCreateEncryptionCertificate() { if (successfullyCreatedEncryptionCertificate) { return; } toggleSignEncryptAndRestart(); } void ResultPage::toggleSignEncryptAndRestart() { if (!wizard()) { return; } if (KMessageBox::warningContinueCancel( this, i18nc("@info", "This operation will delete the certification request. " "Please make sure that you have sent or saved it before proceeding."), i18nc("@title", "Certification Request About To Be Deleted")) != KMessageBox::Continue) { return; } const bool sign = signingAllowed(); const bool encr = encryptionAllowed(); setField(QStringLiteral("signingAllowed"), !sign); setField(QStringLiteral("encryptionAllowed"), !encr); restartAtEnterDetailsPage(); }