Page MenuHome GnuPG

No OneTemporary

diff --git a/CMakeLists.txt b/CMakeLists.txt
index eb81b0e..a4ec47d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,93 +1,92 @@
# SPDX-FileCopyrightText: 2023 g10 code Gmbh
# SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
# SPDX-License-Identifier: BSD-2-Clause
cmake_minimum_required(VERSION 3.20)
set(VERSION "0.9.0")
project(gpgoljs VERSION ${VERSION})
set(KF_MIN_VERSION "6.0.0")
set(QT_MIN_VERSION "6.6.0")
set(MIMETREEPARSER_VERSION "6.3.41")
set(LIBKLEO_VERSION "6.2.0")
set(LIBKDEPIM_VERSION "6.2.0")
set(GPG_ERROR_REQUIRED_VERSION "1.36")
find_package(ECM ${KF_MIN_VERSION} CONFIG REQUIRED)
set(CMAKE_MODULE_PATH
${CMAKE_MODULE_PATH}
${ECM_MODULE_PATH}
)
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
include(FeatureSummary)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(KDEGitCommitHooks)
include(KDECMakeSettings)
include(KDEClangFormat)
include(ECMQtDeclareLoggingCategory)
include(ECMAddTests)
include(ECMSetupVersion)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
ecm_setup_version(${VERSION} VARIABLE_PREFIX GPGOLJS VERSION_HEADER
"${CMAKE_CURRENT_BINARY_DIR}/client/gpgoljs_version.h")
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core HttpServer Widgets PrintSupport WebSockets)
set_package_properties(Qt6 PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
)
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Contacts Completion CoreAddons WidgetsAddons Config ColorScheme Codecs XmlGui GuiAddons JobWidgets Sonnet CalendarCore Archive)
set_package_properties(KF6 PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
)
find_package(KPim6Libkleo ${LIBKLEO_VERSION} CONFIG REQUIRED)
find_package(KPim6Libkdepim ${LIBKDEPIM_LIB_VERSION} CONFIG REQUIRED)
find_package(KPim6MimeTreeParserWidgets ${MIMETREEPARSER_VERSION} CONFIG REQUIRED)
find_package(LibGpgError ${GPG_ERROR_REQUIRED_VERSION} REQUIRED)
# Optional packages
if (WIN32)
# Only a replacement available for Windows so this
# is required on other platforms.
find_package(KF6DBusAddons ${KF_MIN_VERSION} CONFIG)
set_package_properties(KF6DBusAddons PROPERTIES DESCRIPTION "Support library to work with DBus"
PURPOSE "DBus session integration"
URL "https://inqlude.org/libraries/kdbusaddons.html"
TYPE OPTIONAL)
else()
find_package(KF6DBusAddons ${KF_MIN_VERSION} CONFIG REQUIRED)
find_package(KF6Auth ${KF_MIN_VERSION} CONFIG REQUIRED)
set(_gpgol_dbusaddons_libs KF6::DBusAddons)
endif()
if (BUILD_TESTING)
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
endif()
add_subdirectory(common)
add_subdirectory(server)
add_subdirectory(client)
-add_subdirectory(gpgol-cert-generator)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES client/*.cpp client/*.h server/*.cpp server/*.h)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
index baf8e12..af7bc3d 100644
--- a/client/CMakeLists.txt
+++ b/client/CMakeLists.txt
@@ -1,327 +1,328 @@
# SPDX-FileCopyrightText: 2023 g10 code GmbH
# SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
# SPDX-License-Identifier: BSD-2-Clause
add_definitions(-DDATAROUTDIR="${KDE_INSTALL_FULL_DATAROOTDIR}")
add_subdirectory(rootcagenerator)
add_library(gpgol-client-static STATIC)
target_sources(gpgol-client-static PRIVATE
emailviewer.cpp
emailviewer.h
websocketclient.cpp
websocketclient.h
messagedispatcher.cpp
messagedispatcher.h
firsttimedialog.cpp
firsttimedialog.h
# Identity
identity/addressvalidationjob.cpp
identity/addressvalidationjob.h
identity/identitymanager.cpp
identity/identitymanager.h
identity/identitydialog.cpp
identity/identitydialog.h
identity/identity.cpp
identity/identity.h
identity/signature.h
identity/signature.cpp
identity/signatureconfigurator.cpp
identity/signatureconfigurator.h
identity/signaturerichtexteditor.cpp
identity/signaturerichtexteditor_p.h
# Draft
draft/draft.cpp
draft/draft.h
draft/draftmanager.cpp
draft/draftmanager.h
# EWS integration
ews/ewsattachment.cpp
ews/ewsattachment.h
ews/ewsattendee.cpp
ews/ewsattendee.h
ews/ewsclient_debug.cpp
ews/ewsclient_debug.h
ews/ewsid.cpp
ews/ewsid.h
ews/ewsitem.cpp
ews/ewsitem.h
ews/ewsitembase.cpp
ews/ewsitembase.h
ews/ewsitembase_p.h
ews/ewsmailbox.cpp
ews/ewsmailbox.h
ews/ewsmessagedispatcher.cpp
ews/ewsmessagedispatcher.h
ews/ewsoccurrence.cpp
ews/ewsoccurrence.h
ews/ewspropertyfield.cpp
ews/ewspropertyfield.h
ews/ewsrecurrence.cpp
ews/ewsrecurrence.h
ews/ewsserverversion.cpp
ews/ewsserverversion.h
ews/ewstypes.cpp
ews/ewstypes.h
ews/ewsxml.cpp
ews/ewsxml.h
ews/ewscreateitemrequest.cpp
ews/ewscreateitemrequest.h
ews/ewsrequest.cpp
ews/ewsrequest.h
ews/ewsjob.cpp
ews/ewsjob.h
# Editor
editor/addresseelineedit.cpp
editor/addresseelineedit.h
editor/addresseelineeditmanager.cpp
editor/addresseelineeditmanager.h
editor/bodytexteditor.cpp
editor/bodytexteditor.h
editor/composerviewbase.cpp
editor/composerviewbase.h
editor/composerwindow.cpp
editor/composerwindow.h
editor/composerwindowfactory.cpp
editor/composerwindowfactory.h
editor/cryptostateindicatorwidget.cpp
editor/cryptostateindicatorwidget.h
editor/kmcomposerglobalaction.cpp
editor/kmcomposerglobalaction.h
editor/nearexpirywarning.cpp
editor/nearexpirywarning.h
editor/mailtemplates.cpp
editor/mailtemplates.h
editor/recipient.cpp
editor/recipient.h
editor/recipientline.cpp
editor/recipientline.h
editor/recipientseditor.cpp
editor/recipientseditor.h
editor/util.h
editor/util.cpp
editor/kmailcompletion.cpp
editor/kmailcompletion.h
editor/composersignatures.cpp
editor/composersignatures.h
editor/nodehelper.cpp
editor/nodehelper.h
editor/signaturecontroller.cpp
editor/signaturecontroller.h
editor/spellcheckerconfigdialog.cpp
editor/spellcheckerconfigdialog.h
# Editor job
editor/job/abstractencryptjob.h
editor/job/autocryptheadersjob.h
editor/job/contentjobbase.h
editor/job/contentjobbase_p.h
editor/job/composerjob.cpp
editor/job/composerjob.h
editor/job/encryptjob.h
editor/job/inserttextfilejob.h
editor/job/itipjob.h
editor/job/jobbase.h
editor/job/jobbase_p.h
editor/job/maintextjob.h
editor/job/multipartjob.h
editor/job/protectedheadersjob.h
editor/job/signencryptjob.h
editor/job/signjob.h
editor/job/singlepartjob.h
editor/job/skeletonmessagejob.h
editor/job/transparentjob.h
editor/job/autocryptheadersjob.cpp
editor/job/contentjobbase.cpp
editor/job/encryptjob.cpp
editor/job/inserttextfilejob.cpp
editor/job/itipjob.cpp
editor/job/jobbase.cpp
editor/job/maintextjob.cpp
editor/job/multipartjob.cpp
editor/job/protectedheadersjob.cpp
editor/job/saveasfilejob.cpp
editor/job/saveasfilejob.h
editor/job/signencryptjob.cpp
editor/job/signjob.cpp
editor/job/singlepartjob.cpp
editor/job/skeletonmessagejob.cpp
editor/job/transparentjob.cpp
## Editor Part
editor/part/globalpart.h
editor/part/infopart.h
editor/part/itippart.h
editor/part/messagepart.h
editor/part/textpart.h
editor/part/globalpart.cpp
editor/part/infopart.cpp
editor/part/itippart.cpp
editor/part/messagepart.cpp
editor/part/textpart.cpp
## Attachment
editor/attachment/attachmentjob.cpp
editor/attachment/attachmentjob.h
editor/attachment/attachmentclipboardjob.cpp
editor/attachment/attachmentclipboardjob.h
editor/attachment/attachmentcompressjob.cpp
editor/attachment/attachmentcompressjob.h
editor/attachment/attachmentcontroller.cpp
editor/attachment/attachmentcontroller.h
editor/attachment/attachmentcontrollerbase.cpp
editor/attachment/attachmentcontrollerbase.h
editor/attachment/attachmentfromfolderjob.cpp
editor/attachment/attachmentfromfolderjob.h
editor/attachment/attachmentfrommimecontentjob.cpp
editor/attachment/attachmentfrommimecontentjob.h
editor/attachment/attachmentfromurlbasejob.cpp
editor/attachment/attachmentfromurlbasejob.h
editor/attachment/attachmentfromurljob.cpp
editor/attachment/attachmentfromurljob.h
editor/attachment/attachmentfromurlutils.cpp
editor/attachment/attachmentfromurlutils.h
editor/attachment/attachmentfrompublickeyjob.cpp
editor/attachment/attachmentfrompublickeyjob.h
editor/attachment/attachmentloadjob.cpp
editor/attachment/attachmentloadjob.h
editor/attachment/attachmentmodel.cpp
editor/attachment/attachmentmodel.h
editor/attachment/attachmentpart.cpp
editor/attachment/attachmentpart.h
editor/attachment/attachmentpropertiesdialog.cpp
editor/attachment/attachmentpropertiesdialog.h
editor/attachment/attachmentupdatejob.cpp
editor/attachment/attachmentupdatejob.h
editor/attachment/attachmentview.cpp
editor/attachment/attachmentview.h
utils/kuniqueservice.h
utils/kuniqueservice.cpp
utils/systemtrayicon.h
utils/systemtrayicon.cpp
)
if (WIN32)
add_definitions(-DHAVE_QDBUS=false)
target_sources(gpgol-client-static PRIVATE utils/kuniqueservice_win.cpp)
else()
add_definitions(-DHAVE_QDBUS=true)
target_sources(gpgol-client-static PRIVATE utils/kuniqueservice_dbus.cpp)
endif()
ki18n_wrap_ui(gpgol-client-static
editor/attachment/ui/attachmentpropertiesdialog.ui
editor/attachment/ui/attachmentpropertiesdialog_readonly.ui
)
ecm_qt_declare_logging_category(gpgol-client-static_SRCS
HEADER gpgol_client_debug.h
IDENTIFIER GPGOL_CLIENT_LOG
CATEGORY_NAME org.gpgol.client
DESCRIPTION "General client log"
EXPORT GPGOL
)
ecm_qt_declare_logging_category(gpgol-client-static_SRCS
HEADER websocket_debug.h
IDENTIFIER WEBSOCKET_LOG
CATEGORY_NAME org.gpgol.client.websocket
DESCRIPTION "Websocket connection in the client"
EXPORT GPGOL
)
ecm_qt_declare_logging_category(gpgol-client-static_SRCS
HEADER ewsresource_debug.h
IDENTIFIER EWSRES_LOG
CATEGORY_NAME org.gpgol.ews
DESCRIPTION "Ews mail client"
EXPORT GPGOL
)
ecm_qt_declare_logging_category(gpgol-client-static_SRCS
HEADER ewscli_debug.h
IDENTIFIER EWSCLI_LOG
CATEGORY_NAME org.gpgol.ews.client
DESCRIPTION "ews client (gpgol-client)"
EXPORT GPGOL
)
ecm_qt_declare_logging_category(gpgol-client-static_SRCS
HEADER editor_debug.h
IDENTIFIER EDITOR_LOG
CATEGORY_NAME org.gpgol.editor
DESCRIPTION "mail composer"
EXPORT GPGOL
)
set(WARN_TOOMANY_RECIPIENTS_DEFAULT true)
set(ALLOW_SEMICOLON_AS_ADDRESS_SEPARATOR_DEFAULT true)
configure_file(editor/settings/messagecomposer.kcfg.in ${CMAKE_CURRENT_BINARY_DIR}/messagecomposer.kcfg)
kconfig_add_kcfg_files(gpgol-client-static editor/settings/messagecomposersettings.kcfgc config.kcfgc)
install(FILES composerui.rc DESTINATION ${KDE_INSTALL_KXMLGUIDIR}/gpgol-client)
target_sources(gpgol-client-static PUBLIC ${gpgol-client-static_SRCS})
ki18n_wrap_ui(gpgol-client-static firsttimedialog.ui)
target_link_libraries(gpgol-client-static PUBLIC
common
+ rootcagenerator
LibGpgError::LibGpgError
Qt6::HttpServer
Qt6::Widgets
Qt6::PrintSupport
Qt6::WebSockets
KF6::I18n
KF6::JobWidgets
KF6::CalendarCore
KF6::ConfigCore
KF6::ConfigGui
KF6::Contacts
KF6::Completion
KF6::CoreAddons
KF6::ColorScheme
KF6::Codecs
KF6::GuiAddons
KF6::SonnetUi
KF6::SonnetCore
KF6::WidgetsAddons
KF6::XmlGui
KF6::Archive
KPim6::MimeTreeParserCore
KPim6::MimeTreeParserWidgets
KPim6::Libkleo
KPim6::Libkdepim
${_gpgol_dbusaddons_libs}
)
add_executable(gpgol-client main.cpp)
target_link_libraries(gpgol-client PRIVATE gpgol-client-static)
qt_add_resources(gpgol-client
PREFIX
"/"
FILES
assets/64-gpgpass.png
)
if (BUILD_TESTING)
add_subdirectory(autotests)
endif()
install(TARGETS gpgol-client ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES com.gnupg.gpgoljs.desktop DESTINATION ${KDE_INSTALL_APPDIR})
install(FILES com.gnupg.gpgoljs.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps)
diff --git a/client/firsttimedialog.cpp b/client/firsttimedialog.cpp
index 25d0c5e..f339c20 100644
--- a/client/firsttimedialog.cpp
+++ b/client/firsttimedialog.cpp
@@ -1,71 +1,69 @@
// SPDX-FileCopyrightText: 2024 g10 code Gmbh
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "firsttimedialog.h"
#include "config.h"
#include "gpgoljs_version.h"
#include "ui_firsttimedialog.h"
-#include "websocketclient.h"
#include <QClipboard>
#include <QCloseEvent>
#include <QDesktopServices>
using namespace Qt::StringLiterals;
FirstTimeDialog::FirstTimeDialog(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::FirstTimeDialog)
{
ui->setupUi(this);
// center vertically
ui->mainLayout->insertStretch(0);
ui->mainLayout->addStretch();
ui->mainLayoutManifest->insertStretch(0);
ui->mainLayoutManifest->addStretch();
const double titleFontSize = QApplication::font().pointSize() * 2;
auto font = ui->title->font();
font.setPointSize(titleFontSize);
ui->title->setFont(font);
QPixmap logo = QIcon::fromTheme(u"com.gnupg.gpgoljs"_s).pixmap(48, 48);
ui->logo->setPixmap(logo);
ui->version->setText(i18nc("@info", "Version: %1", QString::fromLocal8Bit(GPGOLJS_VERSION_STRING)));
- ui->state->setText(WebsocketClient::self().stateDisplay());
connect(ui->setupButton, &QPushButton::clicked, this, [this]() {
ui->stack->setCurrentIndex(1);
});
ui->manifestPath->setText(QLatin1StringView(DATAROUTDIR) + u"/gpgol/manifest.xml"_s);
connect(ui->openOutlookButton, &QPushButton::clicked, this, []() {
QDesktopServices::openUrl(QUrl(u"https://outlook.office.com/mail/jsmvvmdeeplink/?path=/options/manageapps&bO=4"_s));
});
connect(ui->manifestPathCopy, &QPushButton::clicked, this, [this]() {
QGuiApplication::clipboard()->setText(ui->manifestPath->text());
});
ui->showOnStartup->setChecked(Config::self()->showLauncher());
connect(ui->showOnStartup, &QCheckBox::toggled, this, [](bool checked) {
Config::self()->setShowLauncher(checked);
Config::self()->save();
});
}
-void FirstTimeDialog::slotStateChanged()
+void FirstTimeDialog::slotStateChanged(const QString &stateDisplay)
{
- ui->state->setText(WebsocketClient::self().stateDisplay());
+ ui->state->setText(stateDisplay);
}
void FirstTimeDialog::closeEvent(QCloseEvent *e)
{
e->ignore();
hide();
}
FirstTimeDialog::~FirstTimeDialog() = default;
diff --git a/client/firsttimedialog.h b/client/firsttimedialog.h
index 95a7e2b..2fc29f2 100644
--- a/client/firsttimedialog.h
+++ b/client/firsttimedialog.h
@@ -1,29 +1,29 @@
// SPDX-FileCopyrightText: 2024 g10 code Gmbh
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QMainWindow>
namespace Ui
{
class FirstTimeDialog;
}
class FirstTimeDialog : public QMainWindow
{
Q_OBJECT
public:
explicit FirstTimeDialog(QWidget *parent = nullptr);
~FirstTimeDialog();
public Q_SLOTS:
- void slotStateChanged();
+ void slotStateChanged(const QString &stateDisplay);
protected:
- void closeEvent(QCloseEvent *e);
+ void closeEvent(QCloseEvent *e) override;
private:
QScopedPointer<Ui::FirstTimeDialog> ui;
};
diff --git a/client/main.cpp b/client/main.cpp
index 8e94871..401cc2c 100644
--- a/client/main.cpp
+++ b/client/main.cpp
@@ -1,113 +1,132 @@
// SPDX-FileCopyrightText: 2023 g10 code Gmbh
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QApplication>
#include <QCommandLineParser>
#include <QHttpServer>
#include <QHttpServerResponse>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QPointer>
+#include <QTemporaryDir>
#include <QTimer>
#include <QUuid>
#include <Libkleo/KeyCache>
#include <KAboutData>
+#include <KJob>
#include <KLocalizedString>
#include "config.h"
#include "firsttimedialog.h"
#include "gpgol_client_debug.h"
#include "gpgoljs_version.h"
#include "qnam.h"
+#include "rootcagenerator/controller.h"
#include "utils/kuniqueservice.h"
#include "utils/systemtrayicon.h"
#include "websocketclient.h"
using namespace Qt::Literals::StringLiterals;
using namespace std::chrono;
#ifdef Q_OS_WINDOWS
#include <windows.h>
#endif
#define STARTUP_TIMING qCDebug(GPGOL_CLIENT_LOG) << "Startup timing:" << startupTimer.elapsed() << "ms:"
#define STARTUP_TRACE qCDebug(GPGOL_CLIENT_LOG) << "Startup timing:" << startupTimer.elapsed() << "ms:" << SRCNAME << __func__ << __LINE__;
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setQuitOnLastWindowClosed(false);
KLocalizedString::setApplicationDomain(QByteArrayLiteral("gpgol"));
KAboutData about(QStringLiteral("gpgol-client"),
i18nc("@title:window", "GnuPG Outlook Add-in"),
QStringLiteral(GPGOLJS_VERSION_STRING),
i18nc("@info", "GPG Outlook add-in"),
KAboutLicense::GPL,
i18nc("@info:credit", "© 2023-2024 g10 Code GmbH"));
about.setDesktopFileName(u"com.gnupg.gpgoljs"_s);
about.setProgramLogo(QIcon::fromTheme(u"com.gnupg.gpgoljs"_s));
about.addAuthor(i18nc("@info:credit", "Carl Schwan"), i18nc("@info:credit", "Maintainer"), u"carl.schwan@gnupg.com"_s, u"https://carlschwan.eu"_s);
about.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails"));
about.setOrganizationDomain("gnupg.com");
about.setBugAddress("https://dev.gnupg.org/maniphest/task/edit/form/1/");
QCommandLineParser parser;
KAboutData::setApplicationData(about);
about.setupCommandLine(&parser);
parser.process(app);
about.processCommandLine(&parser);
QElapsedTimer startupTimer;
startupTimer.start();
STARTUP_TIMING << "Application created";
/* Create the unique service ASAP to prevent double starts if
* the application is started twice very quickly. */
KUniqueService service;
STARTUP_TIMING << "Service created";
QObject::connect(qnam, &QNetworkAccessManager::sslErrors, qnam, [](QNetworkReply *reply, const QList<QSslError> &) {
reply->ignoreSslErrors();
});
#ifdef Q_OS_WINDOWS
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
}
#endif
- const auto clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
-
- STARTUP_TIMING << "KeyCache creation";
- WebsocketClient::self(QUrl(u"wss://localhost:5657/"_s), clientId);
- auto keyCache = Kleo::KeyCache::mutableInstance();
- keyCache->startKeyListing();
-
QPointer<FirstTimeDialog> launcher = new FirstTimeDialog;
SystemTrayIcon systemTrayIcon(QIcon::fromTheme(u"com.gnupg.gpgoljs"_s));
systemTrayIcon.setMainWindow(launcher);
systemTrayIcon.show();
- QObject::connect(&WebsocketClient::self(), &WebsocketClient::stateChanged, launcher.get(), &FirstTimeDialog::slotStateChanged);
- QObject::connect(&WebsocketClient::self(), &WebsocketClient::stateChanged, &systemTrayIcon, &SystemTrayIcon::slotStateChanged);
+ const auto gnupghome = qgetenv("GNUPGHOME");
- if (Config::self()->showLauncher()) {
- launcher->show();
- }
+ QTemporaryDir tempDir;
+ tempDir.setAutoRemove(false);
+
+ qputenv("GNUPGHOME", tempDir.path().toUtf8());
+
+ auto controller = new Controller;
+ QObject::connect(controller, &Controller::finished, controller, [gnupghome, launcher, &systemTrayIcon](KJob *) {
+ qputenv("GNUPGHOME", gnupghome);
+
+ const auto clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
+ WebsocketClient::self(QUrl(u"wss://localhost:5657/"_s), clientId);
+
+ QObject::connect(&WebsocketClient::self(), &WebsocketClient::stateChanged, launcher.get(), &FirstTimeDialog::slotStateChanged);
+ QObject::connect(&WebsocketClient::self(), &WebsocketClient::stateChanged, &systemTrayIcon, &SystemTrayIcon::slotStateChanged);
+
+ systemTrayIcon.slotStateChanged(WebsocketClient::self().stateDisplay());
+ launcher->slotStateChanged(WebsocketClient::self().stateDisplay());
+
+ if (Config::self()->showLauncher()) {
+ launcher->show();
+ }
+ });
+ controller->start();
+
+ STARTUP_TIMING << "KeyCache creation";
+ auto keyCache = Kleo::KeyCache::mutableInstance();
+ keyCache->startKeyListing();
return app.exec();
}
diff --git a/client/rootcagenerator/CMakeLists.txt b/client/rootcagenerator/CMakeLists.txt
index a80bc5b..b705633 100644
--- a/client/rootcagenerator/CMakeLists.txt
+++ b/client/rootcagenerator/CMakeLists.txt
@@ -1,43 +1,43 @@
# SPDX-FileCopyrightText: 2024 g10 code GmbH
# SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
# SPDX-License-Identifier: BSD-2-Clause
add_library(rootcagenerator STATIC)
target_sources(rootcagenerator PRIVATE
controller.cpp
controller.h
truststore.cpp
truststore.h
)
if (WIN32)
target_sources(rootcagenerator PRIVATE
truststore_win.cpp
truststore_win.h
)
install(FILES install.ps1 DESTINATION ${KDE_INSTALL_BINDIR})
else()
target_sources(rootcagenerator PRIVATE
truststore_linux.cpp
truststore_linux.h
)
target_link_libraries(rootcagenerator PRIVATE KF6::AuthCore)
add_executable(truststore_linux_helper
truststore_linux_helper.cpp
truststore_linux_helper.h
)
target_link_libraries(truststore_linux_helper KF6::I18n KF6::AuthCore Qt::Core)
install(TARGETS truststore_linux_helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR})
kauth_install_actions(com.gnupg.gpgolweb.truststore com.gnupg.gpgolweb.truststore.actions)
kauth_install_helper_files(truststore_linux_helper com.gnupg.gpgolweb.truststore root)
endif()
-target_link_libraries(rootcagenerator PUBLIC KPim6::Libkleo KF6::I18n)
+target_link_libraries(rootcagenerator PUBLIC KPim6::Libkleo KF6::I18n KF6::CoreAddons)
add_executable(gpgol-cert-generator main.cpp)
target_link_libraries(gpgol-cert-generator PUBLIC rootcagenerator)
install(TARGETS gpgol-cert-generator ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/client/rootcagenerator/controller.cpp b/client/rootcagenerator/controller.cpp
index 6e3ae51..0b9b00e 100644
--- a/client/rootcagenerator/controller.cpp
+++ b/client/rootcagenerator/controller.cpp
@@ -1,291 +1,304 @@
// SPDX-FileCopyrightText: 2023 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "controller.h"
#include "truststore.h"
#include <QDate>
#include <QSaveFile>
#include <QStandardPaths>
#include <QGpgME/ExportJob>
#include <QGpgME/ImportJob>
#include <QGpgME/KeyGenerationJob>
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <Libkleo/Formatting>
#include <Libkleo/KeyParameters>
#include <Libkleo/KeyUsage>
#include <KLocalizedString>
using namespace Qt::StringLiterals;
using namespace Kleo;
using namespace GpgME;
static KeyParameters createRootCaParms()
{
KeyParameters keyParameters(KeyParameters::CMS);
keyParameters.setKeyType(GpgME::Subkey::PubkeyAlgo::AlgoRSA);
keyParameters.setKeyUsage(KeyUsage{KeyUsage::Sign | KeyUsage::Certify});
keyParameters.setDN(u"CN=RootGpgolJs"_s);
keyParameters.setEmail(u"localroot@gpgoljs.local"_s);
keyParameters.setKeyLength(3072);
keyParameters.setExpirationDate(QDate(2060, 10, 10));
keyParameters.setUseRandomSerial();
keyParameters.setControlStatements({u"%no-protection"_s});
return keyParameters;
}
static KeyParameters createTlsCertParms(QLatin1StringView keyGrip)
{
KeyParameters keyParameters(KeyParameters::CMS);
keyParameters.setKeyType(GpgME::Subkey::PubkeyAlgo::AlgoRSA);
keyParameters.setKeyUsage(KeyUsage{KeyUsage::Sign | KeyUsage::Encrypt});
keyParameters.setDN(u"CN=LocalGpgolJs"_s);
keyParameters.setEmail(u"local@gpgoljs.local"_s);
keyParameters.setKeyLength(3072);
keyParameters.setExpirationDate(QDate(2060, 10, 10));
keyParameters.setIssuerDN(u"CN=RootGpgolJs"_s);
keyParameters.setSigningKey(keyGrip);
keyParameters.setUseRandomSerial();
keyParameters.setControlStatements({u"%no-protection"_s});
return keyParameters;
}
Controller::Controller(QObject *parent)
- : QObject(parent)
+ : KJob(parent)
{
}
QString Controller::caUniqueName() const
{
return u"GPGOL2 CA "_s + QString::fromLatin1(m_ca.issuerSerial());
}
QByteArray Controller::caCert() const
{
return m_publicCA;
}
void Controller::start()
{
+ auto certPath = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("certificate.pem"));
+
+ if (!certPath.isEmpty()) {
+ emitResult();
+ return;
+ }
+
auto keyGenerationJob = QGpgME::smime()->keyGenerationJob();
connect(keyGenerationJob, &QGpgME::KeyGenerationJob::result, this, &Controller::slotRootCaCreatedSlot);
keyGenerationJob->start(createRootCaParms().toString());
}
void Controller::slotRootCaCreatedSlot(const GpgME::KeyGenerationResult &result, const QByteArray &pubKeyData, const QString &auditLog)
{
- qWarning() << "created root ca";
Q_UNUSED(auditLog)
if (result.error().code()) {
- qWarning() << (result.error().isCanceled() ? i18n("Operation canceled.")
- : i18n("Could not create key pair: %1", Formatting::errorAsString(result.error())));
- Q_EMIT finished(1);
+ setErrorText(result.error().isCanceled() ? i18n("Operation canceled.")
+ : i18n("Could not create key pair: %1", Formatting::errorAsString(result.error())));
+ setError(UserDefinedError);
+ emitResult();
return;
}
auto importJob = QGpgME::smime()->importJob();
connect(importJob, &QGpgME::ImportJob::result, this, &Controller::slotRootCaImportedSlot);
importJob->start(pubKeyData);
}
void Controller::slotRootCaImportedSlot(const GpgME::ImportResult &result, const QString &auditLogAsHtml, const GpgME::Error &auditLogError)
{
Q_UNUSED(auditLogAsHtml)
Q_UNUSED(auditLogError)
qWarning() << "got root ca imported";
if (result.error().code()) {
- qWarning() << (result.error().isCanceled() ? i18n("Operation canceled.")
- : i18n("Could not create key pair: %1", Formatting::errorAsString(result.error())));
- Q_EMIT finished(1);
+ setErrorText(result.error().isCanceled() ? i18n("Operation canceled.")
+ : i18n("Could not create key pair: %1", Formatting::errorAsString(result.error())));
+ setError(UserDefinedError);
+ emitResult();
return;
}
// Get the keygrip
auto keyListJob = QGpgME::smime()->keyListJob();
connect(keyListJob, &QGpgME::KeyListJob::result, this, &Controller::slotKeyGripOptained);
keyListJob->start({u"RootGpgolJs"_s}, true);
// Export public key
auto exportJob = QGpgME::smime()->publicKeyExportJob(true);
const auto imports = result.imports();
const auto fingerprint = imports[0].fingerprint();
exportJob->start({QString::fromLatin1(fingerprint)});
connect(exportJob, &QGpgME::ExportJob::result, this, [this](const GpgME::Error &error, const QByteArray &keyData) {
if (error.code()) {
- qWarning() << (error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export public key: %1", Formatting::errorAsString(error)));
- Q_EMIT finished(1);
+ setErrorText(error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export public key: %1", Formatting::errorAsString(error)));
+ setError(UserDefinedError);
+ emitResult();
return;
}
m_publicCA = keyData;
checkFinished();
});
// Export private key
auto exportSecretJob = QGpgME::smime()->secretKeyExportJob(true);
exportSecretJob->start({QString::fromLatin1(fingerprint)});
connect(exportSecretJob, &QGpgME::ExportJob::result, this, [this](const GpgME::Error &error, const QByteArray &keyData) {
if (error.code()) {
- qWarning() << (error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export secret key: %1", Formatting::errorAsString(error)));
- Q_EMIT finished(1);
+ setErrorText(error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export secret key: %1", Formatting::errorAsString(error)));
+ setError(UserDefinedError);
+ emitResult();
return;
}
m_secretCA = keyData;
checkFinished();
});
}
void Controller::slotKeyGripOptained(const GpgME::KeyListResult &result,
const std::vector<GpgME::Key> &keys,
const QString &auditLogAsHtml,
const GpgME::Error &auditLogError)
{
qWarning() << "got key grip";
Q_UNUSED(auditLogAsHtml)
Q_UNUSED(auditLogError)
if (result.error().code()) {
- qWarning() << (result.error().isCanceled() ? i18n("Operation canceled.")
- : i18n("Could not get keygrip : %1", Formatting::errorAsString(result.error())));
- Q_EMIT finished(1);
+ setErrorText(result.error().isCanceled() ? i18n("Operation canceled.") : i18n("Could not get keygrip : %1", Formatting::errorAsString(result.error())));
+ setError(UserDefinedError);
+ emitResult();
return;
}
m_ca = keys[0];
auto keyGenerationJob = QGpgME::smime()->keyGenerationJob();
connect(keyGenerationJob, &QGpgME::KeyGenerationJob::result, this, &Controller::slotCertCreatedSlot);
keyGenerationJob->start(createTlsCertParms(QLatin1StringView(keys[0].subkey(0).keyGrip())).toString());
}
void Controller::slotCertCreatedSlot(const GpgME::KeyGenerationResult &result, const QByteArray &pubKeyData, const QString &auditLog)
{
qWarning() << "created tls cert";
Q_UNUSED(auditLog)
if (result.error().code()) {
- qWarning() << (result.error().isCanceled() ? i18n("Operation canceled.")
- : i18n("Could not create key pair for cert: %1", Formatting::errorAsString(result.error())));
- Q_EMIT finished(1);
+ setErrorText(result.error().isCanceled() ? i18n("Operation canceled.")
+ : i18n("Could not create key pair for cert: %1", Formatting::errorAsString(result.error())));
+ setError(UserDefinedError);
+ emitResult();
return;
}
auto importJob = QGpgME::smime()->importJob();
connect(importJob, &QGpgME::ImportJob::result, this, &Controller::slotCertImportedSlot);
importJob->start(pubKeyData);
}
void Controller::slotCertImportedSlot(const GpgME::ImportResult &result, const QString &auditLogAsHtml, const GpgME::Error &auditLogError)
{
Q_UNUSED(auditLogAsHtml)
Q_UNUSED(auditLogError)
qWarning() << "got cert imported";
if (result.error().code()) {
- qWarning() << (result.error().isCanceled() ? i18n("Operation canceled.")
- : i18n("Could not import cert: %1", Formatting::errorAsString(result.error())));
- Q_EMIT finished(1);
+ setErrorText(result.error().isCanceled() ? i18n("Operation canceled.") : i18n("Could not import cert: %1", Formatting::errorAsString(result.error())));
+ setError(UserDefinedError);
+ emitResult();
return;
}
auto keyListJob = QGpgME::smime()->keyListJob();
connect(keyListJob,
&QGpgME::KeyListJob::result,
this,
[this](const GpgME::KeyListResult &result, const std::vector<GpgME::Key> &keys, const QString &auditLogAsHtml, const GpgME::Error &auditLogError) {
m_tls = keys[0];
checkFinished();
});
keyListJob->start({u"RootGpgolJs"_s}, true);
// Export public key
auto exportJob = QGpgME::smime()->publicKeyExportJob(true);
const auto imports = result.imports();
const auto fingerprint = imports[0].fingerprint();
exportJob->start({QString::fromLatin1(fingerprint)});
connect(exportJob, &QGpgME::ExportJob::result, this, [this](const GpgME::Error &error, const QByteArray &keyData) {
if (error.code()) {
- qWarning() << (error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export public key: %1", Formatting::errorAsString(error)));
- Q_EMIT finished(1);
+ setErrorText(error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export public key: %1", Formatting::errorAsString(error)));
+ setError(UserDefinedError);
+ emitResult();
return;
}
m_publicTLS = keyData;
checkFinished();
});
// Export private key
auto exportSecretJob = QGpgME::smime()->secretKeyExportJob(true);
exportSecretJob->start({QString::fromLatin1(fingerprint)});
connect(exportSecretJob, &QGpgME::ExportJob::result, this, [this](const GpgME::Error &error, const QByteArray &keyData) {
if (error.code()) {
- qWarning() << (error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export secret key: %1", Formatting::errorAsString(error)));
- Q_EMIT finished(1);
+ setErrorText(error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export secret key: %1", Formatting::errorAsString(error)));
+ setError(UserDefinedError);
+ emitResult();
return;
}
m_secretTLS = keyData;
checkFinished();
});
}
void Controller::checkFinished()
{
if (!m_secretCA.isEmpty() && !m_publicCA.isEmpty() && !m_publicTLS.isEmpty() && !m_secretTLS.isEmpty() && !m_ca.isNull() && !m_tls.isNull()) {
install();
- Q_EMIT finished();
+ emitResult();
}
}
void Controller::install()
{
qWarning() << "installing";
auto keyPath = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("certificate-key.pem"));
auto certPath = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("certificate.pem"));
QSaveFile rootCaPub(u"rootCA.pem"_s);
if (rootCaPub.open(QIODeviceBase::WriteOnly)) {
rootCaPub.write(m_publicCA);
rootCaPub.commit();
} else {
qDebug() << "no permission to write";
}
QSaveFile rootCaKey(u"rootCA-key.pem"_s);
if (rootCaKey.open(QIODeviceBase::WriteOnly)) {
rootCaKey.write(m_secretCA);
rootCaKey.commit();
} else {
qDebug() << "no permission to write";
}
QSaveFile localhostPub(u"localhost.pem"_s);
if (localhostPub.open(QIODeviceBase::WriteOnly)) {
localhostPub.write(m_publicTLS);
localhostPub.commit();
} else {
qDebug() << "no permission to write";
}
QSaveFile localhostKey(u"localhost-key.pem"_s);
if (localhostKey.open(QIODeviceBase::WriteOnly)) {
localhostKey.write(m_secretTLS);
localhostKey.commit();
} else {
qDebug() << "no permission to write";
}
auto trustStore = TrustStoreFactory::getPlatformTrustStore();
trustStore->install(*this);
}
\ No newline at end of file
diff --git a/client/rootcagenerator/controller.h b/client/rootcagenerator/controller.h
index c7f10c2..5e6ac15 100644
--- a/client/rootcagenerator/controller.h
+++ b/client/rootcagenerator/controller.h
@@ -1,56 +1,53 @@
// SPDX-FileCopyrightText: 2024 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <QObject>
+#include <KJob>
#include <gpgme++/importresult.h>
#include <gpgme++/key.h>
#include <gpgme++/keygenerationresult.h>
#include <gpgme++/keylistresult.h>
static const QLatin1StringView ROOT_NAME = QLatin1StringView("rootCA.pem");
-class Controller : public QObject
+class Controller : public KJob
{
Q_OBJECT
public:
explicit Controller(QObject *parent = nullptr);
QString caUniqueName() const;
QByteArray caCert() const;
- void start();
-
-Q_SIGNALS:
- void finished(int errorCode = 0);
+ void start() override;
private:
void slotRootCaCreatedSlot(const GpgME::KeyGenerationResult &result, const QByteArray &request, const QString &auditLog);
void slotRootCaImportedSlot(const GpgME::ImportResult &result, const QString &auditLogAsHtml, const GpgME::Error &auditLogError);
void slotKeyGripOptained(const GpgME::KeyListResult &result,
const std::vector<GpgME::Key> &keys,
const QString &auditLogAsHtml,
const GpgME::Error &auditLogError);
void slotCertCreatedSlot(const GpgME::KeyGenerationResult &result, const QByteArray &request, const QString &auditLog);
void slotCertImportedSlot(const GpgME::ImportResult &result, const QString &auditLogAsHtml, const GpgME::Error &auditLogError);
void checkFinished();
void install();
private:
GpgME::Key m_ca;
QByteArray m_publicCA;
QByteArray m_secretCA;
GpgME::Key m_tls;
QByteArray m_publicTLS;
QByteArray m_secretTLS;
};
diff --git a/client/rootcagenerator/main.cpp b/client/rootcagenerator/main.cpp
index 90fe97f..9b0f45c 100644
--- a/client/rootcagenerator/main.cpp
+++ b/client/rootcagenerator/main.cpp
@@ -1,40 +1,46 @@
// SPDX-FileCopyrightText: 2023 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QCoreApplication>
#include <QTemporaryDir>
#include <KLocalizedString>
#include "controller.h"
#ifdef Q_OS_WINDOWS
#include <windows.h>
#endif
using namespace Qt::StringLiterals;
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QTemporaryDir tempDir;
tempDir.setAutoRemove(false);
#ifdef Q_OS_WINDOWS
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
}
#endif
qWarning() << tempDir.path();
qputenv("GNUPGHOME", tempDir.path().toUtf8());
Controller controller;
- QObject::connect(&controller, &Controller::finished, &app, &QCoreApplication::exit);
+ QObject::connect(&controller, &KJob::finished, &app, [&app](KJob *job) {
+ if (job->error()) {
+ app.exit(1);
+ } else {
+ app.exit(0);
+ }
+ });
controller.start();
return app.exec();
}
\ No newline at end of file
diff --git a/client/utils/systemtrayicon.cpp b/client/utils/systemtrayicon.cpp
index 67a2774..da58047 100644
--- a/client/utils/systemtrayicon.cpp
+++ b/client/utils/systemtrayicon.cpp
@@ -1,289 +1,286 @@
// SPDX-FileCopyrightText: 2007, 2009 Klarälvdalens Datakonsult AB
// SPDX-License-Identifier: GPL-2.0-or-later
#include "systemtrayicon.h"
#include "gpgol_client_debug.h"
-#include "websocketclient.h"
#include <QCoreApplication>
#include <QEvent>
#include <QMenu>
#include <QPointer>
#include <QTimer>
#include <QWidget>
#include <KAboutApplicationDialog>
#include <KAboutData>
#include <KLocalizedString>
static const int ATTENTION_ANIMATION_FRAMES_PER_SEC = 1;
class SystemTrayIcon::Private
{
friend class ::SystemTrayIcon;
SystemTrayIcon *const q;
public:
explicit Private(SystemTrayIcon *qq);
~Private();
private:
bool attentionWanted() const
{
return attentionAnimationTimer.isActive();
}
void setAttentionWantedImpl(bool on)
{
if (on) {
attentionAnimationTimer.start();
} else {
attentionAnimationTimer.stop();
attentionIconShown = false;
q->setIcon(normalIcon);
}
}
void slotActivated(ActivationReason reason)
{
if (reason == QSystemTrayIcon::Trigger) {
q->doActivated();
}
}
void slotAttentionAnimationTimerTimout()
{
if (attentionIconShown) {
attentionIconShown = false;
q->setIcon(normalIcon);
} else {
attentionIconShown = true;
q->setIcon(attentionIcon);
}
}
private:
bool attentionIconShown;
QMenu menu;
QIcon normalIcon, attentionIcon;
QTimer attentionAnimationTimer;
QPointer<QWidget> mainWindow;
QPointer<QWidget> attentionWindow;
QAction aboutAction;
QAction quitAction;
KAboutApplicationDialog *aboutDialog = nullptr;
};
SystemTrayIcon::Private::Private(SystemTrayIcon *qq)
: q(qq)
, attentionIconShown(false)
, attentionAnimationTimer()
, mainWindow()
, attentionWindow()
, aboutAction(QIcon::fromTheme(QStringLiteral("kleopatra")),
xi18nc("@action:inmenu", "&About <application>%1</application>...", KAboutData::applicationData().displayName()),
q)
, quitAction(QIcon::fromTheme(QStringLiteral("application-exit")),
xi18nc("@action:inmenu", "&Shutdown <application>%1</application>", KAboutData::applicationData().displayName()),
q)
{
Q_SET_OBJECT_NAME(attentionAnimationTimer);
Q_SET_OBJECT_NAME(aboutAction);
Q_SET_OBJECT_NAME(quitAction);
attentionAnimationTimer.setSingleShot(false);
attentionAnimationTimer.setInterval(1000 * ATTENTION_ANIMATION_FRAMES_PER_SEC / 2);
menu.addAction(&aboutAction);
menu.addAction(&quitAction);
q->setContextMenu(&menu);
connect(q, &QSystemTrayIcon::activated, q, [this](QSystemTrayIcon::ActivationReason reason) {
slotActivated(reason);
});
connect(&attentionAnimationTimer, &QTimer::timeout, q, [this]() {
slotAttentionAnimationTimerTimout();
});
connect(&aboutAction, &QAction::triggered, q, &SystemTrayIcon::slotAbout);
connect(&quitAction, &QAction::triggered, QCoreApplication::instance(), &QCoreApplication::quit);
-
- q->setToolTip(WebsocketClient::self().stateDisplay());
}
SystemTrayIcon::Private::~Private()
{
}
SystemTrayIcon::SystemTrayIcon(QObject *p)
: QSystemTrayIcon(p)
, d(std::make_unique<Private>(this))
{
}
SystemTrayIcon::SystemTrayIcon(const QIcon &icon, QObject *p)
: QSystemTrayIcon(icon, p)
, d(std::make_unique<Private>(this))
{
d->normalIcon = d->attentionIcon = icon;
}
SystemTrayIcon::~SystemTrayIcon() = default;
void SystemTrayIcon::setMainWindow(QWidget *mw)
{
if (d->mainWindow) {
return;
}
d->mainWindow = mw;
if (mw) {
mw->installEventFilter(this);
}
doMainWindowSet(mw);
slotEnableDisableActions();
}
QWidget *SystemTrayIcon::mainWindow() const
{
return d->mainWindow;
}
void SystemTrayIcon::setAttentionWindow(QWidget *mw)
{
if (d->attentionWindow) {
return;
}
d->attentionWindow = mw;
if (mw) {
mw->installEventFilter(this);
}
slotEnableDisableActions();
}
QWidget *SystemTrayIcon::attentionWindow() const
{
return d->attentionWindow;
}
bool SystemTrayIcon::eventFilter(QObject *o, QEvent *e)
{
if (o == d->mainWindow)
switch (e->type()) {
case QEvent::Close:
doMainWindowClosed(static_cast<QWidget *>(o));
// fall through:
[[fallthrough]];
case QEvent::Show:
case QEvent::DeferredDelete:
QMetaObject::invokeMethod(this, "slotEnableDisableActions", Qt::QueuedConnection);
default:;
}
else if (o == d->attentionWindow)
switch (e->type()) {
case QEvent::Close:
doAttentionWindowClosed(static_cast<QWidget *>(o));
// fall through:
[[fallthrough]];
case QEvent::Show:
case QEvent::DeferredDelete:
QMetaObject::invokeMethod(this, "slotEnableDisableActions", Qt::QueuedConnection);
default:;
}
return false;
}
void SystemTrayIcon::setAttentionWanted(bool on)
{
if (d->attentionWanted() == on) {
return;
}
qCDebug(GPGOL_CLIENT_LOG) << d->attentionWanted() << "->" << on;
d->setAttentionWantedImpl(on);
}
bool SystemTrayIcon::attentionWanted() const
{
return d->attentionWanted();
}
void SystemTrayIcon::setNormalIcon(const QIcon &icon)
{
if (d->normalIcon.cacheKey() == icon.cacheKey()) {
return;
}
d->normalIcon = icon;
if (!d->attentionWanted() || !d->attentionIconShown) {
setIcon(icon);
}
}
QIcon SystemTrayIcon::normalIcon() const
{
return d->normalIcon;
}
void SystemTrayIcon::setAttentionIcon(const QIcon &icon)
{
if (d->attentionIcon.cacheKey() == icon.cacheKey()) {
return;
}
d->attentionIcon = icon;
if (d->attentionWanted() && d->attentionIconShown) {
setIcon(icon);
}
}
QIcon SystemTrayIcon::attentionIcon() const
{
return d->attentionIcon;
}
void SystemTrayIcon::doMainWindowSet(QWidget *)
{
}
void SystemTrayIcon::doMainWindowClosed(QWidget *)
{
}
void SystemTrayIcon::doAttentionWindowClosed(QWidget *)
{
}
void SystemTrayIcon::slotAbout()
{
if (!d->aboutDialog) {
d->aboutDialog = new KAboutApplicationDialog(KAboutData::applicationData());
d->aboutDialog->setAttribute(Qt::WA_DeleteOnClose);
}
if (d->aboutDialog->isVisible()) {
d->aboutDialog->raise();
} else {
d->aboutDialog->show();
}
}
void SystemTrayIcon::slotEnableDisableActions()
{
}
void SystemTrayIcon::doActivated()
{
Q_ASSERT(mainWindow());
if (mainWindow()->isVisible()) {
mainWindow()->hide();
} else {
mainWindow()->show();
mainWindow()->raise();
}
}
-void SystemTrayIcon::slotStateChanged()
+void SystemTrayIcon::slotStateChanged(const QString &state)
{
- setToolTip(WebsocketClient::self().stateDisplay());
+ setToolTip(state);
}
#include "moc_systemtrayicon.cpp"
diff --git a/client/utils/systemtrayicon.h b/client/utils/systemtrayicon.h
index d3f7cce..4906e12 100644
--- a/client/utils/systemtrayicon.h
+++ b/client/utils/systemtrayicon.h
@@ -1,50 +1,50 @@
// SPDX-FileCopyrightText: 2007, 2009 Klarälvdalens Datakonsult AB
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QSystemTrayIcon>
#include <memory>
class SystemTrayIcon : public QSystemTrayIcon
{
Q_OBJECT
public:
explicit SystemTrayIcon(QObject *parent = nullptr);
explicit SystemTrayIcon(const QIcon &icon, QObject *parent = nullptr);
~SystemTrayIcon() override;
void setMainWindow(QWidget *w);
QWidget *mainWindow() const;
void setAttentionWindow(QWidget *w);
QWidget *attentionWindow() const;
QIcon attentionIcon() const;
QIcon normalIcon() const;
bool attentionWanted() const;
public Q_SLOTS:
void setAttentionIcon(const QIcon &icon);
void setNormalIcon(const QIcon &icon);
void setAttentionWanted(bool);
void slotAbout();
- void slotStateChanged();
+ void slotStateChanged(const QString &state);
protected Q_SLOTS:
virtual void slotEnableDisableActions();
private:
virtual void doMainWindowSet(QWidget *);
virtual void doMainWindowClosed(QWidget *);
virtual void doAttentionWindowClosed(QWidget *);
virtual void doActivated();
private:
bool eventFilter(QObject *, QEvent *) override;
private:
class Private;
const std::unique_ptr<Private> d;
};
diff --git a/client/websocketclient.cpp b/client/websocketclient.cpp
index b9318c1..6cd7c96 100644
--- a/client/websocketclient.cpp
+++ b/client/websocketclient.cpp
@@ -1,343 +1,343 @@
// SPDX-FileCopyrightText: 2023 g10 code Gmbh
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "websocketclient.h"
// Qt headers
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QStandardPaths>
#include <QTimer>
// KDE headers
#include <KLocalizedString>
#include <Libkleo/KeyCache>
#include <MimeTreeParserCore/ObjectTreeParser>
// gpgme headers
#include <gpgme++/key.h>
#include <kmime/message.h>
#include <quuid.h>
#include "draft/draftmanager.h"
#include "editor/composerwindow.h"
#include "editor/composerwindowfactory.h"
#include "emailviewer.h"
#include "ews/ewstypes.h"
#include "gpgol_client_debug.h"
#include "gpgoljs_version.h"
#include "protocol.h"
#include "qnam.h"
#include "websocket_debug.h"
#include "ews/ewsitem.h"
#include "ews/ewspropertyfield.h"
#include "ews/ewsserverversion.h"
#include "ews/ewsxml.h"
using namespace Qt::Literals::StringLiterals;
namespace
{
QStringList trustedEmails(const std::shared_ptr<const Kleo::KeyCache> &keyCache)
{
QStringList emails;
const auto keys = keyCache->keys();
for (const auto &key : keys) {
for (const auto &userId : key.userIDs()) {
if (key.ownerTrust() == GpgME::Key::Ultimate) {
emails << QString::fromLatin1(userId.email()).toLower();
break;
}
}
}
return emails;
}
}
WebsocketClient &WebsocketClient::self(const QUrl &url, const QString &clientId)
{
static WebsocketClient *client = nullptr;
if (!client && url.isEmpty()) {
qFatal() << "Unable to create a client without an url";
} else if (!client) {
client = new WebsocketClient(url, clientId);
}
return *client;
};
WebsocketClient::WebsocketClient(const QUrl &url, const QString &clientId)
: QObject(nullptr)
, m_webSocket(QWebSocket(QStringLiteral("Client")))
, m_url(url)
, m_clientId(clientId)
, m_state(NotConnected)
, m_stateDisplay(i18nc("@info", "Loading..."))
{
auto globalKeyCache = Kleo::KeyCache::instance();
m_emails = trustedEmails(globalKeyCache);
if (m_emails.isEmpty()) {
qWarning() << "No ultimately keys found in keychain";
return;
}
connect(&m_webSocket, &QWebSocket::connected, this, &WebsocketClient::slotConnected);
connect(&m_webSocket, &QWebSocket::disconnected, this, [this] {
m_state = NotConnected;
m_stateDisplay = i18nc("@info", "Connection to outlook lost due to a disconnection with the broker.");
- Q_EMIT stateChanged();
+ Q_EMIT stateChanged(m_stateDisplay);
});
connect(&m_webSocket, &QWebSocket::errorOccurred, this, &WebsocketClient::slotErrorOccurred);
connect(&m_webSocket, &QWebSocket::textMessageReceived, this, &WebsocketClient::slotTextMessageReceived);
connect(&m_webSocket, QOverload<const QList<QSslError> &>::of(&QWebSocket::sslErrors), this, [this](const QList<QSslError> &errors) {
// TODO remove
m_webSocket.ignoreSslErrors(errors);
});
QSslConfiguration sslConfiguration;
auto certPath = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("certificate.pem"));
Q_ASSERT(!certPath.isEmpty());
QFile certFile(certPath);
if (!certFile.open(QIODevice::ReadOnly)) {
qFatal() << "Couldn't read certificate" << certPath;
}
QSslCertificate certificate(&certFile, QSsl::Pem);
certFile.close();
sslConfiguration.addCaCertificate(certificate);
m_webSocket.setSslConfiguration(sslConfiguration);
m_webSocket.open(url);
// TODO remove me
QObject::connect(qnam, &QNetworkAccessManager::sslErrors, qnam, [](QNetworkReply *reply, const QList<QSslError> &errors) {
reply->ignoreSslErrors();
});
}
void WebsocketClient::slotConnected()
{
qCInfo(WEBSOCKET_LOG) << "websocket connected";
QJsonDocument doc(QJsonObject{
{"command"_L1, Protocol::commandToString(Protocol::Register)},
{
"arguments"_L1,
QJsonObject{{"emails"_L1, QJsonArray::fromStringList(m_emails)}, {"type"_L1, "nativeclient"_L1}},
},
});
m_webSocket.sendTextMessage(QString::fromUtf8(doc.toJson()));
m_state = NotConnected; /// We still need to connect to the web client
m_stateDisplay = i18nc("@info", "Waiting for web client.");
- Q_EMIT stateChanged();
+ Q_EMIT stateChanged(m_stateDisplay);
}
void WebsocketClient::slotErrorOccurred(QAbstractSocket::SocketError error)
{
qCWarning(WEBSOCKET_LOG) << error << m_webSocket.errorString();
m_state = NotConnected;
m_stateDisplay = i18nc("@info", "Could not reach the Outlook extension.");
- Q_EMIT stateChanged();
+ Q_EMIT stateChanged(m_stateDisplay);
reconnect();
}
bool WebsocketClient::sendEWSRequest(const QString &fromEmail, const QString &requestId, const QString &requestBody)
{
KMime::Types::Mailbox mailbox;
mailbox.fromUnicodeString(fromEmail);
const QJsonObject json{
{"command"_L1, Protocol::commandToString(Protocol::Ews)},
{"arguments"_L1,
QJsonObject{
{"body"_L1, requestBody},
{"email"_L1, QString::fromUtf8(mailbox.address())},
{"requestId"_L1, requestId},
}},
};
m_webSocket.sendTextMessage(QString::fromUtf8(QJsonDocument(json).toJson()));
return true;
}
void WebsocketClient::slotTextMessageReceived(QString message)
{
const auto doc = QJsonDocument::fromJson(message.toUtf8());
if (!doc.isObject()) {
qCWarning(WEBSOCKET_LOG) << "invalid text message received" << message;
return;
}
const auto object = doc.object();
const auto command = Protocol::commandFromString(object["command"_L1].toString());
const auto args = object["arguments"_L1].toObject();
switch (command) {
case Protocol::Disconnection:
// disconnection of the web client
m_state = NotConnected;
m_stateDisplay = i18nc("@info", "Connection to the Outlook extension lost. Make sure the extension pane is open.");
- Q_EMIT stateChanged();
+ Q_EMIT stateChanged(m_stateDisplay);
return;
case Protocol::Connection:
// reconnection of the web client
m_state = Connected;
m_stateDisplay = i18nc("@info", "Connected.");
- Q_EMIT stateChanged();
+ Q_EMIT stateChanged(m_stateDisplay);
return;
case Protocol::View: {
const auto email = args["email"_L1].toString();
const auto displayName = args["displayName"_L1].toString();
const auto content = args["body"_L1].toString();
const auto token = args["token"_L1].toString();
if (!m_emailViewer) {
m_emailViewer = new EmailViewer(content, email, displayName, token);
m_emailViewer->setAttribute(Qt::WA_DeleteOnClose);
} else {
m_emailViewer->view(content, email, displayName, token);
}
m_emailViewer->show();
m_emailViewer->activateWindow();
m_emailViewer->raise();
return;
}
case Protocol::RestoreAutosave: {
const auto email = args["email"_L1].toString();
const auto displayName = args["displayName"_L1].toString();
const auto bearerToken = args["token"_L1].toString().toUtf8();
ComposerWindowFactory::self().restoreAutosave(email, displayName, bearerToken);
return;
}
case Protocol::EwsResponse: {
// confirmation that the email was sent
const auto args = object["arguments"_L1].toObject();
Q_EMIT ewsResponseReceived(args["requestId"_L1].toString(), args["body"_L1].toString());
return;
}
case Protocol::Composer:
case Protocol::Reply:
case Protocol::Forward:
case Protocol::OpenDraft: {
const auto email = args["email"_L1].toString();
const auto displayName = args["displayName"_L1].toString();
const auto bearerToken = args["token"_L1].toString().toUtf8();
auto dialog = ComposerWindowFactory::self().create(email, displayName, bearerToken);
if (command == Protocol::Reply || command == Protocol::Forward) {
const auto content = args["body"_L1].toString();
KMime::Message::Ptr message(new KMime::Message());
message->setContent(KMime::CRLFtoLF(content.toUtf8()));
message->parse();
if (command == Protocol::Reply) {
dialog->reply(message);
} else {
dialog->forward(message);
}
} else if (command == Protocol::OpenDraft) {
const auto draftId = args["id"_L1].toString();
if (draftId.isEmpty()) {
return;
}
const auto draft = DraftManager::self().draftById(draftId.toUtf8());
dialog->setMessage(draft.mime());
}
dialog->show();
dialog->activateWindow();
dialog->raise();
return;
}
case Protocol::DeleteDraft: {
const auto draftId = args["id"_L1].toString();
if (draftId.isEmpty()) {
qWarning() << "Draft not valid";
return;
}
const auto draft = DraftManager::self().draftById(draftId.toUtf8());
if (!draft.isValid()) {
qWarning() << "Draft not valid";
return;
}
if (!DraftManager::self().remove(draft)) {
qCWarning(GPGOL_CLIENT_LOG) << "Could not delete draft";
return;
}
return;
}
case Protocol::Info: {
const auto content = args["body"_L1].toString();
KMime::Message::Ptr message(new KMime::Message());
message->setContent(KMime::CRLFtoLF(content.toUtf8()));
message->parse();
MimeTreeParser::ObjectTreeParser treeParser;
treeParser.parseObjectTree(message.get());
const QJsonObject json{{"command"_L1, Protocol::commandToString(Protocol::InfoFetched)},
{"arguments"_L1,
QJsonObject{
{"itemId"_L1, args["itemId"_L1]},
{"email"_L1, args["email"_L1]},
{"encrypted"_L1, treeParser.hasEncryptedParts()},
{"signed"_L1, treeParser.hasSignedParts()},
{"drafts"_L1, DraftManager::self().toJson()},
{"viewerOpen"_L1, !m_emailViewer.isNull()},
{"version"_L1, QStringLiteral(GPGOLJS_VERSION_STRING)},
}}};
m_webSocket.sendTextMessage(QString::fromUtf8(QJsonDocument(json).toJson()));
return;
}
default:
qCWarning(WEBSOCKET_LOG) << "Unhandled command" << command;
}
}
void WebsocketClient::reconnect()
{
QTimer::singleShot(1000ms, this, [this]() {
m_webSocket.open(m_url);
});
}
WebsocketClient::State WebsocketClient::state() const
{
return m_state;
}
QString WebsocketClient::stateDisplay() const
{
return m_stateDisplay;
}
void WebsocketClient::sendViewerClosed(const QString &email)
{
const QJsonObject json{
{"command"_L1, Protocol::commandToString(Protocol::ViewerClosed)},
{"arguments"_L1, QJsonObject{{"email"_L1, email}}},
};
m_webSocket.sendTextMessage(QString::fromUtf8(QJsonDocument(json).toJson()));
}
void WebsocketClient::sendViewerOpened(const QString &email)
{
const QJsonObject json{
{"command"_L1, Protocol::commandToString(Protocol::ViewerOpened)},
{"arguments"_L1, QJsonObject{{"email"_L1, email}}},
};
m_webSocket.sendTextMessage(QString::fromUtf8(QJsonDocument(json).toJson()));
}
diff --git a/client/websocketclient.h b/client/websocketclient.h
index a7d2dc6..7719ce1 100644
--- a/client/websocketclient.h
+++ b/client/websocketclient.h
@@ -1,64 +1,64 @@
// SPDX-FileCopyrightText: 2023 g10 code Gmbh
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QPointer>
#include <QWebSocket>
#include <chrono>
class EmailViewer;
using namespace std::chrono;
class WebsocketClient : public QObject
{
Q_OBJECT
Q_PROPERTY(State state READ state NOTIFY stateChanged)
Q_PROPERTY(QString stateDisplay READ stateDisplay NOTIFY stateChanged)
public:
enum State {
Connected,
NotConnected,
};
static WebsocketClient &self(const QUrl &url = {}, const QString &clientId = {});
State state() const;
QString stateDisplay() const;
/// \params fromEmail The email address who should send this EWS request.
/// \params requestId Identifier of the request, will be send back in ewsResponseReceived
/// \params requestBody The SOAP request body.
bool sendEWSRequest(const QString &fromEmail, const QString &requestId, const QString &requestBody);
void sendViewerClosed(const QString &fromEmail);
void sendViewerOpened(const QString &fromEmail);
Q_SIGNALS:
- void stateChanged();
+ void stateChanged(const QString &state);
void ewsResponseReceived(const QString &requestId, const QString &responseBody);
private Q_SLOTS:
void slotConnected();
void slotErrorOccurred(QAbstractSocket::SocketError error);
void slotTextMessageReceived(QString message);
private:
explicit WebsocketClient(const QUrl &url, const QString &clientId);
void reconnect();
bool m_wasConnected = false;
QWebSocket m_webSocket;
QUrl m_url;
QStringList m_emails;
QString m_clientId;
std::chrono::milliseconds m_delay = 2000ms;
State m_state = NotConnected;
QString m_stateDisplay;
QPointer<EmailViewer> m_emailViewer;
};

File Metadata

Mime Type
text/x-diff
Expires
Mon, Jan 12, 10:31 PM (1 d, 7 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
c2/d3/a4ae0e6881e9d5221d22e6d9fc11

Event Timeline