Commit da3aef1b authored by Ingo Klöcker's avatar Ingo Klöcker
Browse files

Add support for flagging a user ID as the primary user ID

GnuPG-bug-id: 5934
parent 83baac67
Pipeline #215245 passed with stage
in 5 minutes
......@@ -123,6 +123,7 @@ if (QGpgme_VERSION VERSION_GREATER_EQUAL "1.17.2")
set(QGPGME_SUPPORTS_KEY_REVOCATION 1)
set(QGPGME_SUPPORTS_KEY_REFRESH 1)
set(QGPGME_SUPPORTS_SET_FILENAME 1)
set(QGPGME_SUPPORTS_SET_PRIMARY_UID 1)
endif()
# Kdepimlibs packages
......
......@@ -56,3 +56,6 @@
/* Defined if QGpgME supports setting the file name of encrypted data */
#cmakedefine QGPGME_SUPPORTS_SET_FILENAME 1
/* Defined if QGpgME supports setting the primary user id of a key */
#cmakedefine QGPGME_SUPPORTS_SET_PRIMARY_UID 1
......@@ -201,6 +201,8 @@ set(_kleopatra_SRCS
commands/setinitialpincommand.h
commands/setpivcardapplicationadministrationkeycommand.cpp
commands/setpivcardapplicationadministrationkeycommand.h
commands/setprimaryuseridcommand.cpp
commands/setprimaryuseridcommand.h
commands/signclipboardcommand.cpp
commands/signclipboardcommand.h
commands/signencryptfilescommand.cpp
......
/* -*- mode: c++; c-basic-offset:4 -*-
commands/setprimaryuseridcommand.cpp
This file is part of Kleopatra, the KDE keymanager
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 "setprimaryuseridcommand.h"
#include "command_p.h"
#include <KLocalizedString>
#include <QGpgME/Protocol>
#ifdef QGPGME_SUPPORTS_SET_PRIMARY_UID
#include <QGpgME/SetPrimaryUserIDJob>
#endif
#include <gpgme++/key.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
class SetPrimaryUserIDCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::SetPrimaryUserIDCommand;
SetPrimaryUserIDCommand *q_func() const
{
return static_cast<SetPrimaryUserIDCommand *>(q);
}
public:
explicit Private(SetPrimaryUserIDCommand *qq, const UserID &userId);
~Private() override;
void startJob();
private:
void createJob();
void slotResult(const Error &err);
void showErrorDialog(const Error &error);
void showSuccessDialog();
private:
GpgME::UserID userId;
#ifdef QGPGME_SUPPORTS_SET_PRIMARY_UID
QPointer<QGpgME::SetPrimaryUserIDJob> job;
#endif
};
SetPrimaryUserIDCommand::Private *SetPrimaryUserIDCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const SetPrimaryUserIDCommand::Private *SetPrimaryUserIDCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
SetPrimaryUserIDCommand::Private::Private(SetPrimaryUserIDCommand *qq, const UserID &userId)
: Command::Private{qq}
, userId{userId}
{
}
SetPrimaryUserIDCommand::Private::~Private() = default;
void Commands::SetPrimaryUserIDCommand::Private::startJob()
{
#ifdef QGPGME_SUPPORTS_SET_PRIMARY_UID
createJob();
if (!job) {
finished();
return;
}
job->start(userId);
#else
error(i18nc("@info", "The backend does not support this operation."));
#endif
}
void SetPrimaryUserIDCommand::Private::createJob()
{
#ifdef QGPGME_SUPPORTS_SET_PRIMARY_UID
Q_ASSERT(!job);
const auto backend = QGpgME::openpgp();
if (!backend) {
return;
}
const auto j = backend->setPrimaryUserIDJob();
if (!j) {
return;
}
connect(j, &QGpgME::Job::progress, q, &Command::progress);
connect(j, &QGpgME::SetPrimaryUserIDJob::result, q, [this](const GpgME::Error &err) {
slotResult(err);
});
job = j;
#endif
}
void SetPrimaryUserIDCommand::Private::slotResult(const Error &err)
{
if (err.isCanceled()) {
} else if (err) {
showErrorDialog(err);
} else {
showSuccessDialog();
}
finished();
}
void SetPrimaryUserIDCommand::Private::showErrorDialog(const Error &err)
{
error(xi18nc("@info",
"<para>An error occurred while trying to flag the user ID<nl/><emphasis>%1</emphasis><nl/>as the primary user ID.</para>"
"<para><message>%2</message></para>",
QString::fromUtf8(userId.id()),
QString::fromLocal8Bit(err.asString())));
}
void SetPrimaryUserIDCommand::Private::showSuccessDialog()
{
success(xi18nc("@info",
"<para>The user ID<nl/><emphasis>%1</emphasis><nl/>has been flagged successfully as the primary user ID.</para>",
QString::fromUtf8(userId.id())));
}
SetPrimaryUserIDCommand::SetPrimaryUserIDCommand(const GpgME::UserID &userId)
: Command{new Private{this, userId}}
{
}
SetPrimaryUserIDCommand::~SetPrimaryUserIDCommand()
{
qCDebug(KLEOPATRA_LOG).nospace() << this << "::" << __func__;
}
void SetPrimaryUserIDCommand::doStart()
{
if (d->userId.isNull()) {
d->finished();
return;
}
const auto key = d->userId.parent();
if (key.protocol() != GpgME::OpenPGP || !key.hasSecret()) {
d->finished();
return;
}
d->startJob();
}
void SetPrimaryUserIDCommand::doCancel()
{
qCDebug(KLEOPATRA_LOG).nospace() << this << "::" << __func__;
#ifdef QGPGME_SUPPORTS_SET_PRIMARY_UID
if (d->job) {
d->job->slotCancel();
}
#endif
}
#undef d
#undef q
/* -*- mode: c++; c-basic-offset:4 -*-
commands/setprimaryuseridcommand.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "command.h"
namespace GpgME
{
class UserID;
}
namespace Kleo
{
namespace Commands
{
class SetPrimaryUserIDCommand : public Command
{
Q_OBJECT
public:
explicit SetPrimaryUserIDCommand(const GpgME::UserID &userId);
~SetPrimaryUserIDCommand() override;
private:
void doStart() override;
void doCancel() override;
private:
class Private;
inline Private *d_func();
inline const Private *d_func() const;
};
}
}
......@@ -30,6 +30,7 @@
#include "commands/refreshcertificatecommand.h"
#include "commands/revokecertificationcommand.h"
#include "commands/revokeuseridcommand.h"
#include "commands/setprimaryuseridcommand.h"
#include "commands/adduseridcommand.h"
#include "commands/genrevokecommand.h"
#include "commands/detailscommand.h"
......@@ -247,6 +248,7 @@ public:
void webOfTrustClicked();
void exportClicked();
void addUserID();
void setPrimaryUserID(const GpgME::UserID &uid = {});
void changePassphrase();
void changeExpiration();
void keysMayHaveChanged();
......@@ -290,6 +292,7 @@ private:
QLabel *userIDTableLabel = nullptr;
NavigatableTreeWidget *userIDTable = nullptr;
QPushButton *addUserIDBtn = nullptr;
QPushButton *setPrimaryUserIDBtn = nullptr;
QPushButton *certifyBtn = nullptr;
QPushButton *revokeCertificationsBtn = nullptr;
QPushButton *revokeUserIDBtn = nullptr;
......@@ -349,6 +352,10 @@ private:
addUserIDBtn = new QPushButton(i18nc("@action:button", "Add User ID"), parent);
buttonRow->addWidget(addUserIDBtn);
setPrimaryUserIDBtn = new QPushButton{i18nc("@action:button", "Flag as Primary"), parent};
setPrimaryUserIDBtn->setToolTip(i18nc("@info:tooltip", "Flag the selected user ID as the primary user ID of this key."));
buttonRow->addWidget(setPrimaryUserIDBtn);
certifyBtn = new QPushButton(i18nc("@action:button", "Certify User IDs"), parent);
buttonRow->addWidget(certifyBtn);
......@@ -509,6 +516,8 @@ CertificateDetailsWidget::Private::Private(CertificateDetailsWidget *qq)
q, [this]() { updateUserIDActions(); });
connect(ui.addUserIDBtn, &QPushButton::clicked,
q, [this]() { addUserID(); });
connect(ui.setPrimaryUserIDBtn, &QPushButton::clicked,
q, [this]() { setPrimaryUserID(); });
connect(ui.revokeUserIDBtn, &QPushButton::clicked,
q, [this]() { revokeSelectedUserID(); });
connect(ui.changePassphraseBtn, &QPushButton::clicked,
......@@ -551,6 +560,11 @@ void CertificateDetailsWidget::Private::setupCommonProperties()
// update visibility of UI elements
ui.userIDs->setVisible(isOpenPGP);
ui.addUserIDBtn->setVisible(isOwnKey);
#ifdef QGPGME_SUPPORTS_SET_PRIMARY_UID
ui.setPrimaryUserIDBtn->setVisible(isOwnKey);
#else
ui.setPrimaryUserIDBtn->setVisible(false);
#endif
// ui.certifyBtn->setVisible(true); // always visible (for OpenPGP keys)
// ui.webOfTrustBtn->setVisible(true); // always visible (for OpenPGP keys)
ui.revokeCertificationsBtn->setVisible(Kleo::Commands::RevokeCertificationCommand::isSupported());
......@@ -585,6 +599,7 @@ void CertificateDetailsWidget::Private::setupCommonProperties()
// update availability of buttons
const auto userCanSignUserIDs = userHasCertificationKey();
ui.addUserIDBtn->setEnabled(canBeUsedForSecretKeyOperations(key));
ui.setPrimaryUserIDBtn->setEnabled(false); // requires a selected user ID
ui.certifyBtn->setEnabled(userCanSignUserIDs);
ui.revokeCertificationsBtn->setEnabled(userCanSignUserIDs);
ui.revokeUserIDBtn->setEnabled(false); // requires a selected user ID
......@@ -606,7 +621,13 @@ void CertificateDetailsWidget::Private::setupCommonProperties()
void CertificateDetailsWidget::Private::updateUserIDActions()
{
const auto userIDs = selectedUserIDs(ui.userIDTable);
ui.revokeUserIDBtn->setEnabled((userIDs.size() == 1) && canCreateCertifications(key) && canRevokeUserID(userIDs.front()));
const auto singleUserID = userIDs.size() == 1 ? userIDs.front() : GpgME::UserID{};
const bool isPrimaryUserID = !singleUserID.isNull() && (ui.userIDTable->selectedItems().front() == ui.userIDTable->topLevelItem(0));
ui.setPrimaryUserIDBtn->setEnabled(!singleUserID.isNull() //
&& !isPrimaryUserID //
&& !Kleo::isRevokedOrExpired(singleUserID) //
&& canBeUsedForSecretKeyOperations(key));
ui.revokeUserIDBtn->setEnabled(!singleUserID.isNull() && canCreateCertifications(key) && canRevokeUserID(singleUserID));
}
void CertificateDetailsWidget::Private::setUpUserIDTable()
......@@ -850,6 +871,28 @@ void CertificateDetailsWidget::Private::addUserID()
cmd->start();
}
void CertificateDetailsWidget::Private::setPrimaryUserID(const GpgME::UserID &uid)
{
auto userId = uid;
if (userId.isNull()) {
const auto userIDs = selectedUserIDs(ui.userIDTable);
if (userIDs.size() != 1) {
return;
}
userId = userIDs.front();
}
auto cmd = new Kleo::Commands::SetPrimaryUserIDCommand(userId);
QObject::connect(cmd, &Kleo::Commands::SetPrimaryUserIDCommand::finished, q, [this]() {
ui.userIDTable->setEnabled(true);
// the Flag As Primary button will be updated by the key update
updateKey();
});
ui.userIDTable->setEnabled(false);
ui.setPrimaryUserIDBtn->setEnabled(false);
cmd->start();
}
namespace
{
void ensureThatKeyDetailsAreLoaded(GpgME::Key &key)
......@@ -880,9 +923,25 @@ void CertificateDetailsWidget::Private::userIDTableContextMenuRequested(const QP
{
const auto userIDs = selectedUserIDs(ui.userIDTable);
const auto singleUserID = (userIDs.size() == 1) ? userIDs.front() : GpgME::UserID{};
#ifdef QGPGME_SUPPORTS_SET_PRIMARY_UID
const bool isPrimaryUserID = !singleUserID.isNull() && (ui.userIDTable->selectedItems().front() == ui.userIDTable->topLevelItem(0));
#endif
const bool canSignUserIDs = userHasCertificationKey();
auto menu = new QMenu(q);
#ifdef QGPGME_SUPPORTS_SET_PRIMARY_UID
if (key.hasSecret()) {
auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("favorite")),
i18nc("@action:inmenu", "Flag as Primary User ID"),
q, [this, singleUserID]() {
setPrimaryUserID(singleUserID);
});
action->setEnabled(!singleUserID.isNull() //
&& !isPrimaryUserID //
&& !Kleo::isRevokedOrExpired(singleUserID) //
&& canBeUsedForSecretKeyOperations(key));
}
#endif
{
const auto actionText = userIDs.empty() ? i18nc("@action:inmenu", "Certify User IDs...")
: i18ncp("@action:inmenu", "Certify User ID...", "Certify User IDs...", userIDs.size());
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment