Signed-off-by: Jakub Filak <jfilak(a)redhat.com>
---
configure.ac | 1 +
libreport.spec.in | 7 -
po/POTFILES.in | 1 +
src/gtk-helpers/Makefile.am | 6 +-
src/gtk-helpers/abrt-keyring.c | 130 ---
src/gtk-helpers/event_config_dialog.c | 71 +-
src/gtk-helpers/internal_libreport_gtk.h | 15 +-
src/gtk-helpers/secrets.c | 1313 ++++++++++++++++++++++++++++++
src/gui-wizard-gtk/main.c | 2 +-
9 files changed, 1336 insertions(+), 210 deletions(-)
delete mode 100644 src/gtk-helpers/abrt-keyring.c
create mode 100644 src/gtk-helpers/secrets.c
diff --git a/configure.ac b/configure.ac
index 9b8082d..c875346 100644
--- a/configure.ac
+++ b/configure.ac
@@ -99,6 +99,7 @@ AC_SUBST(PYTHON_LIBS)
PKG_CHECK_MODULES([GTK], [gtk+-3.0])
PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.21])
+PKG_CHECK_MODULES([GIO], [gio-2.0])
PKG_CHECK_MODULES([DBUS], [dbus-1])
PKG_CHECK_MODULES([LIBXML], [libxml-2.0])
PKG_CHECK_MODULES([NSS], [nss])
diff --git a/libreport.spec.in b/libreport.spec.in
index e8a1ed0..cc06b73 100644
--- a/libreport.spec.in
+++ b/libreport.spec.in
@@ -35,13 +35,6 @@ Requires: libreport-filesystem
# FIXME: can be removed when F15 will EOLed, needs to stay in rhel6!
Requires: libreport-python
-# for rhel6
-%if 0%{?rhel} == 6
-BuildRequires: gnome-keyring-devel
-%else
-BuildRequires: libgnome-keyring-devel
-%endif
-
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
%description
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 09ff83e..118ca80 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -6,6 +6,7 @@ src/cli/cli-report.c
src/client-python/__init__.py
src/client-python/debuginfo.py
src/gtk-helpers/event_config_dialog.c
+src/gtk-helpers/secrets.c
src/gui-wizard-gtk/main.c
src/gui-wizard-gtk/wizard.c
src/gui-wizard-gtk/wizard.glade
diff --git a/src/gtk-helpers/Makefile.am b/src/gtk-helpers/Makefile.am
index 06c3ec8..a821b5d 100644
--- a/src/gtk-helpers/Makefile.am
+++ b/src/gtk-helpers/Makefile.am
@@ -10,7 +10,7 @@ lib_LTLIBRARIES = \
libreport_gtk_la_SOURCES = \
event_config_dialog.c \
- abrt-keyring.c \
+ secrets.c \
hyperlinks.c \
autowrapped_label.c
@@ -19,14 +19,14 @@ libreport_gtk_la_CPPFLAGS = \
-I$(srcdir)/../lib \
$(GTK_CFLAGS) \
$(GLIB_CFLAGS) \
- $(GNOME_KEYRING_CFLAGS) \
+ $(GIO_CFLAGS) \
-D_GNU_SOURCE
libreport_gtk_la_LDFLAGS = \
-version-info 0:1:0
libreport_gtk_la_LIBADD = \
$(GTK_LIBS) \
$(GLIB_LIBS) \
- $(GNOME_KEYRING_LIBS) \
+ $(GIO_LIBS) \
../lib/libreport.la
EXTRA_DIST = libreport-gtk.pc.in
diff --git a/src/gtk-helpers/abrt-keyring.c b/src/gtk-helpers/abrt-keyring.c
deleted file mode 100644
index 4e830ca..0000000
--- a/src/gtk-helpers/abrt-keyring.c
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- Copyright (C) 2011 ABRT Team
- Copyright (C) 2011 RedHat inc.
-
- 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 program 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.
-
- 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 02110-1301 USA.
-*/
-#include <gnome-keyring.h>
-#include "internal_libreport.h"
-#include "internal_libreport_gtk.h"
-
-static char *keyring_name;
-static bool got_keyring = 0;
-bool g_keyring_available = 1; //by default we assume that keyring is available
-
-guint32 find_keyring_item_id_for_event(const char *event_name)
-{
- GnomeKeyringAttributeList *attrs = gnome_keyring_attribute_list_new();
- GList *found = NULL;
- gnome_keyring_attribute_list_append_string(attrs, "libreportEventConfig",
event_name);
- GnomeKeyringResult result = gnome_keyring_find_items_sync(
- GNOME_KEYRING_ITEM_GENERIC_SECRET,
- attrs,
- &found);
- gnome_keyring_attribute_list_free(attrs);
-
- //let's hope 0 is not valid item_id
- guint32 item_id = 0;
- if (result != GNOME_KEYRING_RESULT_OK)
- goto ret;
- if (found)
- item_id = ((GnomeKeyringFound *)found->data)->item_id;
- ret:
- if (found)
- gnome_keyring_found_list_free(found);
- VERB2 log("keyring has %sconfiguration for event '%s'", (item_id !=
0) ? "" : "no ", event_name);
- return item_id;
-}
-
-static void abrt_keyring_load_settings(const char *event_name, event_config_t *ec)
-{
- //don't bother when we already know that keyring is not available
- if (!g_keyring_available)
- return;
-
- guint item_id = find_keyring_item_id_for_event(event_name);
- if (!item_id)
- return;
- GnomeKeyringAttributeList *attrs = NULL;
- GnomeKeyringResult result = gnome_keyring_item_get_attributes_sync(
- keyring_name,
- item_id,
- &attrs);
- if (result == GNOME_KEYRING_RESULT_OK && attrs)
- {
- VERB3 log("num attrs %i", attrs->len);
- guint index;
- for (index = 0; index < attrs->len; index++)
- {
- char *name = g_array_index(attrs, GnomeKeyringAttribute, index).name;
- VERB2 log("keyring has name '%s'", name);
- event_option_t *option = get_event_option_from_list(name, ec->options);
- if (option)
- {
- free(option->eo_value);
- option->eo_value = xstrdup(g_array_index(attrs, GnomeKeyringAttribute,
index).value.string);
- VERB2 log("added or replaced in event config:'%s=%s'",
name, option->eo_value);
- }
- }
- }
- if (attrs)
- gnome_keyring_attribute_list_free(attrs);
-}
-
-static void init_keyring()
-{
- /* Called again? */
- if (got_keyring || !g_keyring_available)
- return;
-
- if (!gnome_keyring_is_available())
- {
- g_keyring_available = 0;
- VERB1 error_msg("Cannot connect to Gnome keyring daemon");
- return;
- }
-
- GnomeKeyringResult result =
gnome_keyring_get_default_keyring_sync(&keyring_name);
- if (result != GNOME_KEYRING_RESULT_OK)
- {
- error_msg("Can't get default keyring (result:%d)", result);
- return;
- }
-
- got_keyring = 1;
- /*
- * Note: The default keyring name can be NULL. It is a valid name.
- */
- VERB2 log("keyring:'%s'", keyring_name);
-}
-
-static void load_event_config(gpointer key, gpointer value, gpointer user_data)
-{
- char* event_name = (char*)key;
- event_config_t *ec = (event_config_t *)value;
- VERB2 log("loading event '%s' configuration from keyring",
event_name);
- abrt_keyring_load_settings(event_name, ec);
-}
-
-/*
- * Tries to load settings for all events in g_event_config_list
-*/
-void load_event_config_data_from_keyring(void)
-{
- init_keyring();
- if (!got_keyring)
- return;
- g_hash_table_foreach(g_event_config_list, &load_event_config, NULL);
-}
diff --git a/src/gtk-helpers/event_config_dialog.c
b/src/gtk-helpers/event_config_dialog.c
index 5a564fe..190dad1 100644
--- a/src/gtk-helpers/event_config_dialog.c
+++ b/src/gtk-helpers/event_config_dialog.c
@@ -17,7 +17,6 @@
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <gtk/gtk.h>
-#include <gnome-keyring.h>
#include "internal_libreport_gtk.h"
static GtkWindow *g_event_list_window;
@@ -298,63 +297,6 @@ static void dehydrate_config_dialog()
g_list_foreach(option_widget_list, &save_value_from_widget, NULL);
}
-static void save_settings_to_keyring(const char *event_name)
-{
- //don't bother when we already know that keyring is not available
- if (!g_keyring_available)
- return;
- char *keyring_name = NULL;
- GnomeKeyringResult result =
gnome_keyring_get_default_keyring_sync(&keyring_name);
- if (result != GNOME_KEYRING_RESULT_OK)
- {
- error_msg("Can't get default keyring (result:%d)", result);
- return;
- }
-
- const char *store_passwords_s = get_user_setting("store_passwords");
- bool store_passwords = !(store_passwords_s && !strcmp(store_passwords_s,
"no"));
-
- GnomeKeyringAttributeList *attrs = gnome_keyring_attribute_list_new();
- event_config_t *ec = get_event_config(event_name);
- /* add string id which we use to search for items */
- gnome_keyring_attribute_list_append_string(attrs, "libreportEventConfig",
event_name);
- GList *l;
- for (l = g_list_first(ec->options); l != NULL; l = g_list_next(l))
- {
- event_option_t *op = (event_option_t *)l->data;
- gnome_keyring_attribute_list_append_string(attrs, op->eo_name,
- (!store_passwords && op->eo_type == OPTION_TYPE_PASSWORD) ?
"" : op->eo_value);
- }
-
- guint32 item_id = find_keyring_item_id_for_event(event_name);
- if (item_id)
- {
- /* found existing item, so just update the values */
- result = gnome_keyring_item_set_attributes_sync(keyring_name, item_id, attrs);
- VERB2 log("updated item with id: %i", item_id);
- }
- else
- {
- /* did't find existing item, so create a new one */
- result = gnome_keyring_item_create_sync(keyring_name,
- GNOME_KEYRING_ITEM_GENERIC_SECRET, /* type */
- event_name, /* display name */
- attrs, /* attributes */
- "", /* secret - no special handling for
password it's stored in attrs */
- 1, /* update if exist */
- &item_id);
- VERB2 log("created new item with id: %i", item_id);
- }
- gnome_keyring_attribute_list_free(attrs);
-
- if (result != GNOME_KEYRING_RESULT_OK)
- {
- error_msg("Error saving event '%s' configuration to keyring",
event_name);
- return;
- }
- VERB2 log("saved event '%s' configuration to keyring",
event_name);
-}
-
int show_event_config_dialog(const char *event_name, GtkWindow *parent)
{
if (option_widget_list != NULL)
@@ -425,14 +367,14 @@ int show_event_config_dialog(const char *event_name, GtkWindow
*parent)
if (g_list_length(gtk_container_get_children(GTK_CONTAINER(g_adv_option_table))) >
0)
gtk_box_pack_start(GTK_BOX(content), adv_expander, false, false, 0);
- /* add warning if keyring is not available showing the nagging dialog
+ /* add warning if secrets service is not available showing the nagging dialog
* is considered "too heavy UI" be designers
*/
- if (!g_keyring_available)
+ if (!is_event_config_user_storage_available())
{
GtkWidget *keyring_warn_lbl =
gtk_label_new(
- _("Gnome Keyring is not available, your settings won't be
saved!"));
+ _("Secret Service is not available, your settings won't be
saved!"));
static const GdkColor red = { .red = 0xffff };
gtk_widget_modify_fg(keyring_warn_lbl, GTK_STATE_NORMAL, &red);
gtk_box_pack_start(GTK_BOX(content), keyring_warn_lbl, false, false, 0);
@@ -444,7 +386,10 @@ int show_event_config_dialog(const char *event_name, GtkWindow
*parent)
if (result == GTK_RESPONSE_APPLY)
{
dehydrate_config_dialog();
- save_settings_to_keyring(event_name);
+ const char *const store_passwords_s =
get_user_setting("store_passwords");
+ save_event_config_data_to_user_storage(event_name,
+ get_event_config(event_name),
+ !(store_passwords_s &&
!strcmp(store_passwords_s, "no")));
}
//else if (result == GTK_RESPONSE_CANCEL)
// log("log");
@@ -460,7 +405,7 @@ void show_events_list_dialog(GtkWindow *parent)
if (g_event_config_list == NULL)
{
load_event_config_data();
- load_event_config_data_from_keyring();
+ load_event_config_data_from_user_storage(g_event_config_list);
}
GtkWidget *event_list_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
diff --git a/src/gtk-helpers/internal_libreport_gtk.h
b/src/gtk-helpers/internal_libreport_gtk.h
index 092bfad..dd9ff87 100644
--- a/src/gtk-helpers/internal_libreport_gtk.h
+++ b/src/gtk-helpers/internal_libreport_gtk.h
@@ -27,19 +27,22 @@
extern "C" {
#endif
-extern bool g_keyring_available;
-
#define make_label_autowrap_on_resize libreport_make_label_autowrap_on_resize
void make_label_autowrap_on_resize(GtkLabel *label);
#define show_events_list_dialog libreport_show_events_list_dialog
void show_events_list_dialog(GtkWindow *parent);
-#define load_event_config_data_from_keyring
libreport_load_event_config_data_from_keyring
-void load_event_config_data_from_keyring(void);
+#define is_event_config_user_storage_available
libreport_is_event_config_user_storage_available
+bool is_event_config_user_storage_available();
+
+#define load_event_config_data_from_user_storage
libreport_load_event_config_data_from_user_storage
+void load_event_config_data_from_user_storage(GHashTable *event_config_list);
-#define find_keyring_item_id_for_event libreport_find_keyring_item_id_for_event
-guint32 find_keyring_item_id_for_event(const char *event_name);
+#define save_event_config_data_to_user_storage
libreport_save_event_config_data_to_user_storage
+void save_event_config_data_to_user_storage(const char *event_name,
+ const event_config_t *event_config,
+ bool store_password);
#define show_event_config_dialog libreport_show_event_config_dialog
int show_event_config_dialog(const char *event_name, GtkWindow *parent);
diff --git a/src/gtk-helpers/secrets.c b/src/gtk-helpers/secrets.c
new file mode 100644
index 0000000..ee6a348
--- /dev/null
+++ b/src/gtk-helpers/secrets.c
@@ -0,0 +1,1313 @@
+/*
+ Copyright (C) 2012 ABRT Team
+ Copyright (C) 2012 RedHat inc.
+
+ 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 program 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.
+
+ 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 02110-1301 USA.
+*/
+#include "internal_libreport.h"
+#include "internal_libreport_gtk.h"
+
+#include <gio/gio.h>
+
+#define SECRETS_SERVICE_BUS "org.freedesktop.secrets"
+#define SECRETS_NAME_SPACE(interface) "org.freedesktop.Secret."interface
+
+/* label and alias have to be same because of ksecrets */
+#define SECRETS_COLLECTION_LABEL SECRETS_COLLECTION_ALIAS
+#define SECRETS_COLLECTION_ALIAS "default"
+
+#define SECRETS_SEARCH_ATTRIBUTE "libreportEventConfig"
+
+/* 5s timeout*/
+#define SECRETS_CALL_DEFAULT_TIMEOUT 5000
+
+/* Well known errors for which we have workarounds */
+#define NOT_IMPLEMENTED_READ_ALIAS_ERROR_MESSAGE
"GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: No such method
'ReadAlias' in interface 'org.freedesktop.Secret.Service' at object path
'/org/freedesktop/secrets' (signature 's')"
+#define INVALID_PROPERTIES_ARGUMENTS
"GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: Invalid properties
argument"
+
+/*
+ Data structure:
+ ===============
+ Secret Service groups data into Collections. Each collection
+ has a label and may have alternative names (aliases).
+ There is a default collection, named "default".
+ Libreport uses it to store our data.
+
+ Collection is a set of Items identified by labels (label is a string).
+ Libreport uses event names as labels for items which hold event config data.
+
+ Item is a set of (name,value) pairs called attributes.
+ Libreport uses attributes to store config data.
+
+ For example, "report_Bugzilla" item will have some attributes,
+ like ("Bugzilla_BugzillaURL", "https://bugzilla.redhat.com").
+
+ DBus API:
+ =========
+ Secret Service requires user to first open a Session using OpenSession()
+ call over session dbus on "/org/freedesktop/secrets" object,
+ "org.freedesktop.Secret.Service" interface.
+ This creates a new object on "org.freedesktop.secrets" bus.
+ Apparently, this object is not used for anything except final Close() call.
+
+ We obtain a dbus object name for the default collection by calling
+ ReadAlias() over session dbus on "/org/freedesktop/secrets" object,
+ "org.freedesktop.Secret.Service" interface. This object is accessible
+ on session dbus. It has "org.freedesktop.Secret.Collection" interface.
+
+ Before we can search for items on the collection, we need to unlock collection
+ (this usually causes a "gimme pwd NOW!" prompt to appear if we are in X).
+ This is done by Unlock() call. It returns a path to prompt dbus object
+ if unlocking is truly needed. In which case we need to call Prompt() on it,
+ and wait for dbus signal "Completed" from the prompt object.
+
+ We can SearchItems() and CreateItem() on the default collection.
+ SearchItems() returns the list of dbus object names of found items.
+ They are accessible on session dbus and have "org.freedesktop.Secret.Item"
+ interface. (We use the first found item, if there are more than one.)
+
+ We retrieve attributes from a found item (they are stored as dbus
+ object's properties).
+
+ CreateItem() creates an item in a collection and returns a tuple consisting
+ from a prompt path and a new item path. We pass item's properties (label and
+ attributes) as a dictionary. Dictionary keys are strings and values are
+ variants. Label is stored under key "org.freedesktop.Secret.Item.Label" and
+ attributes are stored under key "org.freedesktop.Secret.Item.Attributes".
+ This function may require to perform a prompt. If the prompt path is the
+ special value "/" no prompt is required.
+*/
+
+enum secrets_service_state
+{
+ /* intial state */
+ SBS_INITIAL = 0,
+ /* after opening a secrets service session (different than D-Bus session) */
+ SBS_CONNECTED,
+ /* secrets service is not available do not bother user anymore */
+ SBS_UNAVAILABLE,
+};
+
+struct secrets_object
+{
+ GDBusProxy *proxy;
+ /*
http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfac... */
+ GDBusProxy *property_proxy;
+ const gchar *interface_name;
+};
+
+/* DBus session bus connection */
+static GDBusConnection *g_connection;
+
+/* State of the service */
+static enum secrets_service_state g_state;
+
+/*
http://standards.freedesktop.org/secret-service/re01.html */
+/* dbus object "path:/org/freedesktop/secrets,
iface:xorg.freedesktop.Secret.Service" */
+static struct secrets_object *g_service_object;
+
+/*
http://standards.freedesktop.org/secret-service/ch06.html */
+/* proxy for dbus object "path:/org/freedesktop/secrets/session/ssss,
iface:xorg.freedesktop.Secret.Session" */
+/* where ssss is an auto-generated session specific identifier. */
+static GDBusProxy *g_session_proxy;
+
+/******************************************************************************/
+/* helpers */
+/******************************************************************************/
+static GDBusProxy *get_dbus_proxy(const gchar *path, const gchar *interface)
+{
+ GError *error = NULL;
+ GDBusProxy *const proxy = g_dbus_proxy_new_sync(g_connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ /* GDBusInterfaceInfo */ NULL,
+ SECRETS_SERVICE_BUS,
+ path,
+ interface,
+ /* GCancellable */ NULL,
+ &error);
+
+ if (error)
+ {
+ error_msg(_("Can't connect over DBus to name '%s' path
'%s'" "interface '%s': %s"),
+ SECRETS_SERVICE_BUS, path, interface, error->message);
+ g_error_free(error);
+ /* proxy is NULL in this case */
+ }
+ return proxy;
+}
+
+static GVariant *dbus_call_sync(GDBusProxy *proxy, const gchar *method, GVariant *args)
+{
+ GError *error = NULL;
+ GVariant *const resp = g_dbus_proxy_call_sync(proxy,
+ method,
+ args,
+ G_DBUS_PROXY_FLAGS_NONE,
+ SECRETS_CALL_DEFAULT_TIMEOUT,
+ /* GCancellable */ NULL,
+ &error);
+ if (error)
+ {
+ error_msg(_("Can't call method '%s' over DBus on path
'%s' interface '%s': %s"),
+ method,
+ g_dbus_proxy_get_object_path(proxy),
+ g_dbus_proxy_get_interface_name(proxy),
+ error->message);
+ g_error_free(error);
+ /* resp is NULL in this case */
+ }
+ return resp;
+}
+
+/******************************************************************************/
+/* struct secrets_object */
+/******************************************************************************/
+
+static struct secrets_object *secrets_object_new_from_proxy(GDBusProxy *proxy)
+{
+ struct secrets_object *obj = xzalloc(sizeof(*obj));
+ obj->proxy = proxy;
+ obj->interface_name = g_dbus_proxy_get_interface_name(proxy);
+
+ return obj;
+}
+
+static struct secrets_object *secrets_object_new(const gchar *path,
+ const gchar *interface)
+{
+ GDBusProxy *const proxy = get_dbus_proxy(path, interface);
+
+ if (!proxy)
+ return NULL;
+
+ return secrets_object_new_from_proxy(proxy);
+}
+
+static bool secrets_object_change_path(struct secrets_object *obj,
+ const gchar *path)
+{
+ /* Do not set a new path if is the same as the current path */
+ if (strcmp(path, g_dbus_proxy_get_object_path(obj->proxy)) == 0)
+ return true;
+
+ GDBusProxy *const proxy = get_dbus_proxy(path, obj->interface_name);
+
+ if (!proxy)
+ return false;
+
+ /* release the old DBus property proxy */
+ if (obj->property_proxy)
+ {
+ g_object_unref(obj->property_proxy);
+ obj->property_proxy = NULL;
+ }
+
+ /* release the old proxy */
+ if (obj->proxy)
+ g_object_unref(obj->proxy);
+
+ obj->proxy = proxy;
+ obj->interface_name = g_dbus_proxy_get_interface_name(proxy);
+ return true;
+}
+
+static void secrets_object_delete(struct secrets_object *obj)
+{
+ if (!obj)
+ return;
+
+ if (obj->proxy)
+ g_object_unref(obj->proxy);
+
+ if (obj->property_proxy)
+ g_object_unref(obj->property_proxy);
+
+ free(obj);
+}
+
+static GDBusProxy *secrets_object_get_properties_proxy(struct secrets_object *obj)
+{
+ if (!obj->property_proxy)
+ obj->property_proxy =
get_dbus_proxy(g_dbus_proxy_get_object_path(obj->proxy),
+
"org.freedesktop.DBus.Properties");
+
+ return obj->property_proxy;
+}
+
+
+static bool secrets_object_set_dbus_property(struct secrets_object *obj,
+ const char *property,
+ GVariant *value)
+{
+ GDBusProxy *properties = secrets_object_get_properties_proxy(obj);
+
+ if (!properties)
+ return false;
+
+ /*
http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfac... */
+ /* org.freedesktop.DBus.Properties.Set (IN String interface_name, IN String
property_name, IN Variant value); */
+ GVariant *const resp = dbus_call_sync(properties,
+ "Set",
+ g_variant_new("(ssv)",
+ obj->interface_name,
+ property,
+ value)
+ );
+
+ if (resp)
+ g_variant_unref(resp);
+
+ return resp;
+}
+
+/*******************************************************************************/
+/* struct secrets_service */
+/*******************************************************************************/
+
+static void secrets_service_set_unavailable(void);
+
+/*
+ * Initialize all global variables
+ */
+static enum secrets_service_state secrets_service_connect(void)
+{
+ GError *error = NULL;
+ g_connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
+
+ if (!g_connection)
+ {
+ log("Failed to open connection to D-Bus session bus: %s",
error->message);
+ g_error_free(error);
+ return SBS_UNAVAILABLE;
+ }
+
+ g_service_object = secrets_object_new("/org/freedesktop/secrets",
SECRETS_NAME_SPACE("Service"));
+
+ if (!g_service_object)
+ return SBS_UNAVAILABLE;
+
+ /* OpenSession (IN String algorithm, IN Variant input, OUT Variant output, OUT
ObjectPath result); */
+ /* Open a unique session for the caller application. */
+ /* algorithm : The algorithm the caller wishes to use. */
+ /* input : Input arguments for the algorithm. */
+ /* output : Output of the session algorithm negotiation. */
+ /* result : The object path of the session, if session was created. */
+ GVariant *const resp = dbus_call_sync(g_service_object->proxy,
+ "OpenSession",
+ g_variant_new("(sv)",
"plain", g_variant_new_string("")));
+
+ if (!resp)
+ return SBS_UNAVAILABLE;
+
+ GVariant *const var = g_variant_get_child_value(resp, 1);
+
+ g_variant_unref(resp);
+
+ const gchar *const session_path = g_variant_get_string(var, NULL);
+ g_session_proxy = get_dbus_proxy(session_path,
SECRETS_NAME_SPACE("Session"));
+
+ g_variant_unref(var);
+
+ if (!g_session_proxy)
+ return SBS_UNAVAILABLE;
+
+ return g_state;
+}
+
+static void secrets_service_close_connection(void)
+{
+ if (g_state != SBS_CONNECTED)
+ return;
+
+ /* Close (void); */
+ /* Close this session. */
+ GVariant *const resp = dbus_call_sync(g_session_proxy, "Close",
g_variant_new("()"));
+
+ if (resp)
+ g_variant_unref(resp);
+ /*else : don't take care about errors */
+
+ g_object_unref(g_session_proxy);
+ g_session_proxy = NULL;
+
+ secrets_object_delete(g_service_object);
+ g_service_object = NULL;
+
+ g_object_unref(g_connection);
+ g_connection = NULL;
+
+ g_state = SBS_INITIAL;
+}
+
+static void secrets_service_set_unavailable(void)
+{
+ secrets_service_close_connection();
+
+ g_state = SBS_UNAVAILABLE;
+}
+
+/*******************************************************************************/
+/* org.freedesktop.Secret.Prompt */
+/*
http://standards.freedesktop.org/secret-service/re05.html */
+/*******************************************************************************/
+
+struct prompt_source
+{
+ GSource source;
+ GDBusProxy *prompt_proxy;
+ GMainLoop *loop;
+};
+
+struct prompt_call_back_args
+{
+ gboolean dismissed;
+ GVariant *result;
+ GMainLoop *loop;
+};
+
+static void prompt_quit_loop(GMainLoop *loop)
+{
+ if (g_main_loop_is_running(loop))
+ g_main_loop_quit(loop);
+}
+
+static gboolean prompt_g_idle_prepare(GSource *source_data, gint *timeout)
+{
+ return TRUE;
+}
+
+static gboolean prompt_g_idle_check(GSource *source)
+{
+ return TRUE;
+}
+
+/* one shot dispatch function which runs a prompt in an event loop */
+static gboolean prompt_g_idle_dispatch(GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ struct prompt_source *const prompt_source = (struct prompt_source *)source;
+ GDBusProxy *const prompt_proxy = prompt_source->prompt_proxy;
+
+ /* Prompt (IN String window-id); */
+ /* Perform the prompt. A window should appear. */
+ /* window-id : Platform specific window handle to use for showing the prompt. */
+ GVariant *const resp = dbus_call_sync(prompt_proxy,
+ "Prompt",
+ g_variant_new("(s)", ""));
+
+ if (!resp)
+ /* We have to kill the loop because a dbus call failed and the signal */
+ /* which stops the loop won't be received */
+ prompt_quit_loop(prompt_source->loop);
+ else
+ g_variant_unref(resp);
+
+ /* always return FALSE, it means that this source is to be removed from idle loop */
+ return FALSE;
+}
+
+/* Prompt Completed signal callback */
+static void prompt_call_back(GDBusConnection *connection, const gchar *sender_name,
+ const gchar *object_path, const gchar *interface_name,
+ const gchar *signal_name, GVariant *parameters,
+ gpointer user_data)
+{
+ struct prompt_call_back_args *const args = (struct prompt_call_back_args
*)user_data;
+ GVariant *result = NULL;
+
+ /* Completed (IN Boolean dismissed, IN Variant result); */
+ /* The prompt and operation completed. */
+ /* dismissed : Whether the prompt and operation were dismissed or not. */
+ /* result : The possibly empty, operation specific, result. */
+ g_variant_get(parameters, "(bv)", &args->dismissed, &result);
+
+ if (!args->dismissed)
+ args->result = result;
+ else
+ /* if the prompt or operation were dismissed we don't care about result */
+ g_variant_unref(result);
+
+ prompt_quit_loop(args->loop);
+}
+
+/*
+ * If a prompt path is '/' then it isn't required to perform prompt.
+ * Even more, prompt can't be performed because the path is not valid.
+ */
+static bool is_prompt_required(const char *prompt_path)
+{
+ return prompt_path && !(prompt_path[0] == '/' &&
prompt_path[1] == '\0');
+}
+
+/*
+ * Perform a prompt
+ *
+ * @param prompt_path An object path pointing to a prompt
+ * @param result A prompt result (can be NULL)
+ * @param dismissed A dismissed flag (can't be NULL)
+ * @return returns TRUE if no errors occured; otherwise false
+ */
+static GVariant *secrets_prompt(const char *prompt_path,
+ bool *dismissed)
+{
+ static GSourceFuncs idle_funcs = {
+ .prepare = prompt_g_idle_prepare,
+ .check = prompt_g_idle_check,
+ .dispatch = prompt_g_idle_dispatch,
+ .finalize = NULL,
+ .closure_callback = NULL,
+ .closure_marshal = NULL,
+ };
+
+ GDBusProxy *const prompt_proxy = get_dbus_proxy(prompt_path,
SECRETS_NAME_SPACE("Prompt"));
+
+ *dismissed = false;
+ if (!prompt_proxy)
+ return NULL;
+
+ /* We have to use the thread default main context because a dbus signal callback */
+ /* will be invoked in the thread default main loop */
+ GMainContext *context = g_main_context_get_thread_default();
+ GMainLoop *loop = g_main_loop_new(context, FALSE);
+
+ struct prompt_call_back_args args = {
+ .result=NULL,
+ .dismissed=FALSE,
+ .loop=loop
+ };
+
+ /* g_dbus_connection_signal_subscribe() doesn't report any error */
+ const guint signal_ret =
+ g_dbus_connection_signal_subscribe(g_connection, SECRETS_SERVICE_BUS,
SECRETS_NAME_SPACE("Prompt"),
+ "Completed", prompt_path, NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
+ prompt_call_back, &args, NULL);
+
+ /* prepare the prompt source */
+ /* The promp source is implemented as idle source because we have to perform */
+ /* a prompt in some event loop. We need an event loop because a promp operation is
*/
+ /* perfomed assynchronously and we have to wait on the Completed signal. */
+ /* Idle source simply performs a prompt after the loop is stared. */
+ struct prompt_source *const prompt_source =
+ (struct prompt_source*)g_source_new(&idle_funcs, sizeof(*prompt_source));
+
+ prompt_source->prompt_proxy = prompt_proxy;
+
+ g_source_attach((GSource*)prompt_source, context);
+
+ /* the loop is exited when the Completed signal is received */
+ /* the loop may sucks in infinite loop if the signal is never received */
+ /* TODO : if timeout is required use g_timeout_source_new() and g_source_attach() */
+ g_main_loop_run(loop);
+
+ /* destroy prompt source */
+ g_object_unref(prompt_proxy);
+ g_source_destroy((GSource*)prompt_source);
+
+ g_dbus_connection_signal_unsubscribe(g_connection, signal_ret);
+ g_main_loop_unref(loop);
+
+ *dismissed = args.dismissed;
+ return args.result;
+}
+
+/*******************************************************************************/
+/* org.freedesktop.Secret.Service */
+/*
http://standards.freedesktop.org/secret-service/re01.html */
+/*******************************************************************************/
+
+static GVariant *secrets_service_unlock(const gchar *const *objects,
+ bool *dismissed)
+{
+ /* Unlock (IN Array<ObjectPath> objects, OUT Array<ObjectPath> unlocked,
OUT ObjectPath prompt); */
+ /* Unlock the specified objects. */
+ /* objects : Objects to unlock. */
+ /* unlocked : Objects that were unlocked without a prompt. */
+ /* prompt : A prompt object which can be used to unlock the remaining objects,
or the special value '/' when no prompt is necessary. */
+ GVariant *const var = dbus_call_sync(g_service_object->proxy,
+ "Unlock",
+ g_variant_new("(^ao)", objects));
+ if (!var)
+ return false;
+
+ gchar *prompt = NULL;
+ GVariant *result = NULL;
+ g_variant_get(var, "(@aoo)", &result, &prompt);
+
+ *dismissed = false;
+
+ if (is_prompt_required(prompt))
+ {
+ g_variant_unref(result);
+ result = secrets_prompt(prompt, dismissed);
+ }
+
+ g_variant_unref(var);
+
+ return result;
+}
+
+static bool secrets_service_unlock_object(struct secrets_object *obj,
+ bool *dismissed)
+{
+ /* objects to unlock */
+ const gchar *const locked[] = {
+ g_dbus_proxy_get_object_path(obj->proxy),
+ NULL
+ };
+
+ GVariant *result = secrets_service_unlock(locked, dismissed);
+
+ if (result)
+ {
+ gsize length = 0;
+ const gchar *const *unlocked = g_variant_get_objv(result, &length);
+
+ /* ksecrets doesn't set dismissed correctly */
+ if (length)
+ {
+ const bool succ = secrets_object_change_path(obj, *unlocked);
+ g_free((gpointer)unlocked);
+ g_variant_unref(result);
+
+ return succ;
+ }
+ else
+ {
+ *dismissed = true;
+ g_variant_unref(result);
+ }
+ }
+
+ /* the function is successful if result is not NULL or */
+ /* if user dismissed any prompt */
+ return result || *dismissed;
+}
+
+/*
+ * KSecretsService doesn't implement ReadAlias
+ *
+ * The function iterates over all collections and compares their Labels
+ * with given alias.
+ *
+ * Collection is null on error or if it doesn't exist
+ */
+static bool secrets_service_find_collection(const char *alias, struct secrets_object
**collection)
+{
+ /* do not unref this proxy, the proxy is owned by secrets object */
+ GDBusProxy *const properties =
secrets_object_get_properties_proxy(g_service_object);
+
+ if (!properties)
+ return false;
+
+ /* cannot use g_dbus_proxy_get_cached_property() because of */
+ /* errors in case when secrets service was restarted during session */
+ /*
http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfac... */
+ /* org.freedesktop.DBus.Properties.Get (IN String interface_name, IN String
property_name, OUT Variant value); */
+ GVariant *const prop = dbus_call_sync(properties,
+ "Get",
+ g_variant_new("(ss)",
+
SECRETS_NAME_SPACE("Service"),
+ "Collections")
+ );
+ if (!prop)
+ return false;
+
+ *collection = NULL;
+
+ /* Returned value */
+ GVariant *const value = g_variant_get_child_value(prop, 0);
+ /* Contentet of returned value */
+ GVariant *const list = g_variant_get_child_value(value, 0);
+
+ GVariantIter *iter = NULL;
+ g_variant_get(g_variant_get_child_value(value, 0), "ao", &iter);
+
+ bool found = false;
+ gchar *path = NULL;
+ while (g_variant_iter_loop(iter, "o", &path))
+ {
+ GDBusProxy *const collection_proxy = get_dbus_proxy(path,
+
SECRETS_NAME_SPACE("Collection"));
+ /* an error occurred */
+ if (!collection_proxy)
+ break;
+
+ GVariant *const lbl_var = g_dbus_proxy_get_cached_property(collection_proxy,
+ "Label");
+
+ const gchar *const label = g_variant_get_string(lbl_var, NULL);
+ found = label && (strcmp(label, alias) == 0);
+
+ g_variant_unref(lbl_var);
+
+ if (found)
+ {
+ *collection = secrets_object_new_from_proxy(collection_proxy);
+ /* the collection_proxy variable will be unrefed byt the collection object
*/
+ break;
+ }
+
+ g_object_unref(collection_proxy);
+ }
+
+ g_variant_iter_free(iter);
+ g_variant_unref(list);
+ g_variant_unref(value);
+ g_variant_unref(prop);
+
+ /* If coolection is not found no error could occure */
+ /* If coolection is found and variable is null then error occured */
+ return !found || *collection;
+}
+
+/*
+ * Collection is null on error or if it doesn't exist
+ */
+static bool secrets_service_read_alias(const char *alias, struct secrets_object
**collection)
+{
+ GError *error = NULL;
+ /* use dbus_call_sync if KSecrets doesn't require special handling */
+ /* ReadAlias (IN String name, OUT ObjectPath collection); */
+ /* Get the collection with the given alias. */
+ /* name : An alias, such as 'default'. */
+ /* collection : The collection or the the path '/' if no such collection
exists. */
+ GVariant *const resp =
+ g_dbus_proxy_call_sync(g_service_object->proxy, "ReadAlias",
g_variant_new("(s)", alias),
+ G_DBUS_PROXY_FLAGS_NONE, SECRETS_CALL_DEFAULT_TIMEOUT,
+ NULL, &error);
+
+ if (!error)
+ {
+ GVariant *const obj_path = g_variant_get_child_value(resp, 0);
+ const gchar *const coll_path = g_variant_get_string(obj_path, NULL);
+
+ /* found if the path is not "/" */
+ const bool found = coll_path[0] != '/' || coll_path[1] != '\0';
+
+ if (found)
+ *collection = secrets_object_new(coll_path,
SECRETS_NAME_SPACE("Collection"));
+
+ g_variant_unref(obj_path);
+ g_variant_unref(resp);
+
+ /* return TRUE if collection was not found or if collection object is not NULL
*/
+ return !found || *collection;
+ }
+ else if (strcmp(error->message, NOT_IMPLEMENTED_READ_ALIAS_ERROR_MESSAGE) == 0)
+
+ {
+ /* this code branch can be safely removed if KSecrets provides ReadAlias
method*/
+ VERB1 log("D-Bus Secrets Service ReadAlias method failed,"
+ "going to search on the client side : %s",
error->message);
+ g_error_free(error);
+
+ /* ksecrets doesn't implement ReadAlias, therefore search by label is
performed */
+ return secrets_service_find_collection(alias, collection);
+ }
+
+ error_msg(_("D-Bus Secrets Service ReadAlias('%s') method failed:
%s"), alias, error->message);
+ g_error_free(error);
+ return false;
+}
+
+/*
+ * Creates a new secrets service collection.
+ *
+ * @param label a collection label
+ * @param alias a collection alias, empty string means default (see Secrets Service API)
+ * @param dismissed a dismissed flag, true value means that user dismissed creation
+ * the param never holds true if error occurred
+ * @return on error returns false, otherwise returns true
+ */
+static struct secrets_object *secrets_service_create_collection(const gchar *label,
+ const gchar *alias,
+ bool *dismissed)
+{
+ GVariantBuilder *const prop_builder =
g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);
+
+ g_variant_builder_add(prop_builder, "{sv}",
"org.freedesktop.Secret.Collection.Label",
+ g_variant_new_string(label) );
+
+ /* CreateCollection (IN Dict<String,Variant> properties, IN String alias, */
+ /* OUT ObjectPath collection, OUT ObjectPath prompt); */
+ /* Create a new collection with the specified properties. */
+ /* properties : Properties for the new collection. This allows setting the */
+ /* new collection's properties upon its creation. All */
+ /* READWRITE properties are useable. Specify the property */
+ /* names in full interface.Property form. */
+ /* alias : If creating this connection for a well known alias then */
+ /* a string like default. If an collection with this */
+ /* well-known alias already exists, then that collection */
+ /* will be returned instead of creating a new collection. */
+ /* Any readwrite properties provided to this function will */
+ /* be set on the collection. */
+ /* Set this to an empty string if the new collection should */
+ /* not be associated with a well known alias. */
+ /* collection : The new collection object, or '/' if prompting is */
+ /* necessary. */
+ /* prompt : A prompt object if prompting is necessary, or '/' if no */
+ /* prompt was needed. */
+ GVariant *const resp = dbus_call_sync(g_service_object->proxy,
+ "CreateCollection",
+ g_variant_new("(@a{sv}s)",
+
g_variant_builder_end(prop_builder),
+ alias)
+ );
+
+ if (!resp)
+ return NULL;
+
+ struct secrets_object *collection = NULL;
+
+ gchar *prompt = NULL;
+ GVariant *collection_var = NULL;
+ g_variant_get(resp, "(@oo)", &collection_var, &prompt);
+
+ if (is_prompt_required(prompt))
+ {
+ g_variant_unref(collection_var);
+ collection_var = secrets_prompt(prompt, dismissed);
+ }
+
+ if (collection_var)
+ {
+ gsize length = 0;
+ const gchar *const coll_path = g_variant_get_string(collection_var,
&length);
+
+ /* ksecrets doens't properly set dismissed */
+ /* if dismissed is false and result is empty */
+ /* then ksecrets didn't properly set dismissed */
+ if (length)
+ collection = secrets_object_new(coll_path,
SECRETS_NAME_SPACE("Collection"));
+ else
+ *dismissed = true;
+
+ g_variant_unref(collection_var);
+ }
+
+ g_variant_unref(resp);
+
+ return collection;
+}
+
+/*******************************************************************************/
+/* org.freedesktop.Secret.Collection */
+/*
http://standards.freedesktop.org/secret-service/re02.html */
+/*******************************************************************************/
+
+static GVariant *create_item_call_args(const char *event_name,
+ GVariant *attributes,
+ GDBusProxy *session,
+ bool full_property_name)
+{
+ /* gnome-keyring accepts properties with namespace */
+ /* {
+ * "org.freedesktop.Secret.Item.Label": 'event name',
+ * "org.freedesktop.Secret.Item.Attributes": {
+ * "BugzillaURL":
"Value1",
+ * "BugzillaPassword":
"Value2"
+ * }
+ * }
+ */
+
+ /* ksecrets accepts properties without namespace */
+ /* {
+ * "Label": 'event name',
+ * "Attributes": {
+ * "BugzillaURL": "Value1",
+ * "BugzillaPassword": "Value2"
+ * }
+ * }
+ */
+ const char *const lbl_name = full_property_name ?
SECRETS_NAME_SPACE("Item.Label")
+ : "Label";
+ const char *const att_name = full_property_name ?
SECRETS_NAME_SPACE("Item.Attributes")
+ : "Attributes";
+
+ GVariantBuilder *const prop_builder =
g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);
+
+ g_variant_builder_add(prop_builder, "{sv}", lbl_name,
g_variant_new_string(event_name));
+ g_variant_builder_add(prop_builder, "{sv}", att_name, attributes);
+
+ GVariantBuilder *const param_builder =
g_variant_builder_new(G_VARIANT_TYPE("ay"));
+ GVariantBuilder *const value_builder =
g_variant_builder_new(G_VARIANT_TYPE("ay"));
+
+ GVariant *const secret = g_variant_new("(o@ay@ays)",
+ g_dbus_proxy_get_object_path(session),
+ g_variant_builder_end(param_builder),
+ g_variant_builder_end(value_builder),
+ "text/plain, charset=utf8");
+
+ return g_variant_new("(@a{sv}@(oayays)b)",
g_variant_builder_end(prop_builder), secret, FALSE);
+}
+
+static bool secrets_collection_create_text_item(struct secrets_object *collection,
+ const char *event_name,
+ GVariant *attributes,
+ bool *dismissed)
+{
+ bool succ = false;
+ GError *error = NULL;
+
+ /* not dismissed by default */
+ *dismissed = false;
+
+ /* ksecrets silently drops properties with full names */
+ /* gnome-keyring returns an error if a property key is not full property name */
+ /* the first iteration sends property names without namespace */
+ /* if NO error was returned service is ksecrets and everything is ok */
+ /* if error was returned service is gnome-keyring and the second iteration */
+ /* must be performed */
+ /* the second iteration sends property names with namespace */
+ static bool const options[] = {FALSE, TRUE};
+ for (size_t choice = 0; choice < sizeof(options)/sizeof(*options); ++choice)
+ {
+ if (error)
+ { /* this code is here because I want to know */
+ /* if an error occurred from outside the loop */
+ /* it cannot be on the end of the loop because */
+ /* I need unfreed error after the last iteration */
+ g_error_free(error);
+ error = NULL;
+ }
+
+ /* CreateItem (IN Dict<String,Variant> properties, IN Secret secret, */
+ /* IN Boolean replace, OUT ObjectPath item, */
+ /* OUT ObjectPath prompt); */
+ /* Create an item with the given attributes, secret and label. If */
+ /* replace is set, then it replaces an item already present with the */
+ /* same values for the attributes. */
+ /* properties : The properties for the new item. This allows setting */
+ /* the new item's properties upon its creation. All */
+ /* READWRITE properties are useable. Specify the */
+ /* property names in full interface.Property form. */
+ /* secret : The secret to store in the item, encoded with the */
+ /* included session. */
+ /* replace : Whether to replace an item with the same attributes */
+ /* or not. */
+ /* item : The item created, or the special value '/' if a prompt
*/
+ /* is necessary. */
+ /* prompt : A prompt object, or the special value '/' if no prompt
*/
+ /* is necessary. */
+ GVariant *const resp = g_dbus_proxy_call_sync(collection->proxy,
"CreateItem",
+ create_item_call_args(event_name,
g_variant_ref(attributes),
+ g_session_proxy,
options[choice]),
+ G_DBUS_PROXY_FLAGS_NONE,
SECRETS_CALL_DEFAULT_TIMEOUT,
+ NULL, &error);
+
+ if (error)
+ {
+ if (strcmp(INVALID_PROPERTIES_ARGUMENTS, error->message) == 0)
+ { /* it is OK - we know this error and we can safely continue */
+ VERB2 log("CreateItem failed, going to use other property names:
%s", error->message);
+ continue;
+ }
+
+ /* if the error wasn't about invalid properties we have an another
problem */
+ error_msg(_("Can't create an secret item for event '%s':
%s"), event_name, error->message);
+ g_error_free(error);
+ break;
+ }
+
+ gchar *prompt = NULL;
+ gchar *item = NULL;
+ g_variant_get(resp, "(oo)", &item, &prompt);
+
+ /* if prompt is no required the function is successfull */
+ /* therefore set return value to 'true' */
+ succ = true;
+
+ if (is_prompt_required(prompt))
+ succ = secrets_prompt(prompt, dismissed) != NULL;
+
+ g_variant_unref(resp);
+
+ /* a dbus call was successfull, we don't need to try next type of property
names */
+ /* breaking the loop, nothing else to do */
+ break;
+ }
+
+ g_variant_unref(attributes);
+
+ return succ;
+}
+
+/*
+ * Performs org.freedesktop.Secret.Collection.SearchItems and returns the first
+ * item from result. A found item can be possibly locked.
+ */
+static bool secrets_collection_search_one_item(const struct secrets_object *collection,
+ GVariant *search_attrs,
+ struct secrets_object **item)
+{
+ /* SearchItems (IN Dict<String,String> attributes, OUT Array<ObjectPath>
results); */
+ /* Search for items in this collection matching the lookup attributes. */
+ /* attributes : Attributes to match. */
+ /* results : Items that matched the attributes. */
+ GVariant *const resp = dbus_call_sync(collection->proxy,
+ "SearchItems",
+ g_variant_new("(@a{ss})",
search_attrs));
+
+ if (!resp)
+ return false;
+
+ /* even if no item is found the function finishes successfully */
+ bool found = false;
+ *item = NULL;
+
+ /* gnome-keyring returns (unlocked,locked) */
+ /* ksecretsservice returns (unlocked) */
+ /* all result childs has to be taken into account */
+ const gsize n_results = g_variant_n_children(resp);
+
+ for (gsize child = 0; !*item && child < n_results; ++child)
+ { /* ^^^^^^ break if the item object was created */
+
+ GVariant *const paths = g_variant_get_child_value(resp, 0);
+
+ /* NULL terminated list of path */
+ const gchar *const *item_path_vector = g_variant_get_objv(paths, NULL);
+
+ /* the first valid object path will be returned */
+ if (*item_path_vector)
+ {
+ found = true;
+ *item = secrets_object_new(*item_path_vector,
SECRETS_NAME_SPACE("Item"));
+ /* contniue on error - gnome-keyring unlocked result can */
+ /* hold an invalid resp */
+ }
+
+ g_free((gpointer)item_path_vector);
+ g_variant_unref(paths);
+ }
+
+ g_variant_unref(resp);
+
+ /* If item is not found no error could occure */
+ /* If item is found and variable is null then error occured */
+ return !found || *item;
+}
+
+/*******************************************************************************/
+/* utility functions */
+/*******************************************************************************/
+
+/*
+ * Gets an unlocked default collection if it exists
+ *
+ * Collection is null when error occured or when user dismissed unlocking
+ *
+ * @return on error false
+ */
+static bool get_default_collection(struct secrets_object **collection,
+ bool create,
+ bool *dismissed)
+{
+ bool succ = secrets_service_read_alias(SECRETS_COLLECTION_ALIAS, collection);
+
+ if (!succ)
+ return false;
+
+ /* ReadAlias was successful*/
+ if (*collection)
+ { /* the default collection was found */
+ succ = secrets_service_unlock_object(*collection, dismissed);
+
+ if (!succ || *dismissed)
+ {
+ secrets_object_delete(*collection);
+ *collection = NULL;
+ }
+ }
+ else if (create)
+ { /* the default collection wasn't found */
+ /* a method caller requires to create a new collection */
+ /* if the default collection doesn't exist */
+ VERB2 log("going to create a new default collection
'"SECRETS_COLLECTION_LABEL"'"
+ " with alias
'"SECRETS_COLLECTION_ALIAS"'");
+
+ *collection = secrets_service_create_collection(SECRETS_COLLECTION_LABEL,
+ SECRETS_COLLECTION_ALIAS,
+ dismissed);
+
+ /* create collection function succeded if a collection is not NULL */
+ /* or if the call was dismissed */
+ /* if dismissed is false and collection is null then an error occured */
+ succ = *collection || *dismissed;
+ }
+
+ return succ;
+}
+
+static void load_event_options_from_item(GList *options,
+ struct secrets_object *item)
+{
+ GVariant *const attributes =
+ g_dbus_proxy_get_cached_property(item->proxy, "Attributes");
+
+ GVariantIter *iter = NULL;
+ g_variant_get(attributes, "a{ss}", &iter);
+
+ gchar *name = NULL;
+ gchar *value = NULL;
+ while (g_variant_iter_loop(iter, "{ss}", &name, &value))
+ {
+ event_option_t *const option = get_event_option_from_list(name, options);
+ if (option)
+ {
+ free(option->eo_value);
+ option->eo_value = xstrdup(value);
+ VERB2 log("loaded event option : '%s => %s'", name,
option->eo_value);
+ }
+ }
+
+ g_variant_unref(attributes);
+}
+
+static GVariant *create_attributes_from_options(GList *options,
+ const char *event_name,
+ bool store_passwords)
+{
+ GVariantBuilder *const attr_builder =
g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);
+ for (GList *iter = g_list_first(options); iter; iter = g_list_next(iter))
+ { /* we store data in the search attributes */
+ event_option_t *const op = (event_option_t *)iter->data;
+ if (op->eo_type != OPTION_TYPE_PASSWORD || store_passwords)
+ g_variant_builder_add(attr_builder, "{ss}", op->eo_name,
op->eo_value);
+ }
+
+ /* and finally, add extra attribute used for searching */
+ g_variant_builder_add(attr_builder, "{ss}", SECRETS_SEARCH_ATTRIBUTE,
event_name);
+
+ /* An example of attributes: */
+ /* { */
+ /* "BugzillaURL": "Value1", */
+ /* "BugzillaPassword": "Value2", */
+ /* "libreportEventConfig": "event_name", */
+ /* } */
+ return g_variant_builder_end(attr_builder);
+}
+
+static bool find_item_by_event_name(const struct secrets_object *collection,
+ const char *event_name,
+ struct secrets_object **item)
+{
+ GVariantBuilder *const attrs = g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);
+
+ /* For searching uses item's attribute holding an event name */
+ g_variant_builder_add(attrs, "{ss}", SECRETS_SEARCH_ATTRIBUTE,
event_name);
+
+ return secrets_collection_search_one_item(collection, g_variant_builder_end(attrs),
item);
+}
+
+static void load_settings(struct secrets_object *collection, const char *event_name,
event_config_t *ec)
+{
+ /* Locked property is not supported by ksecrets */
+ /* thus we have to perform Unlock for each call of load settings */
+ /* because a collection can be locked by the service at anytime */
+ bool dismissed = false;
+ bool succ = secrets_service_unlock_object(collection, &dismissed);
+
+ if (succ && !dismissed)
+ {
+ struct secrets_object *item = NULL;
+ succ = find_item_by_event_name(collection, event_name, &item);
+ if (succ && item)
+ {
+ VERB2 log("loading event config : '%s'", event_name);
+ load_event_options_from_item(ec->options, item);
+ secrets_object_delete(item);
+ item = NULL;
+ }
+ }
+
+ if (!succ || dismissed)
+ secrets_service_set_unavailable();
+}
+
+static void load_event_config(gpointer key, gpointer value, gpointer user_data)
+{
+ char *const event_name = (char*)key;
+ event_config_t *const ec = (event_config_t *)value;
+ struct secrets_object *const collection = (struct secrets_object *)user_data;
+
+ if (is_event_config_user_storage_available())
+ load_settings(collection, event_name, ec);
+}
+
+static bool save_options(struct secrets_object *collection,
+ const char *event_name,
+ GList *options,
+ bool store_passwords,
+ bool *dismissed)
+{
+ struct secrets_object *item = NULL;
+ bool succ = find_item_by_event_name(collection, event_name, &item);
+
+ if (!succ)
+ return succ;
+
+ GVariant *const attributes = create_attributes_from_options(options,
+ event_name,
+ store_passwords);
+
+ if (item)
+ { /* item exists, change Attributes */
+ VERB2 log("updating event config : '%s'", event_name);
+ *dismissed = false;
+ succ = secrets_object_set_dbus_property(item, "Attributes",
attributes);
+ secrets_object_delete(item);
+ item = NULL;
+ }
+ else
+ { /* create a new item with Attributes */
+ VERB2 log("creating event config : '%s'", event_name);
+ succ = secrets_collection_create_text_item(collection, event_name, attributes,
dismissed);
+ }
+
+ return succ;
+}
+
+static void save_event_config(const char *event_name,
+ GList *options,
+ bool store_passwords)
+{
+ bool dismissed = false;
+
+ /* We have to handle four cases:
+ * 1. The default collection exists and is unlocked
+ * collection is set to some value different than NULL
+ * succ is TRUE - call was successful
+ * dismissed is FALSE - a prompt was confirmed
+ * In this case everything is ok and we are happy
+ *
+ * 2. The default collection doesn't exist.
+ * collection is NULL
+ * succ is TRUE - call was successful
+ * dismissed is FALSE - no collection no prompt no dismissed
+ * In this case everything is ok and we are happy
+ *
+ * 3. User dismissed a prompt for unlocking of the default collection
+ * collection is NULL - collection is not unlocked thus we can't read it
+ * succ is TRUE - call was successful
+ * dismissed is TRUE - USER DISMISSED A PROMPT, he don't want to unlock the
default collection !!
+ * We the set service state to unavailable in order to not bother user with
prompts anymore
+ *
+ * 4. An error occured
+ * collection is NULL
+ * succ is TRUE - call was NOT successful
+ * dismissed variable holds FALSE - no prompt no dismissed
+ * Set the service state to unavailable in order to not bother user with error
messages anymore
+ */
+ struct secrets_object *collection = NULL;
+ bool succ = get_default_collection(&collection, true, &dismissed);
+
+ if (collection)
+ {
+ succ = save_options(collection, event_name, options, store_passwords,
&dismissed);
+ secrets_object_delete(collection);
+ collection = NULL;
+ }
+
+ if (!succ || dismissed)
+ secrets_service_set_unavailable();
+}
+
+/******************************************************************************/
+/* Public interface */
+/******************************************************************************/
+
+bool is_event_config_user_storage_available()
+{
+ if (g_state == SBS_INITIAL)
+ g_state = secrets_service_connect();
+
+ return g_state != SBS_UNAVAILABLE;
+}
+
+/*
+ * Loads event config options for passed events
+ *
+ * @param event_config_list Events configs
+ */
+void load_event_config_data_from_user_storage(GHashTable *event_config_list)
+{
+ if (is_event_config_user_storage_available())
+ {
+ bool dismissed = false;
+ struct secrets_object *collection = NULL;
+
+ /* We have to handle four cases:
+ * 1. The default collection exists and is unlocked
+ * collection is set to some value different than NULL
+ * succ is TRUE - call was successful
+ * dismissed is FALSE - a prompt was confirmed
+ * In this case everything is ok and we are happy
+ *
+ * 2. The default collection doesn't exist.
+ * collection is NULL
+ * succ is TRUE - call was successful
+ * dismissed is FALSE - no collection no prompt no dismissed
+ * In this case everything is ok and we are happy
+ *
+ * 3. User dismissed a prompt for unlocking of the default collection
+ * collection is NULL - collection is not unlocked thus we can't read
it
+ * succ is TRUE - call was successful
+ * dismissed is TRUE - USER DISMISSED A PROMPT, he don't want to unlock
the default collection !!
+ * We the set service state to unavailable in order to not bother user with
prompts anymore
+ *
+ * 4. An error occured
+ * collection is NULL
+ * succ is TRUE - call was NOT successful
+ * dismissed variable holds FALSE - no prompt no dismissed
+ * Set the service state to unavailable in order to not bother user with
error messages anymore
+ */
+ const bool succ = get_default_collection(&collection, false,
&dismissed);
+
+ if (collection)
+ {
+ g_hash_table_foreach(event_config_list, &load_event_config, collection);
+ secrets_object_delete(collection);
+ collection = NULL;
+ }
+
+ if (!succ || dismissed)
+ secrets_service_set_unavailable();
+ }
+ else
+ {
+ VERB1 log("Can't load user's configuration due to unavailability of
D-Bus Secrets Service");
+ }
+}
+
+/*
+ * Saves an event_config options to some kind of session storage.
+ *
+ * @param event_name Lookup key
+ * @param event_config Event data
+ * @param store_passwords If TRUE stores options with passwords, otherwise skips them
+ */
+void save_event_config_data_to_user_storage(const char *event_name,
+ const event_config_t *event_config,
+ bool store_passwords)
+{
+ if (is_event_config_user_storage_available())
+ save_event_config(event_name, event_config->options, store_passwords);
+ else
+ {
+ VERB1 log("Can't save user's configuration due to unavailability of
D-Bus secrets API");
+ }
+}
diff --git a/src/gui-wizard-gtk/main.c b/src/gui-wizard-gtk/main.c
index 7d4de0c..c11e998 100644
--- a/src/gui-wizard-gtk/main.c
+++ b/src/gui-wizard-gtk/main.c
@@ -137,7 +137,7 @@ int main(int argc, char **argv)
/* load /etc/abrt/events/foo.{conf,xml} stuff
and $XDG_CACHE_HOME/abrt/events/foo.conf */
load_event_config_data();
- load_event_config_data_from_keyring();
+ load_event_config_data_from_user_storage(g_event_config_list);
load_user_settings("report-gtk");
problem_data_reload_from_dump_dir();
--
1.7.10.4