aboutsummaryrefslogtreecommitdiffstats
path: root/src/m_key_server_sync/KeyServerSyncModule.cpp
diff options
context:
space:
mode:
authorsaturneric <[email protected]>2025-12-11 22:59:09 +0100
committersaturneric <[email protected]>2025-12-11 22:59:09 +0100
commit562e92a148402b98938c7bfb1f91b2a0e99e53d5 (patch)
tree5a2e65977368bab55da94cf277022994535c3a33 /src/m_key_server_sync/KeyServerSyncModule.cpp
parentfeat(ui): add `GnuPG` info dialog to help menu (diff)
downloadmodules-562e92a148402b98938c7bfb1f91b2a0e99e53d5.tar.gz
modules-562e92a148402b98938c7bfb1f91b2a0e99e53d5.zip
feat(key-server): add public key search and import dialog
* Introduce UI dialog for searching and importing public keys from key servers using multiple search criteria (key ID, email, fingerprint). * Implement backend integration for querying and importing keys from standard PKS-compatible servers. * Add logic to parse, display, and import key details with status indicators and user feedback. * Enhance menu integration and key operation options in the UI for improved usability and workflow. Relates to user-requested feature for easier key server integration.
Diffstat (limited to 'src/m_key_server_sync/KeyServerSyncModule.cpp')
-rw-r--r--src/m_key_server_sync/KeyServerSyncModule.cpp240
1 files changed, 236 insertions, 4 deletions
diff --git a/src/m_key_server_sync/KeyServerSyncModule.cpp b/src/m_key_server_sync/KeyServerSyncModule.cpp
index ad56d2d..65153c3 100644
--- a/src/m_key_server_sync/KeyServerSyncModule.cpp
+++ b/src/m_key_server_sync/KeyServerSyncModule.cpp
@@ -28,9 +28,13 @@
#include "KeyServerSyncModule.h"
+#include <GFSDKGpg.h>
+
#include <QtCore>
+#include <QtWidgets>
#include "GFModuleDefine.h"
+#include "SearchKeyDialog.h"
#include "VKSInterface.h"
GF_MODULE_API_DEFINE_V2("com.bktus.gpgfrontend.module.key_server_sync",
@@ -48,9 +52,241 @@ auto GFActiveModule() -> int {
LISTEN("REQUEST_GET_PUBLIC_KEY_BY_FINGERPRINT");
LISTEN("REQUEST_GET_PUBLIC_KEY_BY_KEY_ID");
LISTEN("REQUEST_UPLOAD_PUBLIC_KEY");
+ LISTEN("MAINWINDOW_MENU_MOUNTED");
+ LISTEN("KEY_PAIR_OPERA_MENU_CREATED");
return 0;
}
+namespace {
+
+auto UploadKeyToServer(QWidget* parent, int channel, const QString& key_id)
+ -> int {
+ char* key_data = nullptr;
+ int size = 0;
+ auto ret = GFGpgExportKey(channel, QDUP(key_id), 1, &key_data, &size);
+ if (ret != 0 || key_data == nullptr || size <= 0) {
+ return -1;
+ }
+
+ auto data = UDUP(key_data);
+
+ if (data.size() != size) {
+ FLOG_ERROR("uploaded key data size mismatch: expected %1, got %2", size,
+ data.size());
+ return -1;
+ }
+
+ QByteArray key_data_array = data.toUtf8();
+
+ auto* vks = new VKSInterface();
+ QObject::connect(
+ vks, &VKSInterface::SignalKeyUploaded, QThread::currentThread(),
+ [parent](const QString& fpr, const QJsonObject& status,
+ const QString& token) {
+ // Handle successful response
+ QString status_message = QCoreApplication::translate(
+ "GTrC", "The following email addresses have status:\n");
+ QStringList email_list;
+ if (!status.isEmpty()) {
+ for (auto it = status.constBegin(); it != status.constEnd(); ++it) {
+ status_message +=
+ QString("%1: %2\n").arg(it.key(), it.value().toString());
+ email_list.append(it.key());
+ }
+ } else {
+ status_message += QCoreApplication::translate(
+ "GTrC", "Could not parse status information.");
+ }
+
+ // Notify user of successful upload and status details
+ QMessageBox::information(
+ parent,
+ QCoreApplication::translate("GTrC", "Public Key Upload Successful"),
+ QCoreApplication::translate(
+ "GTrC",
+ "The public key was successfully uploaded to the "
+ "key server keys.openpgp.org.\n"
+ "Fingerprint: %1\n\n"
+ "%2\n"
+ "Please check your email (%3) for further "
+ "verification from keys.openpgp.org.\n\n"
+ "Note: For verification, you can find more "
+ "information here: "
+ "https://keys.openpgp.org/about")
+ .arg(fpr, status_message, email_list.join(", ")));
+ });
+
+ QObject::connect(
+ vks, &VKSInterface::SignalErrorOccurred, QThread::currentThread(),
+ [parent, key_id](const QString& error, const QString& data) {
+ QMessageBox::critical(
+ parent, QCoreApplication::translate("GTrC", "Key Upload Failed"),
+ QCoreApplication::translate(
+ "GTrC",
+ "Failed to upload public key to the server.\n"
+ "Fingerprint: %1\n"
+ "Error: %2")
+ .arg(key_id, error));
+ });
+
+ QObject::connect(vks, &VKSInterface::SignalKeyRetrieved, vks,
+ &VKSInterface::deleteLater);
+
+ vks->UploadKey(key_data);
+
+ return 0;
+}
+
+auto UpdateKeyFromKeyServer(QWidget* parent, int channel, const QString& fpr)
+ -> int {
+ auto* vks = new VKSInterface();
+
+ QObject::connect(vks, &VKSInterface::SignalKeyRetrieved,
+ QThread::currentThread(),
+ [parent, channel](const QString& key_data) {
+ auto data = key_data.toUtf8();
+ GFGpgImportKeys(channel, parent, data.constData(),
+ static_cast<int>(data.size()));
+ });
+
+ QObject::connect(
+ vks, &VKSInterface::SignalErrorOccurred, QThread::currentThread(),
+ [parent, fpr](const QString& error, const QString& data) {
+ QMessageBox::critical(
+ parent, QCoreApplication::translate("GTrC", "Key Update Failed"),
+ QCoreApplication::translate(
+ "GTrC",
+ "Failed to retrieve public key from the server.\n"
+ "Key ID: %1\n"
+ "Error: %2")
+ .arg(fpr, error));
+ });
+ QObject::connect(vks, &VKSInterface::SignalKeyRetrieved, vks,
+ &VKSInterface::deleteLater);
+ vks->GetByFingerprint(fpr);
+ return 0;
+}
+
+} // namespace
+
+REGISTER_EVENT_HANDLER(MAINWINDOW_MENU_MOUNTED, [](const MEvent& event) -> int {
+ LOG_DEBUG("main window menu mounted event: processing");
+
+ if (!event.contains("main_window")) {
+ LOG_DEBUG("main window menu mounted event: no main_window found");
+ CB_ERR(event, -1, "no main_window found");
+ }
+
+ auto* main_window = GFUIGetGUIObjectAs<QMainWindow>(event["main_window"]);
+ if (!main_window) {
+ LOG_ERROR(
+ "main window menu mounted: main_window handle invalid or not "
+ "QMainWindow");
+ CB_ERR(event, -1, "main_window handle invalid or not QMainWindow");
+ }
+
+ if (!event.contains("import_key_menu")) {
+ LOG_DEBUG("main window menu mounted event: no import_key_menu found");
+ CB_ERR(event, -1, "no import_key_menu found");
+ }
+
+ auto* import_key_menu = GFUIGetGUIObjectAs<QMenu>(event["import_key_menu"]);
+ if (!import_key_menu) {
+ LOG_ERROR(
+ "main window menu mounted: import_key_menu handle invalid or not "
+ "QMenu");
+ CB_ERR(event, -1, "import_key_menu handle invalid or not QMenu");
+ }
+
+ LOG_DEBUG("adding key server sync actions to import key menu");
+
+ QMetaObject::invokeMethod(
+ QApplication::instance(),
+ [&]() -> void {
+ QWidget* parent =
+ qobject_cast<QWidget*>(static_cast<QObject*>(main_window));
+ auto* action = new QAction(
+ QCoreApplication::translate("GTrC", "Key Server"), nullptr);
+ action->setToolTip(QCoreApplication::translate(
+ "GTrC", "Import public keys from a trusted key server."));
+ action->setIcon(QIcon(":/icons/import_key_from_server.png"));
+ QObject::connect(action, &QAction::triggered, parent, [=]() {
+ auto* dialog = new SearchKeyDialog(parent);
+ dialog->show();
+ });
+ import_key_menu->addAction(action);
+ },
+ Qt::BlockingQueuedConnection);
+ CB_SUCC(event);
+});
+
+REGISTER_EVENT_HANDLER(
+ KEY_PAIR_OPERA_MENU_CREATED, [](const MEvent& event) -> int {
+ auto* tab = GFUIGetGUIObjectAs<QWidget>(event["tab"]);
+ if (!tab) {
+ LOG_ERROR(
+ "key pair opera menu created: tab handle "
+ "invalid or not KeyPairOperaTab");
+ CB_ERR(event, -1, "tab handle invalid or not KeyPairOperaTab");
+ }
+
+ auto* layout = GFUIGetGUIObjectAs<QVBoxLayout>(event["opera_layout"]);
+ if (!layout) {
+ LOG_ERROR(
+ "key pair opera menu created: opera_menu handle "
+ "invalid or not QMenu");
+ CB_ERR(event, -1, "opera_menu handle invalid or not QMenu");
+ }
+
+ auto is_private_key = event["is_private_key"].toInt() != 0;
+ auto has_master_key = event["has_master_key"].toInt() != 0;
+
+ auto channel = event["channel"].toInt();
+ auto key_id = event["key_id"];
+ auto fpr = event["fpr"];
+
+ FLOG_DEBUG(
+ "adding key server sync actions: key id: %1, channel: %2, is "
+ "private key: %3, has master key: %4",
+ key_id, channel, static_cast<int>(is_private_key),
+ static_cast<int>(has_master_key));
+
+ QMetaObject::invokeMethod(QApplication::instance(), [=]() -> void {
+ auto* menu = new QMenu(tab);
+
+ auto* key_server_opera_button = new QPushButton(
+ QCoreApplication::translate("GTrC", "Key Server Operations"));
+ key_server_opera_button->setStyleSheet("text-align:center;");
+ key_server_opera_button->setMenu(menu);
+
+ // add upload / update key actions
+ auto* upload_key_pair = new QAction(QCoreApplication::translate(
+ "GTrC", "Publish Public Key to Key Server"));
+ QObject::connect(upload_key_pair, &QAction::triggered, tab,
+ [=]() { UploadKeyToServer(tab, channel, key_id); });
+ if (!(is_private_key && has_master_key)) {
+ upload_key_pair->setDisabled(true);
+ }
+
+ auto* update_key_pair = new QAction(QCoreApplication::translate(
+ "GTrC", "Refresh Public Key From Key Server"));
+ QObject::connect(update_key_pair, &QAction::triggered, tab,
+ [=]() { UpdateKeyFromKeyServer(tab, channel, fpr); });
+
+ // when a key has primary key, it should always upload to keyserver.
+ if (has_master_key) {
+ update_key_pair->setDisabled(true);
+ }
+
+ menu->addAction(upload_key_pair);
+ menu->addAction(update_key_pair);
+
+ layout->addWidget(key_server_opera_button);
+ });
+
+ CB_SUCC(event);
+ });
+
REGISTER_EVENT_HANDLER(
REQUEST_GET_PUBLIC_KEY_BY_FINGERPRINT, [](const MEvent& event) -> int {
if (event["fingerprint"].isEmpty())
@@ -105,8 +341,6 @@ REGISTER_EVENT_HANDLER(
{"key_data", key},
});
});
- QObject::connect(vks, &VKSInterface::SignalKeyRetrieved, vks,
- &VKSInterface::deleteLater);
QObject::connect(vks, &VKSInterface::SignalErrorOccurred,
QThread::currentThread(),
[event](const QString& error, const QString& data) {
@@ -145,8 +379,6 @@ REGISTER_EVENT_HANDLER(
{"token", token},
});
});
- QObject::connect(vks, &VKSInterface::SignalKeyRetrieved, vks,
- &VKSInterface::deleteLater);
QObject::connect(vks, &VKSInterface::SignalErrorOccurred,
QThread::currentThread(),
[event](const QString& error, const QString& data) {