pidgin/pidgin

c792b39da167
Parents 1bad06536f81
Children cc0679f47fd9
Port the secretservice keyring to PurpleCredentialProvider

Port the secret service keyring to PurpleCredentialProvider

Testing Done:
Made sure passwords were created and deleted in seahorse.

Reviewed at https://reviews.imfreedom.org/r/401/
--- a/libpurple/core.c Mon Jan 25 20:32:04 2021 -0600
+++ b/libpurple/core.c Tue Jan 26 01:28:00 2021 -0600
@@ -149,12 +149,13 @@
purple_cmds_init();
purple_protocols_init();
+ purple_credential_manager_startup(); /* before accounts */
+
/* Since plugins get probed so early we should probably initialize their
* subsystem right away too.
*/
purple_plugins_init();
- purple_credential_manager_startup(); /* before accounts */
purple_keyring_init(); /* before accounts */
purple_theme_manager_init();
--- a/libpurple/plugins/keyrings/secretservice.c Mon Jan 25 20:32:04 2021 -0600
+++ b/libpurple/plugins/keyrings/secretservice.c Tue Jan 26 01:28:00 2021 -0600
@@ -1,24 +1,19 @@
-/* purple
- * @file secretservice.c Secret Service password storage
- * @ingroup plugins
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * source distribution.
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
*
- * This program is distributed in the hope that it will be useful,
+ * This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program ; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
*/
/* TODO
@@ -43,15 +38,16 @@
project name. It may not be appropriate to translate this string, but
transliterating to your alphabet is reasonable. More info about the
project can be found at https://wiki.gnome.org/Projects/Libsecret */
-#define SECRETSERVICE_NAME N_("Secret Service")
-#define SECRETSERVICE_ID "keyring-libsecret"
-#define SECRETSERVICE_DOMAIN (g_quark_from_static_string(SECRETSERVICE_ID))
+#define SECRETSERVICE_NAME N_("Secret Service")
+#define SECRETSERVICE_ID "secret-service"
-static PurpleKeyring *keyring_handler = NULL;
-static GCancellable *keyring_cancellable = NULL;
+/******************************************************************************
+ * Globals
+ *****************************************************************************/
+static PurpleCredentialProvider *instance = NULL;
-static const SecretSchema purple_schema = {
- "im.pidgin.Purple", SECRET_SCHEMA_NONE,
+static const SecretSchema purple_secret_service_schema = {
+ "im.pidgin.Purple3", SECRET_SCHEMA_NONE,
{
{"user", SECRET_SCHEMA_ATTRIBUTE_STRING},
{"protocol", SECRET_SCHEMA_ATTRIBUTE_STRING},
@@ -61,263 +57,232 @@
0, 0, 0, 0, 0, 0, 0, 0
};
-typedef struct _InfoStorage InfoStorage;
+#define PURPLE_TYPE_SECRET_SERVICE (purple_secret_service_get_type())
+G_DECLARE_FINAL_TYPE(PurpleSecretService, purple_secret_service,
+ PURPLE, SECRET_SERVICE, PurpleCredentialProvider)
-struct _InfoStorage
-{
- PurpleAccount *account;
- gpointer cb;
- gpointer user_data;
+struct _PurpleSecretService {
+ PurpleCredentialProvider parent;
};
-/***********************************************/
-/* Keyring interface */
-/***********************************************/
-static void
-ss_g_error_to_keyring_error(GError **error, PurpleAccount *account)
-{
- GError *old_error;
- GError *new_error = NULL;
-
- g_return_if_fail(error != NULL);
-
- old_error = *error;
+G_DEFINE_DYNAMIC_TYPE(PurpleSecretService, purple_secret_service,
+ PURPLE_TYPE_CREDENTIAL_PROVIDER)
- if (g_error_matches(old_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
- new_error = g_error_new_literal(PURPLE_KEYRING_ERROR,
- PURPLE_KEYRING_ERROR_CANCELLED,
- _("Operation cancelled."));
- } else if (g_error_matches(old_error, G_DBUS_ERROR,
- G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND) ||
- g_error_matches(old_error, G_DBUS_ERROR,
- G_DBUS_ERROR_IO_ERROR)) {
- purple_debug_info("keyring-libsecret",
- "Failed to communicate with Secret "
- "Service (account: %s (%s)).\n",
- purple_account_get_username(account),
- purple_account_get_protocol_id(account));
- new_error = g_error_new(PURPLE_KEYRING_ERROR,
- PURPLE_KEYRING_ERROR_BACKENDFAIL,
- "Failed to communicate with Secret "
- "Service (account: %s).",
- purple_account_get_username(account));
- } else if (g_error_matches(old_error, SECRET_ERROR,
- SECRET_ERROR_IS_LOCKED)) {
- purple_debug_info("keyring-libsecret",
- "Secret Service is locked (account: %s (%s)).\n",
- purple_account_get_username(account),
- purple_account_get_protocol_id(account));
- new_error = g_error_new(PURPLE_KEYRING_ERROR,
- PURPLE_KEYRING_ERROR_ACCESSDENIED,
- "Secret Service is locked (account: %s).",
- purple_account_get_username(account));
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+purple_secret_service_read_password_callback(GObject *obj,
+ GAsyncResult *result,
+ gpointer data)
+{
+ GTask *task = G_TASK(data);
+ GError *error = NULL;
+ gchar *password = NULL;
+
+ password = secret_password_lookup_finish(result, &error);
+
+ if(error != NULL) {
+ g_task_return_error(task, error);
} else {
- purple_debug_error("keyring-libsecret",
- "Unknown error (account: %s (%s), "
- "domain: %s, code: %d): %s.\n",
- purple_account_get_username(account),
- purple_account_get_protocol_id(account),
- g_quark_to_string(old_error->domain),
- old_error->code, old_error->message);
- new_error = g_error_new(PURPLE_KEYRING_ERROR,
- PURPLE_KEYRING_ERROR_BACKENDFAIL,
- "Unknown error (account: %s).",
- purple_account_get_username(account));
+ g_task_return_pointer(task, password, g_free);
}
- g_clear_error(error);
- g_propagate_error(error, new_error);
+ g_object_unref(G_OBJECT(task));
}
static void
-ss_read_continue(GObject *object, GAsyncResult *result, gpointer data)
+purple_secret_service_write_password_callback(GObject *obj,
+ GAsyncResult *result,
+ gpointer data)
{
- InfoStorage *storage = data;
- PurpleAccount *account = storage->account;
- PurpleKeyringReadCallback cb = storage->cb;
- char *password;
+ GTask *task = G_TASK(data);
GError *error = NULL;
+ gboolean ret = FALSE;
- password = secret_password_lookup_finish(result, &error);
+ ret = secret_password_store_finish(result, &error);
- if (error != NULL) {
- ss_g_error_to_keyring_error(&error, account);
- } else if (password == NULL) {
- error = g_error_new(PURPLE_KEYRING_ERROR,
- PURPLE_KEYRING_ERROR_NOPASSWORD,
- "No password found for account: %s",
- purple_account_get_username(account));
+ if(error != NULL) {
+ g_task_return_error(task, error);
+ } else {
+ g_task_return_boolean(task, ret);
}
- if (cb != NULL) {
- cb(account, password, error, storage->user_data);
- }
-
- g_clear_error(&error);
- g_free(storage);
+ g_object_unref(G_OBJECT(task));
}
static void
-ss_read(PurpleAccount *account, PurpleKeyringReadCallback cb, gpointer data)
+purple_secret_service_clear_password_callback(GObject *obj,
+ GAsyncResult *result,
+ gpointer data)
{
- InfoStorage *storage = g_new0(InfoStorage, 1);
+ GTask *task = G_TASK(data);
+ GError *error = NULL;
+ gboolean ret = FALSE;
+
+ ret = secret_password_clear_finish(result, &error);
+
+ if(error != NULL) {
+ g_task_return_error(task, error);
+ } else {
+ g_task_return_boolean(task, ret);
+ }
+
+ g_object_unref(G_OBJECT(task));
+}
- storage->account = account;
- storage->cb = cb;
- storage->user_data = data;
+/******************************************************************************
+ * PurpleCredentialProvider Implementation
+ *****************************************************************************/
+static void
+purple_secret_service_read_password_async(PurpleCredentialProvider *provider,
+ PurpleAccount *account,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer data)
+{
+ GTask *task = g_task_new(G_OBJECT(provider), cancellable, callback, data);
- secret_password_lookup(&purple_schema, keyring_cancellable,
- ss_read_continue, storage,
- "user", purple_account_get_username(account),
- "protocol", purple_account_get_protocol_id(account), NULL);
+ secret_password_lookup(&purple_secret_service_schema, cancellable,
+ purple_secret_service_read_password_callback, task,
+ "user", purple_account_get_username(account),
+ "protocol", purple_account_get_protocol_id(account),
+ NULL);
+}
+
+static gchar *
+purple_secret_service_read_password_finish(PurpleCredentialProvider *provider,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider), FALSE);
+ g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);
+
+ return g_task_propagate_pointer(G_TASK(result), error);
}
static void
-ss_save_continue(GError *error, gpointer data)
+purple_secret_service_write_password_async(PurpleCredentialProvider *provider,
+ PurpleAccount *account,
+ const gchar *password,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer data)
{
- InfoStorage *storage = data;
- PurpleKeyringSaveCallback cb;
- PurpleAccount *account;
-
- account = storage->account;
- cb = storage->cb;
+ GTask *task = NULL;
+ gchar *label = NULL;
+ const gchar *username = NULL;
- if (error != NULL) {
- ss_g_error_to_keyring_error(&error, account);
- } else {
- purple_debug_info("keyring-libsecret", "Password for %s updated.\n",
- purple_account_get_username(account));
- }
+ task = g_task_new(G_OBJECT(provider), cancellable, callback, data);
+ username = purple_account_get_username(account);
- if (cb != NULL)
- cb(account, error, storage->user_data);
-
- g_clear_error(&error);
- g_free(storage);
+ label = g_strdup_printf(_("libpurple password for account %s"), username);
+ secret_password_store(&purple_secret_service_schema,
+ SECRET_COLLECTION_DEFAULT, label, password,
+ cancellable,
+ purple_secret_service_write_password_callback, task,
+ "user", username,
+ "protocol", purple_account_get_protocol_id(account),
+ NULL);
+ g_free(label);
}
-static void
-ss_store_continue(GObject *object, GAsyncResult *result, gpointer data)
+static gboolean
+purple_secret_service_write_password_finish(PurpleCredentialProvider *provider,
+ GAsyncResult *result,
+ GError **error)
{
- GError *error = NULL;
-
- secret_password_store_finish(result, &error);
+ g_return_val_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider), FALSE);
+ g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);
- ss_save_continue(error, data);
-}
-
-static void
-ss_clear_continue(GObject *object, GAsyncResult *result, gpointer data)
-{
- GError *error = NULL;
-
- secret_password_clear_finish(result, &error);
-
- ss_save_continue(error, data);
+ return g_task_propagate_boolean(G_TASK(result), error);
}
static void
-ss_save(PurpleAccount *account,
- const gchar *password,
- PurpleKeyringSaveCallback cb,
- gpointer data)
+purple_secret_service_clear_password_async(PurpleCredentialProvider *provider,
+ PurpleAccount *account,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer data)
{
- InfoStorage *storage = g_new0(InfoStorage, 1);
+ GTask *task = g_task_new(G_OBJECT(provider), cancellable, callback, data);
- storage->account = account;
- storage->cb = cb;
- storage->user_data = data;
-
- if (password != NULL && *password != '\0') {
- const char *username = purple_account_get_username(account);
- char *label;
-
- purple_debug_info("keyring-libsecret",
- "Updating password for account %s (%s).\n",
- username, purple_account_get_protocol_id(account));
+ secret_password_clear(&purple_secret_service_schema, cancellable,
+ purple_secret_service_clear_password_callback, task,
+ "user", purple_account_get_username(account),
+ "protocol", purple_account_get_protocol_id(account),
+ NULL);
+}
- label = g_strdup_printf(_("Pidgin IM password for account %s"), username);
- secret_password_store(&purple_schema, SECRET_COLLECTION_DEFAULT,
- label, password, keyring_cancellable,
- ss_store_continue, storage,
- "user", username,
- "protocol", purple_account_get_protocol_id(account),
- NULL);
- g_free(label);
+static gboolean
+purple_secret_service_clear_password_finish(PurpleCredentialProvider *provider,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider), FALSE);
+ g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);
- } else { /* password == NULL, delete password. */
- purple_debug_info("keyring-libsecret",
- "Forgetting password for account %s (%s).\n",
- purple_account_get_username(account),
- purple_account_get_protocol_id(account));
+ return g_task_propagate_boolean(G_TASK(result), error);
+}
- secret_password_clear(&purple_schema, keyring_cancellable,
- ss_clear_continue, storage,
- "user", purple_account_get_username(account),
- "protocol", purple_account_get_protocol_id(account),
- NULL);
- }
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+purple_secret_service_init(PurpleSecretService *ss) {
}
static void
-ss_cancel(void)
-{
- g_cancellable_cancel(keyring_cancellable);
+purple_secret_service_class_init(PurpleSecretServiceClass *klass) {
+ PurpleCredentialProviderClass *provider_class = NULL;
- /* Swap out cancelled cancellable for new one for further operations */
- g_clear_object(&keyring_cancellable);
- keyring_cancellable = g_cancellable_new();
+ provider_class = PURPLE_CREDENTIAL_PROVIDER_CLASS(klass);
+ provider_class->read_password_async =
+ purple_secret_service_read_password_async;
+ provider_class->read_password_finish =
+ purple_secret_service_read_password_finish;
+ provider_class->write_password_async =
+ purple_secret_service_write_password_async;
+ provider_class->write_password_finish =
+ purple_secret_service_write_password_finish;
+ provider_class->clear_password_async =
+ purple_secret_service_clear_password_async;
+ provider_class->clear_password_finish =
+ purple_secret_service_clear_password_finish;
}
static void
-ss_close(void)
-{
- ss_cancel();
+purple_secret_service_class_finalize(PurpleSecretServiceClass *klass) {
}
-static gboolean
-ss_init(GError **error)
-{
- keyring_cancellable = g_cancellable_new();
-
- keyring_handler = purple_keyring_new();
-
- purple_keyring_set_name(keyring_handler, _(SECRETSERVICE_NAME));
- purple_keyring_set_id(keyring_handler, SECRETSERVICE_ID);
- purple_keyring_set_read_password(keyring_handler, ss_read);
- purple_keyring_set_save_password(keyring_handler, ss_save);
- purple_keyring_set_cancel_requests(keyring_handler, ss_cancel);
- purple_keyring_set_close_keyring(keyring_handler, ss_close);
-
- purple_keyring_register(keyring_handler);
-
- return TRUE;
+/******************************************************************************
+ * API
+ *****************************************************************************/
+static PurpleCredentialProvider *
+purple_secret_service_new(void) {
+ return PURPLE_CREDENTIAL_PROVIDER(g_object_new(
+ PURPLE_TYPE_SECRET_SERVICE,
+ "id", SECRETSERVICE_ID,
+ "name", _(SECRETSERVICE_NAME),
+ NULL
+ ));
}
-static void
-ss_uninit(void)
-{
- ss_close();
- purple_keyring_unregister(keyring_handler);
- purple_keyring_free(keyring_handler);
- keyring_handler = NULL;
+/******************************************************************************
+ * Plugin Exports
+ *****************************************************************************/
+G_MODULE_EXPORT GPluginPluginInfo *gplugin_query(GPluginPlugin *plugin, GError **error);
+G_MODULE_EXPORT gboolean gplugin_load(GPluginPlugin *plugin, GError **error);
+G_MODULE_EXPORT gboolean gplugin_unload(GPluginPlugin *plugin, GError **error);
- g_clear_object(&keyring_cancellable);
-}
-
-/***********************************************/
-/* Plugin interface */
-/***********************************************/
-
-static PurplePluginInfo *
-plugin_query(GError **error)
-{
+G_MODULE_EXPORT GPluginPluginInfo *
+gplugin_query(GPluginPlugin *plugin, GError **error) {
const gchar * const authors[] = {
"Elliott Sales de Andrade (qulogic[at]pidgin.im)",
NULL
};
- return purple_plugin_info_new(
+ return GPLUGIN_PLUGIN_INFO(purple_plugin_info_new(
"id", SECRETSERVICE_ID,
"name", SECRETSERVICE_NAME,
"version", DISPLAY_VERSION,
@@ -327,29 +292,39 @@
"authors", authors,
"website", PURPLE_WEBSITE,
"abi-version", PURPLE_ABI_VERSION,
- "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL,
+ "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL |
+ PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
NULL
- );
-}
-
-static gboolean
-plugin_load(PurplePlugin *plugin, GError **error)
-{
- return ss_init(error);
+ ));
}
-static gboolean
-plugin_unload(PurplePlugin *plugin, GError **error)
-{
- if (purple_keyring_get_inuse() == keyring_handler) {
- g_set_error(error, SECRETSERVICE_DOMAIN, 0, "The keyring is currently "
- "in use.");
- return FALSE;
+G_MODULE_EXPORT gboolean
+gplugin_load(GPluginPlugin *plugin, GError **error) {
+ PurpleCredentialManager *manager = NULL;
+
+ purple_secret_service_register_type(G_TYPE_MODULE(plugin));
+
+ manager = purple_credential_manager_get_default();
+
+ instance = purple_secret_service_new();
+
+ return purple_credential_manager_register_provider(manager, instance,
+ error);
+}
+
+G_MODULE_EXPORT gboolean
+gplugin_unload(GPluginPlugin *plugin, GError **error) {
+ PurpleCredentialManager *manager = NULL;
+ gboolean ret = FALSE;
+
+ manager = purple_credential_manager_get_default();
+ ret = purple_credential_manager_unregister_provider(manager, instance,
+ error);
+ if(!ret) {
+ return ret;
}
- ss_uninit();
+ g_clear_object(&instance);
return TRUE;
}
-
-PURPLE_PLUGIN_INIT(secret_service, plugin_query, plugin_load, plugin_unload);
--- a/libpurple/purplecredentialmanager.c Mon Jan 25 20:32:04 2021 -0600
+++ b/libpurple/purplecredentialmanager.c Tue Jan 26 01:28:00 2021 -0600
@@ -19,7 +19,12 @@
#include <glib/gi18n-lib.h>
#include "purplecredentialmanager.h"
+
+#include "core.h"
+#include "debug.h"
+#include "prefs.h"
#include "purpleprivate.h"
+#include "util.h"
enum {
SIG_PROVIDER_REGISTERED,
@@ -112,6 +117,23 @@
g_object_unref(G_OBJECT(task));
}
+/******************************************************************************
+ * Purple Callbacks
+ *****************************************************************************/
+static void
+purple_credential_manager_core_init_cb(gpointer data) {
+ PurpleCredentialManager *manager = PURPLE_CREDENTIAL_MANAGER(data);
+ PurpleCredentialManagerPrivate *priv = NULL;
+
+ priv = purple_credential_manager_get_instance_private(manager);
+ if(!PURPLE_IS_CREDENTIAL_PROVIDER(priv->active_provider)) {
+ purple_notify_error(NULL, _("Credential Manager"),
+ _("Failed to load the selected credential "
+ "provider."),
+ _("Check your system configuration or select "
+ "another one in the preferences dialog."), NULL);
+ }
+}
/******************************************************************************
* GObject Implementation
@@ -138,6 +160,13 @@
priv->providers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
g_object_unref);
+
+ /* Connect to the core-initialized signal so we can alert the user if we
+ * were unable to find their credential provider.
+ */
+ purple_signal_connect(purple_get_core(), "core-initialized", manager,
+ PURPLE_CALLBACK(purple_credential_manager_core_init_cb),
+ manager);
}
static void
@@ -267,6 +296,20 @@
g_signal_emit(G_OBJECT(manager), signals[SIG_PROVIDER_REGISTERED], 0,
provider);
+ /* If we don't currently have an active provider, check if the newly
+ * registered provider has the id of the stored provider in preferences.
+ * If it is, go ahead and make it the active provider.
+ */
+ if(!PURPLE_IS_CREDENTIAL_PROVIDER(priv->active_provider)) {
+ const gchar *wanted = NULL;
+
+ wanted = purple_prefs_get_string("/purple/credentials/active-provider");
+
+ if(purple_strequal(wanted, id)) {
+ purple_credential_manager_set_active_provider(manager, id, error);
+ }
+ }
+
return TRUE;
}
@@ -337,6 +380,9 @@
g_clear_object(&old);
+ purple_debug_info("PurpleCredentialProvider",
+ "set active provider to '%s'", id);
+
return TRUE;
}