Page MenuHome GnuPG

No OneTemporary

diff --git a/client/setupdialogs.cpp b/client/setupdialogs.cpp
index 89cdbd9..0fe3a1a 100644
--- a/client/setupdialogs.cpp
+++ b/client/setupdialogs.cpp
@@ -1,782 +1,786 @@
// SPDX-FileCopyrightText: 2026 g10 code Gmbh
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-Contributor: Thomas Friedrichsmeier <thomas.friedrichsmeier@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "setupdialogs.h"
#include "config.h"
#include "connectioncontroller.h"
#include "gpgolweb_version.h"
#include "rootcagenerator/controller.h"
#include "statusdialog.h"
#include "websocketclient.h"
#include <KAssistantDialog>
#include <KLocalizedString>
#include <KMessageBox>
#include <KTitleWidget>
#include <KUrlRequester>
#include <QApplication>
#include <QButtonGroup>
#include <QCheckBox>
#include <QClipboard>
#include <QComboBox>
#include <QDesktopServices>
#include <QDialog>
#include <QDialogButtonBox>
#include <QDir>
#include <QFile>
#include <QGroupBox>
#include <QLabel>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QRadioButton>
#include <QSaveFile>
#include <QSettings>
#include <QToolTip>
#include <QVBoxLayout>
#include <Libkleo/KeyCache>
using namespace Qt::StringLiterals;
PairingDialog::PairingDialog(QWidget *parent)
: QDialog(parent)
{
setWindowTitle(i18n("Pairing mode active"));
auto l = new QVBoxLayout(this);
auto lab = new QLabel(i18n("<p>Copy and paste the code shown below into the input field shown at the top of the Add-In:</p>"));
lab->setWordWrap(true);
l->addWidget(lab);
m_pairingTokenLabel = new QLabel(i18nc("status message", "<p align='center'><b>Obtaining token...</b></p>"));
l->addWidget(m_pairingTokenLabel);
auto bb = new QDialogButtonBox;
auto endButton = bb->addButton(i18nc("@button", "End pairing mode"), QDialogButtonBox::RejectRole);
connect(endButton, &QAbstractButton::clicked, this, &QDialog::reject);
m_copyButton = bb->addButton(i18nc("@button", "Copy code to clipboard"), QDialogButtonBox::ActionRole);
m_copyButton->setEnabled(false);
l->addWidget(bb);
setFixedSize(sizeHint());
connect(&WebsocketClient::self(), &WebsocketClient::pairingStatusChanged, this, &PairingDialog::pairingStatusChanged);
WebsocketClient::self().enterPairingMode();
}
void PairingDialog::pairingStatusChanged(const QString& token, bool pairingActive) {
if (!pairingActive) {
reject();
return;
}
m_pairingTokenLabel->setText(QString(u"<p align='center'><b>%1</b></p>").arg(token));
m_copyButton->setEnabled(true);
connect(m_copyButton, &QAbstractButton::clicked, [this, token]() {
qApp->clipboard()->setText(token);
});
}
static QPushButton *makeTestPageButton()
{
auto testPageButton = new QPushButton(i18nc("@button", "Open Test Page"));
testPageButton->setIcon(QIcon::fromTheme(u"external-link-symbolic"_s));
QObject::connect(testPageButton, &QPushButton::clicked, testPageButton, []() {
QDesktopServices::openUrl(QUrl(u"https://"_s + ConnectionController::serverDomain() + u"/test"_s));
});
return testPageButton;
}
class GenerateCertificateWidget : public QWidget {
Q_OBJECT
public:
GenerateCertificateWidget(bool assistant, QWidget *parent)
: QWidget(parent)
, m_controller(nullptr)
, m_installed(false)
, m_dialog(nullptr)
, m_forceSetup(assistant)
{
auto vbox = new QVBoxLayout(this);
vbox->setContentsMargins(0, 0, 0, 0);
m_label = new QLabel();
m_label->setWordWrap(true);
vbox->addWidget(m_label);
auto hbox = new QHBoxLayout();
hbox->setContentsMargins(0, 0, 0, 0);
m_testButton = makeTestPageButton();
hbox->addWidget(m_testButton);
m_generateButton = new QPushButton();
m_generateButton->setVisible(!m_forceSetup);
if (!assistant) {
connect(m_generateButton, &QPushButton::clicked, this, [this, parent]() {
KAssistantDialog dialog(parent);
addAssistantPages(&dialog);
auto cancelButton = dialog.button(QDialogButtonBox::Cancel);
if (cancelButton) {
cancelButton->hide(); // it does not really have defined behavior
}
startGenerate();
dialog.setMinimumSize(dialog.sizeHint() + QSize(50, 50));
dialog.exec();
});
}
hbox->addWidget(m_generateButton);
hbox->addStretch();
vbox->addLayout(hbox);
updateStatus();
}
void addAssistantPages(KAssistantDialog *dialog) {
m_dialog = dialog;
auto genPage = new QWidget();
auto vbox = new QVBoxLayout(genPage);
m_genProgress = new QPlainTextEdit();
vbox->addWidget(m_genProgress);
m_genDoneLabel = new QLabel(i18n("Please wait while the certificate is being generated."));
// certificate fingerprint will be displayed non-breaking. Reserve enough space.
m_genDoneLabel->setMinimumWidth(QFontMetrics(QFontDatabase::systemFont(QFontDatabase::FixedFont)).horizontalAdvance(u"AA:"_s) * 20);
// this is just a rule of thumb estimate, nothing bad will happen, if it's not quite correct
m_genDoneLabel->setMinimumHeight(m_genDoneLabel->fontMetrics().height() * 4);
m_genDoneLabel->setWordWrap(true);
vbox->addWidget(m_genDoneLabel);
m_genPageItem = new KPageWidgetItem(genPage);
m_genPageItem->setHeader(i18n("Generating TLS certificate"));
m_dialog->addPage(m_genPageItem);
auto installPage = new QWidget();
vbox = new QVBoxLayout(installPage);
m_installProgress = new QPlainTextEdit();
vbox->addWidget(m_installProgress);
m_installDoneLabel = new QLabel();
vbox->addWidget(m_installDoneLabel);
m_installPageItem = new KPageWidgetItem(installPage);
m_installPageItem->setHeader(i18n("Installing certificate"));
m_dialog->addPage(m_installPageItem);
connect(dialog, &KPageDialog::currentPageChanged, this,
[this](KPageWidgetItem *current, KPageWidgetItem *) {
if (current == m_genPageItem) {
startGenerate();
} else if (current == m_installPageItem) {
if (!m_installed) {
m_controller->install();
}
}
});
}
void updateStatus() {
if (Config::self()->isLocalServer()) {
setVisible(true);
if (Controller::certificateAlreadyGenerated() && !m_forceSetup) {
m_label->setText(
i18n("A TLS certificate has already been generated. It is not generally necessary to regenerate it, unless the following test page shows "
"any problems."));
m_testButton->setVisible(true);
m_generateButton->setText(i18n("Regenerate certificate"));
m_generateButton->setFixedSize(m_generateButton->minimumSizeHint());
} else {
if (m_dialog) {
m_label->setText(i18n("A TLS certificate is needed for the secure connection to the proxy."));
} else {
m_label->setText(
i18n("A TLS certificate is needed for the secure connection to the proxy. This will be generated and installed in the next step."));
}
m_testButton->setVisible(false);
m_generateButton->setText(i18n("Generate and install certificate"));
}
} else {
setVisible(false);
}
if (m_dialog) {
m_dialog->setAppropriate(m_genPageItem, Config::self()->isLocalServer() && (!Controller::certificateAlreadyGenerated() || m_forceSetup));
m_dialog->setAppropriate(m_installPageItem, Config::self()->isLocalServer() && (!Controller::certificateAlreadyGenerated() || m_forceSetup));
}
}
private:
QLabel *m_label;
QPushButton *m_generateButton;
QPushButton *m_testButton;
QPlainTextEdit *m_genProgress, *m_installProgress;
QLabel *m_genDoneLabel, *m_installDoneLabel;
KPageWidgetItem *m_genPageItem, *m_installPageItem;
QPointer<Controller> m_controller; // it's a KJob and will auto-delete
bool m_installed;
KAssistantDialog *m_dialog;
bool m_forceSetup;
void startGenerate() {
if (m_controller || m_installed) {
return;
}
m_dialog->setValid(m_genPageItem, false);
m_dialog->setValid(m_installPageItem, false);
m_controller = new Controller(this);
m_dialog->setValid(m_genPageItem, false);
connect(m_controller, &Controller::generationDone, this, [this]() {
m_genDoneLabel->setText(StatusDialog::statusIconAsText(StatusDialog::IconOk)
+ i18nc("@info", "Successfully generated certificate with fingerprint<br><tt>%1</tt>", m_controller->rootFingerprint())
+ u"<br>"_s + i18n("Next we will install this certificate. <b>Note:</b> You may be prompted to confirm the fingerprint."));
m_dialog->setValid(m_genPageItem, true);
});
connect(m_controller, &Controller::debutOutput, this, [this](const QString &output) {
m_genProgress->appendPlainText(output);
m_installProgress->appendPlainText(output);
});
connect(m_controller, &Controller::result, this, [this](KJob *) {
if (m_controller->error()) {
m_genProgress->appendPlainText(m_controller->errorText());
m_installProgress->appendPlainText(m_controller->errorText());
m_installDoneLabel->setText(StatusDialog::statusIconAsText(StatusDialog::IconError)
+ i18nc("@info", "Installation of certificate failed.<br><b>Please restart this installer.</b>"));
return;
}
m_installDoneLabel->setText(StatusDialog::statusIconAsText(StatusDialog::IconOk)
+ i18nc("@info", "Installed certificate with fingerprint:<br><tt>%1</tt>", m_controller->rootFingerprint()));
m_label->setText(m_installDoneLabel->text()); // for use in assistant
m_dialog->setValid(m_installPageItem, true);
m_installed = true;
ConnectionController::instance()->startStopLocalServer();
ConnectionController::instance()->startWebsocketClient();
});
m_controller->start();
}
};
class SteppableAssistant : public KAssistantDialog
{
Q_OBJECT
public:
SteppableAssistant(QWidget *parent)
: KAssistantDialog(parent)
{
}
protected:
void next() override
{
bool stay = false;
Q_EMIT overrideNext(&stay);
if (!stay) {
KAssistantDialog::next();
}
}
Q_SIGNALS:
void overrideNext(bool *stay);
};
static QByteArray ampersandEncode(const QString &input)
{
QByteArray encoded;
for(int i = 0; i < input.size(); ++i) {
QChar ch = input.at(i);
if(ch.unicode() > 127) {
encoded += QString(u"&#%1;"_s).arg(static_cast<int>(ch.unicode())).toLatin1();
} else {
encoded += ch.toLatin1();
}
}
return encoded;
}
void DialogController::doDialog(const QList<PageID> &pageIds, const bool assistant, QWidget *parent) {
KPageDialog *dialog = assistant ? new SteppableAssistant(parent) : new KPageDialog(parent);
KPageWidgetItem *activePageItem = nullptr;
auto cancelButton = dialog->button(QDialogButtonBox::Cancel);
if (cancelButton) {
cancelButton->hide(); // it does not really have defined behavior
}
if (pageIds.contains(PageWelcome)) {
auto widget = new QWidget();
auto vbox = new QVBoxLayout(widget);
auto item = new KPageWidgetItem(widget, i18nc("@title", "Welcome"));
item->setHeader(i18nc("@title", "Welcome to GpgOL/Web"));
auto label =
new QLabel(i18n("<p>Before the first use, the GpgOL/Web Outlook Add-in has to be set up and connected to Outlook. This assistant will guide you "
"through the required steps.</p>"));
label->setWordWrap(true);
vbox->addWidget(label);
vbox->addStretch();
dialog->addPage(item);
activePageItem = item;
}
if (pageIds.contains(PageProxy)) {
auto widget = new QWidget();
auto vbox = new QVBoxLayout(widget);
auto item = new KPageWidgetItem(widget, i18nc("@title", "Proxy & SSL"));
item->setHeader(i18nc("@title", "SSL Certificate"));
item->setIcon(QIcon::fromTheme(u"applications-network"_s));
dialog->addPage(item);
auto certcontrol = new GenerateCertificateWidget(assistant, dialog);
if (assistant) {
certcontrol->addAssistantPages(static_cast<KAssistantDialog*>(dialog));
}
vbox->addWidget(certcontrol);
vbox->addStretch();
if (pageIds.first() == PageProxy) {
activePageItem = item;
}
}
if (pageIds.contains(PageInstallAddin)) {
auto widget = new QWidget();
auto vbox = new QVBoxLayout(widget);
auto item = new KPageWidgetItem(widget, i18nc("@title", "Add-In"));
item->setHeader(i18nc("@title", "Install Outlook Add-In"));
item->setIcon(QIcon::fromTheme(u"extension-symbolic"_s));
auto grid = new QGridLayout();
auto addGridRow = [](QGridLayout *grid, bool number, const QString &label, QWidget *button) {
const int row = grid->rowCount();
if (number) {
auto num = new QLabel(u"%1."_s.arg(row));
grid->addWidget(num, row, 0, Qt::AlignTop);
}
auto lbl = new QLabel(label);
lbl->setWordWrap(true);
grid->addWidget(lbl, row, 0 + number, 1, 3 - number - (button != nullptr), Qt::AlignTop);
if (button) {
grid->addWidget(button, row, 2, Qt::AlignTop);
grid->setColumnMinimumWidth(1, button->sizeHint().width() * 2);
}
// Make layout look more structured
grid->setRowMinimumHeight(row, lbl->fontMetrics().height() * 3);
};
auto lbl = new QLabel(i18n("Before the first use, the add-in has to be activated in Outlook:"));
lbl->setWordWrap(true);
vbox->addWidget(lbl);
auto extMgrButton = new QPushButton(i18nc("@button", "Outlook Extension Manager"));
extMgrButton->setToolTip(i18n("Click to open the Outlook Extension Manager in your web browser. You may be prompted to log in. Please allow a few seconds for the page to load."));
extMgrButton->setIcon(QIcon::fromTheme(u"external-link-symbolic"_s));
addGridRow(grid, true, i18n("Open the Outlook Extension Manager (you may be prompted to log in):"), extMgrButton);
auto generateManifestButton = new QPushButton(i18nc("@button", "Copy Manifest"));
generateManifestButton->setIcon(QIcon::fromTheme(u"edit-copy"_s));
addGridRow(grid, true, i18n("Generate a manifest file (the filename will be copied to the clipboard):"), generateManifestButton);
addGridRow(grid, true, i18n("In Outlook, register this via <tt>My Add-Ins -> Custom Add-Ins -> Add a custom Add-In -> Add from file...</tt>"), nullptr);
addGridRow(grid, true, i18n("In Outlook, select any e-mail, and activate the add-in by clicking the GnuPG icon <nobr>( %1 )</nobr> shown above the email header.",
u"<img src=':/icons/addin_logo.png' height='%1'>"_s.arg(QFontMetrics(dialog->font()).ascent())), nullptr);
QObject::connect(extMgrButton, &QPushButton::clicked, dialog, []() {
QDesktopServices::openUrl(QUrl(u"https://aka.ms/olksideload"_s));
});
QObject::connect(generateManifestButton, &QPushButton::clicked, dialog, [generateManifestButton]() {
QFile file(u":/gpgol-client/manifest.xml.in"_s);
if (!file.open(QIODeviceBase::ReadOnly)) {
Q_ASSERT(false);
return;
}
QByteArray manifest = file.readAll();
manifest.replace("%HOST%", ConnectionController::serverDomain().toUtf8());
manifest.replace("%VERSION%", GPGOLWEB_VERSION_STRING);
// HACK: For a manifest loaded from local file - as single users will do - MS does not apply translations.
// They claim it's a feature, not a bug. To work around this, we localize the default value, here, instead.
// At the same time, we also have to keep translations in the manifest, so as not to break translations
// for origanization-installed manifests.
int offset = 0;
const auto attrib = QByteArray("DefaultValue=\"");
while ((offset = manifest.indexOf("GPGOLI18N=\"true\"", offset)) > -1) {
int strBegin = manifest.indexOf(attrib, offset) + attrib.length();
Q_ASSERT(strBegin > attrib.length());
int strEnd = manifest.indexOf("\"", strBegin);
Q_ASSERT(strEnd > 0);
const auto translation = ki18nd("gpgol-js-manifest", manifest.mid(strBegin, strEnd-strBegin).constData()).toString();
manifest.replace(offset, strEnd-offset, QByteArray(attrib + ampersandEncode(translation).constData()));
}
const auto saveFilePath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/gpgol-web-manifest.xml"_s;
QSaveFile saveFile(saveFilePath);
if (!saveFile.open(QIODeviceBase::WriteOnly)) {
Q_ASSERT(false);
return;
}
saveFile.write(manifest);
saveFile.commit();
QGuiApplication::clipboard()->setText(saveFilePath);
QToolTip::showText(generateManifestButton->mapToGlobal(QPoint(10, 10)), i18n("Copied to clipboard."), generateManifestButton, QRect(), 2000);
});
if (!Config::isLocalServer()) {
auto pairingButton = new QPushButton(i18nc("@button", "Enter Pairing Mode"));
pairingButton->setIcon(QIcon::fromTheme(u"network-connect"_s));
addGridRow(grid, true, i18n("When prompted for a pairing code, click here to enter pairing mode:"), pairingButton);
QObject::connect(pairingButton, &QPushButton::clicked, dialog, [dialog]() {
PairingDialog d(dialog);
d.exec();
WebsocketClient::self().quitPairingMode();
});
}
vbox->addLayout(grid);
vbox->addStretch();
dialog->addPage(item);
if (assistant) {
for (int row = 2; row < grid->rowCount(); ++row) {
for (int col = 0; col < grid->columnCount(); ++col) {
auto w = grid->itemAtPosition(row, col)->widget();
if (w) {
w->setEnabled(false);
}
}
}
QObject::connect(static_cast<SteppableAssistant *>(dialog), &SteppableAssistant::overrideNext, grid, [grid, dialog, item](bool *stay) {
if (dialog->currentPage() != item) {
return;
}
// On this page, highlight the steps one by one, by enabling them in sequence.
// It is quite intentional that this stepping is only done once, i.e. subsequent
// clicks on Back/Next will simply change the page, as usual
for (int row = 1; row < grid->rowCount(); ++row) {
auto w = grid->itemAtPosition(row, 0)->widget();
if (w && !w->isEnabled()) {
for (int col = 0; col < grid->columnCount(); ++col) {
w = grid->itemAtPosition(row, col)->widget();
if (w) {
w->setEnabled(true);
}
}
*stay = true;
return;
}
}
});
}
if (pageIds.first() == PageInstallAddin) {
activePageItem = item;
}
}
if (pageIds.contains(PageSettings)) {
auto widget = new QWidget();
auto vbox = new QVBoxLayout(widget);
auto startupbox = new QGroupBox(i18n("Startup Behavior"));
auto boxlayout = new QVBoxLayout(startupbox);
#ifdef Q_OS_WIN
auto autoStartBox = new QCheckBox(i18n("Start GpgOL/Web service on login"));
// We intentionally don't use our own config for this: If users disable autostart via
// the Windows settings menu, we want to respect that, too.
{
QSettings winreg(u"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"_s, QSettings::NativeFormat);
autoStartBox->setChecked(!winreg.value(QCoreApplication::applicationName()).toString().isEmpty());
}
QObject::connect(autoStartBox, &QCheckBox::checkStateChanged, dialog, [](Qt::CheckState state) {
QSettings winreg(u"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"_s, QSettings::NativeFormat);
if (state) {
winreg.setValue(QCoreApplication::applicationName(),
QDir::toNativeSeparators(QCoreApplication::applicationFilePath()));
} else {
winreg.remove(QCoreApplication::applicationName());
}
});
boxlayout->addWidget(autoStartBox);
#endif
auto showOnStartup = new QCheckBox(i18n("Show status dialog on startup"));
showOnStartup->setChecked(Config::self()->showLauncher());
QObject::connect(showOnStartup, &QCheckBox::checkStateChanged, dialog, [](Qt::CheckState state) {
Config::self()->setShowLauncher(state == Qt::Checked);
Config::self()->save();
});
boxlayout->addWidget(showOnStartup);
vbox->addWidget(startupbox);
auto featuresbox = new QGroupBox(i18n("Optional Features"));
boxlayout = new QVBoxLayout(featuresbox);
auto reencrypt = new QCheckBox(i18n("Offer reencryption of email folders with new keys"), featuresbox);
reencrypt->setChecked(Config::self()->reencrypt());
QObject::connect(reencrypt, &QCheckBox::checkStateChanged, dialog, [](Qt::CheckState state) {
Config::self()->setReencrypt(state == Qt::Checked);
Config::self()->save();
});
boxlayout->addWidget(reencrypt);
vbox->addWidget(featuresbox);
vbox->addStretch();
auto item = new KPageWidgetItem(widget, i18nc("@title", "Options"));
item->setHeader(i18nc("@title", "Options"));
item->setIcon(QIcon::fromTheme(u"configure-symbolic"_s));
dialog->addPage(item);
if (pageIds.first() == PageSettings) {
activePageItem = item;
}
}
if (pageIds.contains(PageTroubleshooting)) {
auto widget = new QWidget();
auto vbox = new QVBoxLayout(widget);
auto item = new KPageWidgetItem(widget, i18nc("@title", "Troubleshooting"));
item->setHeader(i18nc("@title", "Troubleshooting"));
item->setIcon(QIcon::fromTheme(u"state-warning"_s));
auto hbox = new QHBoxLayout();
auto label = new QLabel(i18n("Test for problems with the TLS-certificate installation, by opening this test page in your browser:"));
label->setWordWrap(true);
hbox->addWidget(label);
hbox->addWidget(makeTestPageButton());
vbox->addLayout(hbox);
auto group = new QGroupBox(i18n("Log Diagnostic Messages"));
group->setCheckable(true);
group->setChecked(Config::self()->customDebug());
QObject::connect(group, &QGroupBox::toggled, group, [group]() {
Config::self()->setCustomDebug(group->isChecked());
Config::self()->save();
});
auto groupbox = new QVBoxLayout(group);
hbox = new QHBoxLayout();
hbox->addWidget(new QLabel(i18n("Verbosity:")));
auto levelbox = new QComboBox();
levelbox->addItem(i18n("All Messages"), QVariant(QtDebugMsg));
levelbox->addItem(i18n("Informational"), QVariant(QtInfoMsg));
levelbox->addItem(i18n("Warnings and Errors"), QVariant(QtWarningMsg));
levelbox->addItem(i18n("Errors, only"), QVariant(QtCriticalMsg));
levelbox->setCurrentIndex(levelbox->findData(static_cast<QtMsgType>(Config::self()->logLevel())));
QObject::connect(levelbox, &QComboBox::currentIndexChanged, levelbox, [levelbox]() {
Config::self()->setLogLevel(levelbox->currentData().toInt());
Config::self()->save();
});
hbox->addWidget(levelbox);
groupbox->addLayout(hbox);
hbox = new QHBoxLayout();
hbox->addWidget(new QLabel(i18n("Log File Path:")));
auto pathbox = new KUrlRequester();
pathbox->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly);
const auto path = Config::self()->logPath();
pathbox->setUrl(QUrl::fromLocalFile(path.isEmpty() ? QStandardPaths::writableLocation(QStandardPaths::HomeLocation) : path));
QObject::connect(pathbox, &KUrlRequester::urlSelected, pathbox, [pathbox]() {
Config::self()->setLogPath(pathbox->url().toLocalFile());
Config::self()->save();
});
hbox->addWidget(pathbox);
groupbox->addLayout(hbox);
// NOTE: client log settings could be made to apply, immediately, proxy log settings not so easily
groupbox->addWidget(new QLabel(i18n("<b>These settings take effect after a restart.</b>")));
vbox->addWidget(group);
vbox->addStretch();
dialog->addPage(item);
if (pageIds.first() == PageTroubleshooting) {
activePageItem = item;
}
}
dialog->setCurrentPage(activePageItem);
dialog->exec();
}
void DialogController::doFirstTimeAssistant()
{
doDialog(QList{ PageWelcome, PageProxy, PageInstallAddin, PageSettings}, true);
}
#ifdef Q_OS_WIN
#include <windows.h>
#endif
void DialogController::strongActivateWindow(QWidget *window)
{
window->show();
window->activateWindow();
window->raise();
#ifdef Q_OS_WIN
// HACK: Simulate Alt-keyPress while bringing the window to the front.
// This helps when our app does not currently have focus - and
// frequently it does not, because we have just clicked in browser/outlook.
// https://stackoverflow.com/questions/72620538/whats-the-correct-way-to-bring-a-window-to-the-front
keybd_event(VK_MENU, 0, 0, 0);
auto hwnd = HWND(window->winId());
SetForegroundWindow(hwnd);
keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, 0);
// Sometimes Qt may act as if Alt was still pressed, after the above sequence.
// Send it another key release event, explicitly.
auto e = new QKeyEvent(QEvent::KeyRelease, Qt::Key_Alt, Qt::AltModifier);
qApp->postEvent(window, e);
#endif
}
void DialogController::invokeKleopatra(const QStringList &_args, QWidget *parent)
{
auto args = _args;
if (parent) {
args.append(QStringLiteral("--parent-windowid"));
args.append(QString::number(parent->winId()));
}
+ // Always create a new instance: Created windows will usually hide in the
+ // background, without this
+ args.append(u"--standalone"_s);
+
QString exec;
#ifdef Q_OS_WIN
exec = QStandardPaths::findExecutable(u"kleopatra"_s, {QCoreApplication::applicationDirPath()});
#endif
if (exec.isEmpty()) {
exec = QStandardPaths::findExecutable(u"kleopatra"_s);
}
if (exec.isEmpty() || !QProcess::startDetached(exec, args)) {
qCWarning(GPGOL_CLIENT_LOG) << "Unable to execute kleopatra";
KMessageBox::error(parent,
i18n("The kleopatra executable could not be invoked. Please check your installation."),
i18n("Unable to start kleopatra"));
}
}
// It's a bit silly, but to be able to call strongActivateWindow on a KMessageBox, we have to replicate a bunch of KMessageBox code...
static KMessageBox::ButtonCode strongActivatedQuestionBox(QWidget *parent,
const QString &message,
const QString &title,
const KGuiItem &yesItem,
const KGuiItem &noItem,
const KGuiItem &cancelItem,
const QString &dontAskAgainName)
{
KMessageBox::ButtonCode res;
if (!KMessageBox::shouldBeShownTwoActions(dontAskAgainName, res)) {
return res;
}
auto dialog = new QDialog(parent);
dialog->setWindowTitle(title);
QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog);
if (!yesItem.text().isEmpty()) {
KGuiItem::assign(buttonBox->addButton(QDialogButtonBox::Yes), yesItem);
}
if (!noItem.text().isEmpty()) {
KGuiItem::assign(buttonBox->addButton(QDialogButtonBox::No), noItem);
}
if (!cancelItem.text().isEmpty()) {
KGuiItem::assign(buttonBox->addButton(QDialogButtonBox::Cancel), cancelItem);
}
bool dontAskAgain = false;
createKMessageBox(dialog,
buttonBox,
QMessageBox::Question,
message,
QStringList(),
QApplication::translate("KMessageBox", "Do not ask again"),
&dontAskAgain,
KMessageBox::Notify | KMessageBox::NoExec);
DialogController::strongActivateWindow(dialog);
const auto result = QDialogButtonBox::StandardButton(dialog->exec());
res =
(result == QDialogButtonBox::Yes ? KMessageBox::PrimaryAction : (result == QDialogButtonBox::No ? KMessageBox::SecondaryAction : KMessageBox::Cancel));
if (dontAskAgain) {
saveDontShowAgainTwoActions(dontAskAgainName, res);
}
delete dialog;
return res;
}
void DialogController::onWebClientConnection(const QString &email)
{
// This gets called every single time user selects a new message, so don't aks too often
static QSet<QString> emailAccountsConnectedThisSession;
if (emailAccountsConnectedThisSession.contains(email)) {
return;
}
emailAccountsConnectedThisSession.insert(email);
auto keys = Kleo::KeyCache::instance()->findByEMailAddress(email.toStdString());
qCDebug(GPGOL_CLIENT_LOG) << keys.size() << "secret keys found for connected account" << email;
bool anySecretKey = false;
bool expiredSecretKey = false;
bool disabledSecretKey = false;
bool revokedSecretKey = false;
bool foundGoodKey = false;
for (const auto &key : keys) {
if (key.hasSecret() && !key.isBad()) {
foundGoodKey = true;
break;
}
if (key.hasSecret()) {
anySecretKey = true;
// check non-recoverable problems, first
if (key.isRevoked() || key.isInvalid()) {
revokedSecretKey = true;
qCInfo(GPGOL_CLIENT_LOG) << "Revoked" << key.keyID();
} else if (key.isExpired()) {
expiredSecretKey = true;
qCInfo(GPGOL_CLIENT_LOG) << "Expired" << key.keyID();
} else if (key.isDisabled()) {
disabledSecretKey = true;
qCInfo(GPGOL_CLIENT_LOG) << "Disabled" << key.keyID();
}
} else {
qCInfo(GPGOL_CLIENT_LOG) << "Only pubkey" << key.keyID();
}
}
if (!foundGoodKey) {
if (keys.size()) { // found at least something
QString message;
if (!anySecretKey) {
message = i18n(
"<p>A public key, but no secret key was found for the email account <tt>%1</tt>.</p><p>You may want to import an existing key pair "
"using the Kleopatra key management tool. Make sure the entry for the given email address is shown in bold font face, indicating that "
"a secret key is available.</p>",
email);
} else if (expiredSecretKey) {
message = i18n(
"<p>A secret key was found for the email account <tt>%1</tt>, but it has expired. You will be able to decrypt messages encrypted for "
"this key, but you will not be able to sign messages.</p><p>You may want to prolong the expiration date or to generate a new key pair "
"using the Kleopatra key management tool.</p>",
email);
} else if (disabledSecretKey) {
message = i18n(
"<p>A secret key was found for the email account <tt>%1</tt>, but it was set to disabled, and cannot currently be used for decryption "
"or signing.</p><p>You may want open the Kleopatra key management tool to re-activate the key, or to create a new key pair.</p>",
email);
} else if (revokedSecretKey) {
message = i18n(
"<p>A secret key was found for the email account <tt>%1</tt>, but it has been revoked. You will be able to decrypt messages encrypted "
"for this key, but you will not be able to sign messages.</p><p>You may want open the Kleopatra key management tool to create a new "
"key pair.</p>",
email);
} else {
i18n(
"<p>One or several secret keys are installed matching the email account <tt>%1</tt>, but none are currently usable. You will probably "
"want to investigate the situation and/or generate a fresh key pair now.</p>",
email);
}
auto res = strongActivatedQuestionBox(nullptr,
message,
i18n("No usable secret key"),
KGuiItem(i18nc("@button", "Open key management dialog")),
KGuiItem(i18n("Ignore")),
KGuiItem(),
u"nowarnunusablekey_%1"_s.arg(email));
if (res == KMessageBox::PrimaryAction) {
invokeKleopatra(QStringList());
}
} else { // no known key at all
auto res = strongActivatedQuestionBox(
nullptr,
i18n("<p>GpgOL/Web was successfully connected to the email account <tt>%1</tt>, but no secret key is known matching this email "
"address.</p><p>If you are just starting to use GnuPG with this account, you will probably want to generate a fresh key pair now. "
"Otherwise you may want to export an existing key pair using the key management dialog.</p>",
email),
i18n("No secret key available"),
KGuiItem(i18nc("@button", "Generate new key pair")),
KGuiItem(i18nc("@button", "Open key management dialog")),
KGuiItem(i18n("Ignore")),
u"nowarnkeymissing_%1"_s.arg(email));
if (res == KMessageBox::PrimaryAction) {
invokeKleopatra(QStringList{u"--gen-key"_s});
} else if (res == KMessageBox::SecondaryAction) {
invokeKleopatra(QStringList());
}
}
}
}
#include "setupdialogs.moc"

File Metadata

Mime Type
text/x-diff
Expires
Sat, Apr 25, 3:24 PM (1 d, 16 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
9f/9d/e84504cffb34bb282c80dd547e9b

Event Timeline