[gnome-keyring] daemon: Emit secret service signals when collections/items change



commit 8ce8788e850357a1467e6f18ca952248888da116
Author: Stef Walter <stefw gnome org>
Date:   Wed Jun 27 11:38:29 2012 +0200

    daemon: Emit secret service signals when collections/items change
    
     * Emit the Secret Service DBus API signals when collections/items
       change.
     * Also fire the PropertiesChanged signal appropriately

 .gitignore                              |    2 +
 daemon/dbus/gkd-secret-create.c         |    6 +-
 daemon/dbus/gkd-secret-objects.c        |  391 ++++++++++++--
 daemon/dbus/gkd-secret-objects.h        |   19 +
 daemon/dbus/gkd-secret-service.c        |  116 ++++-
 daemon/dbus/gkd-secret-service.h        |    6 +
 daemon/dbus/gkd-secret-unlock.c         |   16 +
 daemon/dbus/tests/Makefile.am           |   13 +-
 daemon/dbus/tests/files/test.keyring    |  Bin 0 -> 180 bytes
 daemon/dbus/tests/test-secret-signals.c |  916 +++++++++++++++++++++++++++++++
 10 files changed, 1432 insertions(+), 53 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index eecb8d1..a6e8721 100644
--- a/.gitignore
+++ b/.gitignore
@@ -104,6 +104,8 @@ p11-tests.conf
 /daemon/control/tests/frob-control-quit
 /daemon/control/tests/frob-control-unlock
 
+/daemon/dbus/tests/test-secret-signals
+
 /pkcs11/gkm/tests/test-attributes
 /pkcs11/gkm/tests/test-certificate
 /pkcs11/gkm/tests/test-credential
diff --git a/daemon/dbus/gkd-secret-create.c b/daemon/dbus/gkd-secret-create.c
index d59b319..a3aeae4 100644
--- a/daemon/dbus/gkd-secret-create.c
+++ b/daemon/dbus/gkd-secret-create.c
@@ -117,14 +117,18 @@ create_collection_with_secret (GkdSecretCreate *self, GkdSecretSecret *master)
 		return FALSE;
 	}
 
+	service = gkd_secret_prompt_get_service (GKD_SECRET_PROMPT (self));
+
 	if (self->alias) {
 		if (!gkd_secret_util_parse_path (self->result_path, &identifier, NULL))
 			g_assert_not_reached ();
-		service = gkd_secret_prompt_get_service (GKD_SECRET_PROMPT (self));
 		gkd_secret_service_set_alias (service, self->alias, identifier);
 		g_free (identifier);
 	}
 
+	/* Notify the callers that a collection was created */
+	gkd_secret_service_emit_collection_created (service, self->result_path);
+
 	return TRUE;
 }
 
diff --git a/daemon/dbus/gkd-secret-objects.c b/daemon/dbus/gkd-secret-objects.c
index 491d130..8bc500f 100644
--- a/daemon/dbus/gkd-secret-objects.c
+++ b/daemon/dbus/gkd-secret-objects.c
@@ -51,6 +51,13 @@ struct _GkdSecretObjects {
 	GckSlot *pkcs11_slot;
 };
 
+static gchar *    object_path_for_item          (const gchar *base,
+                                                 GckObject *item);
+
+static gchar *    object_path_for_collection    (GckObject *collection);
+
+static gchar *    collection_path_for_item      (GckObject *item);
+
 G_DEFINE_TYPE (GkdSecretObjects, gkd_secret_objects, G_TYPE_OBJECT);
 
 /* -----------------------------------------------------------------------------
@@ -122,8 +129,10 @@ object_property_get (GckObject *object, DBusMessage *message,
 }
 
 static DBusMessage*
-object_property_set (GckObject *object, DBusMessage *message,
-                     DBusMessageIter *iter, const gchar *prop_name)
+object_property_set (GckObject *object,
+                     DBusMessage *message,
+                     DBusMessageIter *iter,
+                     const gchar *prop_name)
 {
 	GckBuilder builder = GCK_BUILDER_INIT;
 	DBusMessage *reply;
@@ -180,11 +189,14 @@ item_property_get (GckObject *object, DBusMessage *message)
 }
 
 static DBusMessage*
-item_property_set (GckObject *object, DBusMessage *message)
+item_property_set (GkdSecretObjects *self,
+                   GckObject *object,
+                   DBusMessage *message)
 {
 	DBusMessageIter iter;
 	const char *interface;
 	const char *name;
+	DBusMessage *reply;
 
 	if (!dbus_message_has_signature (message, "ssv"))
 		return NULL;
@@ -200,7 +212,13 @@ item_property_set (GckObject *object, DBusMessage *message)
 		                                      "Object does not have properties on interface '%s'",
 		                                      interface);
 
-	return object_property_set (object, message, &iter, name);
+	reply = object_property_set (object, message, &iter, name);
+
+	/* Notify everyone a property changed */
+	if (reply && dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN)
+		gkd_secret_objects_emit_item_changed (self, object, name, NULL);
+
+	return reply;
 }
 
 static DBusMessage*
@@ -248,13 +266,30 @@ static DBusMessage*
 item_method_delete (GkdSecretObjects *self, GckObject *object, DBusMessage *message)
 {
 	GError *error = NULL;
+	gchar *collection_path;
+	gchar *item_path;
 	DBusMessage *reply;
 	const gchar *prompt;
+	GckObject *collection;
 
 	if (!dbus_message_get_args (message, NULL, DBUS_TYPE_INVALID))
 		return NULL;
 
-	if (!gck_object_destroy (object, NULL, &error)) {
+	collection_path = collection_path_for_item (object);
+	item_path = object_path_for_item (NULL, object);
+
+	if (gck_object_destroy (object, NULL, &error)) {
+		collection = gkd_secret_objects_lookup_collection (self, NULL, collection_path);
+		if (collection != NULL) {
+			gkd_secret_objects_emit_item_deleted (self, collection, item_path);
+			g_object_unref (collection);
+		}
+
+		prompt = "/"; /* No prompt necessary */
+		reply = dbus_message_new_method_return (message);
+		dbus_message_append_args (reply, DBUS_TYPE_OBJECT_PATH, &prompt, DBUS_TYPE_INVALID);
+
+	} else {
 		if (g_error_matches (error, GCK_ERROR, CKR_USER_NOT_LOGGED_IN))
 			reply = dbus_message_new_error_printf (message, SECRET_ERROR_IS_LOCKED,
 			                                       "Cannot delete a locked item");
@@ -262,13 +297,12 @@ item_method_delete (GkdSecretObjects *self, GckObject *object, DBusMessage *mess
 			reply = dbus_message_new_error_printf (message, DBUS_ERROR_FAILED,
 			                                       "Couldn't delete collection: %s",
 			                                       egg_error_message (error));
+
 		g_clear_error (&error);
-		return reply;
 	}
 
-	prompt = "/"; /* No prompt necessary */
-	reply = dbus_message_new_method_return (message);
-	dbus_message_append_args (reply, DBUS_TYPE_OBJECT_PATH, &prompt, DBUS_TYPE_INVALID);
+	g_free (collection_path);
+	g_free (item_path);
 	return reply;
 }
 
@@ -348,7 +382,7 @@ item_message_handler (GkdSecretObjects *self, GckObject *object, DBusMessage *me
 
 	/* org.freedesktop.DBus.Properties.Set */
 	else if (dbus_message_is_method_call (message, DBUS_INTERFACE_PROPERTIES, "Set"))
-		return item_property_set (object, message);
+		return item_property_set (self, object, message);
 
 	/* org.freedesktop.DBus.Properties.GetAll */
 	else if (dbus_message_is_method_call (message, DBUS_INTERFACE_PROPERTIES, "GetAll"))
@@ -424,6 +458,7 @@ static DBusMessage*
 collection_property_set (GkdSecretObjects *self, GckObject *object, DBusMessage *message)
 {
 	DBusMessageIter iter;
+	DBusMessage *reply;
 	const char *interface;
 	const char *name;
 
@@ -441,7 +476,13 @@ collection_property_set (GkdSecretObjects *self, GckObject *object, DBusMessage
 		                                      "Object does not have properties on interface '%s'",
 		                                      interface);
 
-	return object_property_set (object, message, &iter, name);
+	reply = object_property_set (object, message, &iter, name);
+
+	/* Notify everyone a property changed */
+	if (reply && dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN)
+		gkd_secret_objects_emit_collection_changed (self, object, name, NULL);
+
+	return reply;
 }
 
 static DBusMessage*
@@ -543,7 +584,7 @@ collection_find_matching_item (GkdSecretObjects *self,
 
 static gchar *
 object_path_for_item (const gchar *base,
-                      GckObject *object)
+                      GckObject *item)
 {
 	GError *error = NULL;
 	gpointer identifier;
@@ -551,19 +592,10 @@ object_path_for_item (const gchar *base,
 	gchar *alloc = NULL;
 	gchar *path = NULL;
 
-	if (base == NULL) {
-		identifier = gck_object_get_data (object, CKA_G_COLLECTION, NULL, &n_identifier, &error);
-		if (!identifier) {
-			g_warning ("couldn't get item collection identifier: %s", egg_error_message (error));
-			g_clear_error (&error);
-			return NULL;
-		}
-
-		base = alloc = gkd_secret_util_build_path (SECRET_COLLECTION_PREFIX, identifier, n_identifier);
-		g_free (identifier);
-	}
+	if (base == NULL)
+		base = alloc = collection_path_for_item (item);
 
-	identifier = gck_object_get_data (object, CKA_ID, NULL, &n_identifier, &error);
+	identifier = gck_object_get_data (item, CKA_ID, NULL, &n_identifier, &error);
 	if (identifier == NULL) {
 		g_warning ("couldn't get item identifier: %s", egg_error_message (error));
 		g_clear_error (&error);
@@ -578,6 +610,48 @@ object_path_for_item (const gchar *base,
 	return path;
 }
 
+static gchar *
+collection_path_for_item (GckObject *item)
+{
+	GError *error = NULL;
+	gpointer identifier;
+	gsize n_identifier;
+	gchar *path = NULL;
+
+	identifier = gck_object_get_data (item, CKA_G_COLLECTION, NULL, &n_identifier, &error);
+	if (!identifier) {
+		g_warning ("couldn't get item collection identifier: %s", egg_error_message (error));
+		g_clear_error (&error);
+		return NULL;
+	}
+
+	path = gkd_secret_util_build_path (SECRET_COLLECTION_PREFIX, identifier, n_identifier);
+	g_free (identifier);
+	return path;
+}
+
+static gchar *
+object_path_for_collection (GckObject *collection)
+{
+	GError *error = NULL;
+	gpointer identifier;
+	gsize n_identifier;
+	gchar *path = NULL;
+
+	identifier = gck_object_get_data (collection, CKA_ID, NULL, &n_identifier, &error);
+	if (identifier == NULL) {
+		g_warning ("couldn't get collection identifier: %s", egg_error_message (error));
+		g_clear_error (&error);
+		path = NULL;
+
+	} else {
+		path = gkd_secret_util_build_path (SECRET_COLLECTION_PREFIX, identifier, n_identifier);
+		g_free (identifier);
+	}
+
+	return path;
+}
+
 static DBusMessage*
 collection_method_create_item (GkdSecretObjects *self, GckObject *object, DBusMessage *message)
 {
@@ -657,10 +731,12 @@ collection_method_create_item (GkdSecretObjects *self, GckObject *object, DBusMe
 		goto cleanup;
 	}
 
+	path = object_path_for_item (base, item);
+	gkd_secret_objects_emit_item_created (self, object, item);
+
 	/* Build up the item identifier */
 	reply = dbus_message_new_method_return (message);
 	dbus_message_iter_init_append (reply, &iter);
-	path = object_path_for_item (base, item);
 	dbus_message_iter_append_basic (&iter, DBUS_TYPE_OBJECT_PATH, &path);
 	prompt = "/";
 	dbus_message_iter_append_basic (&iter, DBUS_TYPE_OBJECT_PATH, &prompt);
@@ -701,18 +777,27 @@ collection_method_delete (GkdSecretObjects *self, GckObject *object, DBusMessage
 	GError *error = NULL;
 	DBusMessage *reply;
 	const gchar *prompt;
+	gchar *path;
 
 	if (!dbus_message_get_args (message, NULL, DBUS_TYPE_INVALID))
 		return NULL;
 
+	path = object_path_for_collection (object);
+	g_return_val_if_fail (path != NULL, NULL);
+
 	if (!gck_object_destroy (object, NULL, &error)) {
 		reply = dbus_message_new_error_printf (message, DBUS_ERROR_FAILED,
 		                                       "Couldn't delete collection: %s",
 		                                       egg_error_message (error));
 		g_clear_error (&error);
+		g_free (path);
 		return reply;
 	}
 
+	/* Notify the callers that a collection was deleted */
+	gkd_secret_service_emit_collection_deleted (self->service, path);
+	g_free (path);
+
 	prompt = "/";
 	reply = dbus_message_new_method_return (message);
 	dbus_message_append_args (reply, DBUS_TYPE_OBJECT_PATH, &prompt, DBUS_TYPE_INVALID);
@@ -981,14 +1066,16 @@ gkd_secret_objects_lookup_collection (GkdSecretObjects *self, const gchar *calle
 	gchar *identifier;
 
 	g_return_val_if_fail (GKD_SECRET_IS_OBJECTS (self), NULL);
-	g_return_val_if_fail (caller, NULL);
 	g_return_val_if_fail (path, NULL);
 
 	if (!parse_object_path (self, path, &identifier, NULL))
 		return NULL;
 
 	/* The session we're using to access the object */
-	session = gkd_secret_service_get_pkcs11_session (self->service, caller);
+	if (caller == NULL)
+		session = gkd_secret_service_internal_pkcs11_session (self->service);
+	else
+		session = gkd_secret_service_get_pkcs11_session (self->service, caller);
 	g_return_val_if_fail (session, NULL);
 
 	gck_builder_add_ulong (&builder, CKA_CLASS, CKO_G_COLLECTION);
@@ -1089,8 +1176,12 @@ gkd_secret_objects_foreach_item (GkdSecretObjects *self,
 	g_return_if_fail (callback != NULL);
 
 	/* The session we're using to access the object */
-	session = gkd_secret_service_get_pkcs11_session (self->service, dbus_message_get_sender (message));
-	g_return_if_fail (session);
+	if (message == NULL) {
+		session = gkd_secret_service_internal_pkcs11_session (self->service);
+	} else {
+		session = gkd_secret_service_get_pkcs11_session (self->service,
+		                                                 dbus_message_get_sender (message));
+	}
 
 	if (!parse_object_path (self, base, &identifier, NULL))
 		g_return_if_reached ();
@@ -1134,7 +1225,6 @@ gkd_secret_objects_append_item_paths (GkdSecretObjects *self,
 	g_return_if_fail (GKD_SECRET_IS_OBJECTS (self));
 	g_return_if_fail (base);
 	g_return_if_fail (iter);
-	g_return_if_fail (message);
 
 
 	dbus_message_iter_open_container (iter, DBUS_TYPE_VARIANT, "ao", &variant);
@@ -1164,8 +1254,12 @@ gkd_secret_objects_foreach_collection (GkdSecretObjects *self,
 	g_return_if_fail (callback);
 
 	/* The session we're using to access the object */
-	session = gkd_secret_service_get_pkcs11_session (self->service, dbus_message_get_sender (message));
-	g_return_if_fail (session);
+	if (message == NULL) {
+		session = gkd_secret_service_internal_pkcs11_session (self->service);
+	} else {
+		session = gkd_secret_service_get_pkcs11_session (self->service,
+		                                                 dbus_message_get_sender (message));
+	}
 
 	gck_builder_add_ulong (&builder, CKA_CLASS, CKO_G_COLLECTION);
 
@@ -1205,7 +1299,7 @@ gkd_secret_objects_append_collection_paths (GkdSecretObjects *self,
 	DBusMessageIter array;
 
 	g_return_if_fail (GKD_SECRET_IS_OBJECTS (self));
-	g_return_if_fail (iter && message);
+	g_return_if_fail (iter != NULL);
 
 	dbus_message_iter_open_container (iter, DBUS_TYPE_VARIANT, "ao", &variant);
 	dbus_message_iter_open_container (&variant, DBUS_TYPE_ARRAY, "o", &array);
@@ -1380,3 +1474,234 @@ gkd_secret_objects_handle_get_secrets (GkdSecretObjects *self, DBusMessage *mess
 
 	return reply;
 }
+
+static void
+on_each_item_emit_locked (GkdSecretObjects *self,
+                          const gchar *path,
+                          GckObject *object,
+                          gpointer user_data)
+{
+	gkd_secret_objects_emit_item_changed (self, object, "Locked", NULL);
+}
+
+void
+gkd_secret_objects_emit_collection_locked (GkdSecretObjects *self,
+                                           GckObject *collection)
+{
+	const gchar *collection_path;
+
+	collection_path = object_path_for_collection (collection);
+	gkd_secret_objects_foreach_item (self, NULL, collection_path,
+	                                 on_each_item_emit_locked, NULL);
+
+	gkd_secret_objects_emit_collection_changed (self, collection, "Locked", NULL);
+}
+
+static void
+emit_object_properties_changed (GkdSecretObjects *self,
+                                GckObject *object,
+                                const gchar *path,
+                                const gchar *iface,
+                                va_list va)
+{
+	gchar *collection_path;
+	const gchar *propname;
+	DBusMessage *message;
+	DBusMessageIter iter;
+	DBusMessageIter array;
+	DBusMessageIter dict;
+	CK_ATTRIBUTE_TYPE type;
+	GckAttributes *attrs;
+	GError *error = NULL;
+	gboolean items = FALSE;
+	GArray *types;
+
+	types = g_array_new (FALSE, FALSE, sizeof (CK_ATTRIBUTE_TYPE));
+	while ((propname = va_arg (va, const gchar *)) != NULL) {
+
+		/* Special case the Items property */
+		if (g_str_equal (propname, "Items")) {
+			items = TRUE;
+			continue;
+		}
+
+		if (gkd_secret_property_get_type (propname, &type))
+			g_array_append_val (types, type);
+		else
+			g_warning ("invalid property: %s", propname);
+	}
+
+	attrs = gck_object_get_full (object, (CK_ATTRIBUTE_TYPE *)types->data,
+	                             types->len, NULL, &error);
+	g_array_free (types, TRUE);
+
+	if (error != NULL) {
+		g_warning ("couldn't retrieve properties: %s", egg_error_message (error));
+		return;
+	}
+
+	message = dbus_message_new_signal (path, DBUS_INTERFACE_PROPERTIES,
+	                                   "PropertiesChanged");
+
+	dbus_message_iter_init_append (message, &iter);
+	dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &iface);
+	dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "{sv}", &array);
+	gkd_secret_property_append_all (&array, attrs);
+
+	/* Append the Items property */
+	if (items) {
+		collection_path = object_path_for_collection (object);
+		dbus_message_iter_open_container (&array, DBUS_TYPE_DICT_ENTRY, NULL, &dict);
+		propname = "Items";
+		dbus_message_iter_append_basic (&dict, DBUS_TYPE_STRING, &propname);
+		gkd_secret_objects_append_item_paths (self, collection_path, &dict, NULL);
+		dbus_message_iter_close_container (&array, &dict);
+		g_free (collection_path);
+	}
+
+	dbus_message_iter_close_container (&iter, &array);
+	dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "s", &array);
+	dbus_message_iter_close_container (&iter, &array);
+
+	if (!dbus_connection_send (gkd_secret_service_get_connection (self->service),
+	                           message, NULL))
+		g_return_if_reached ();
+	dbus_message_unref (message);
+
+	gck_attributes_unref (attrs);
+}
+
+void
+gkd_secret_objects_emit_collection_changed (GkdSecretObjects *self,
+                                            GckObject *collection,
+                                            ...)
+{
+	DBusMessage *message;
+	gchar *collection_path;
+	va_list va;
+
+	g_return_if_fail (GKD_SECRET_IS_OBJECTS (self));
+	g_return_if_fail (GCK_OBJECT (collection));
+
+	collection_path = object_path_for_collection (collection);
+
+	message = dbus_message_new_signal (SECRET_SERVICE_PATH,
+	                                   SECRET_SERVICE_INTERFACE,
+	                                   "CollectionChanged");
+	dbus_message_append_args (message, DBUS_TYPE_OBJECT_PATH, &collection_path,
+	                          DBUS_TYPE_INVALID);
+
+	if (!dbus_connection_send (gkd_secret_service_get_connection (self->service),
+	                           message, NULL))
+		g_return_if_reached ();
+
+	dbus_message_unref (message);
+
+	va_start (va, collection);
+	emit_object_properties_changed (self, collection, collection_path,
+	                                SECRET_COLLECTION_INTERFACE, va);
+	va_end (va);
+
+	g_free (collection_path);
+}
+
+void
+gkd_secret_objects_emit_item_created (GkdSecretObjects *self,
+                                      GckObject *collection,
+                                      GckObject *item)
+{
+	DBusMessage *message;
+	gchar *collection_path;
+	gchar *item_path;
+
+	g_return_if_fail (GKD_SECRET_IS_OBJECTS (self));
+	g_return_if_fail (GCK_OBJECT (collection));
+	g_return_if_fail (GCK_OBJECT (item));
+
+	collection_path = object_path_for_collection (collection);
+	item_path = object_path_for_item (collection_path, item);
+
+	message = dbus_message_new_signal (collection_path,
+	                                   SECRET_COLLECTION_INTERFACE,
+	                                   "ItemCreated");
+	dbus_message_append_args (message, DBUS_TYPE_OBJECT_PATH, &item_path,
+	                          DBUS_TYPE_INVALID);
+
+	if (!dbus_connection_send (gkd_secret_service_get_connection (self->service),
+	                           message, NULL))
+		g_return_if_reached ();
+
+	dbus_message_unref (message);
+
+	gkd_secret_objects_emit_collection_changed (self, collection, "Items", NULL);
+
+	g_free (item_path);
+	g_free (collection_path);
+}
+
+void
+gkd_secret_objects_emit_item_changed (GkdSecretObjects *self,
+                                      GckObject *item,
+                                      ...)
+{
+	DBusMessage *message;
+	gchar *collection_path;
+	gchar *item_path;
+	va_list va;
+
+	g_return_if_fail (GKD_SECRET_IS_OBJECTS (self));
+	g_return_if_fail (GCK_OBJECT (item));
+
+	collection_path = collection_path_for_item (item);
+	item_path = object_path_for_item (collection_path, item);
+
+	message = dbus_message_new_signal (collection_path,
+	                                   SECRET_COLLECTION_INTERFACE,
+	                                   "ItemChanged");
+	dbus_message_append_args (message, DBUS_TYPE_OBJECT_PATH, &item_path,
+	                          DBUS_TYPE_INVALID);
+
+	if (!dbus_connection_send (gkd_secret_service_get_connection (self->service),
+	                           message, NULL))
+		g_return_if_reached ();
+
+	dbus_message_unref (message);
+
+	va_start (va, item);
+	emit_object_properties_changed (self, item, item_path,
+	                                SECRET_ITEM_INTERFACE, va);
+	va_end (va);
+
+	g_free (item_path);
+	g_free (collection_path);
+}
+
+void
+gkd_secret_objects_emit_item_deleted (GkdSecretObjects *self,
+                                      GckObject *collection,
+                                      const gchar *item_path)
+{
+	DBusMessage *message;
+	gchar *collection_path;
+
+	g_return_if_fail (GKD_SECRET_IS_OBJECTS (self));
+	g_return_if_fail (GCK_OBJECT (collection));
+	g_return_if_fail (item_path != NULL);
+
+	collection_path = object_path_for_collection (collection);
+
+	message = dbus_message_new_signal (collection_path,
+	                                   SECRET_COLLECTION_INTERFACE,
+	                                   "ItemDeleted");
+	dbus_message_append_args (message, DBUS_TYPE_OBJECT_PATH, &item_path,
+	                          DBUS_TYPE_INVALID);
+
+	if (!dbus_connection_send (gkd_secret_service_get_connection (self->service),
+	                           message, NULL))
+		g_return_if_reached ();
+
+	dbus_message_unref (message);
+	g_free (collection_path);
+
+	gkd_secret_objects_emit_collection_changed (self, collection, "Items", NULL);
+}
diff --git a/daemon/dbus/gkd-secret-objects.h b/daemon/dbus/gkd-secret-objects.h
index 234efd3..bd7f1aa 100644
--- a/daemon/dbus/gkd-secret-objects.h
+++ b/daemon/dbus/gkd-secret-objects.h
@@ -90,4 +90,23 @@ GckObject*          gkd_secret_objects_lookup_item               (GkdSecretObjec
                                                                   const gchar *caller,
                                                                   const gchar *path);
 
+void                gkd_secret_objects_emit_collection_locked    (GkdSecretObjects *self,
+                                                                  GckObject *collection);
+
+void                gkd_secret_objects_emit_collection_changed   (GkdSecretObjects *self,
+                                                                  GckObject *collection,
+                                                                  ...) G_GNUC_NULL_TERMINATED;
+
+void                gkd_secret_objects_emit_item_created         (GkdSecretObjects *self,
+                                                                  GckObject *collection,
+                                                                  GckObject *item);
+
+void                gkd_secret_objects_emit_item_changed         (GkdSecretObjects *self,
+                                                                  GckObject *item,
+                                                                  ...) G_GNUC_NULL_TERMINATED;
+
+void                gkd_secret_objects_emit_item_deleted         (GkdSecretObjects *self,
+                                                                  GckObject *collection,
+                                                                  const gchar *item_path);
+
 #endif /* __GKD_SECRET_OBJECTS_H__ */
diff --git a/daemon/dbus/gkd-secret-service.c b/daemon/dbus/gkd-secret-service.c
index de57446..4aa166e 100644
--- a/daemon/dbus/gkd-secret-service.c
+++ b/daemon/dbus/gkd-secret-service.c
@@ -334,15 +334,31 @@ service_property_set (GkdSecretService *self, DBusMessage *message)
 	return NULL; /* TODO: Need to implement */
 }
 
+static void
+service_append_all_properties (GkdSecretService *self,
+                               DBusMessageIter *iter)
+{
+	DBusMessageIter array;
+	DBusMessageIter dict;
+	const gchar *name;
+
+	dbus_message_iter_open_container (iter, DBUS_TYPE_ARRAY, "{sv}", &array);
+
+	name = "Collections";
+	dbus_message_iter_open_container (&array, DBUS_TYPE_DICT_ENTRY, NULL, &dict);
+	dbus_message_iter_append_basic (&dict, DBUS_TYPE_STRING, &name);
+	gkd_secret_objects_append_collection_paths (self->objects, &dict, NULL);
+	dbus_message_iter_close_container (&array, &dict);
+
+	dbus_message_iter_close_container (iter, &array);
+}
+
 static DBusMessage*
 service_property_getall (GkdSecretService *self, DBusMessage *message)
 {
 	DBusMessage *reply = NULL;
-	DBusMessageIter array;
-	DBusMessageIter dict;
 	DBusMessageIter iter;
 	const gchar *interface;
-	const gchar *name;
 
 	if (!dbus_message_get_args (message, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_INVALID))
 		return NULL;
@@ -354,16 +370,7 @@ service_property_getall (GkdSecretService *self, DBusMessage *message)
 
 	reply = dbus_message_new_method_return (message);
 	dbus_message_iter_init_append (reply, &iter);
-	dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "{sv}", &array);
-
-	name = "Collections";
-	dbus_message_iter_open_container (&array, DBUS_TYPE_DICT_ENTRY, NULL, &dict);
-	dbus_message_iter_append_basic (&dict, DBUS_TYPE_STRING, &name);
-	gkd_secret_objects_append_collection_paths (self->objects, &dict, message);
-	dbus_message_iter_close_container (&array, &dict);
-
-	dbus_message_iter_close_container (&iter, &array);
-
+	service_append_all_properties (self, &iter);
 	return reply;
 }
 
@@ -529,8 +536,11 @@ service_method_lock (GkdSecretService *self, DBusMessage *message)
 	for (i = 0; i < n_objpaths; ++i) {
 		collection = gkd_secret_objects_lookup_collection (self->objects, caller, objpaths[i]);
 		if (collection != NULL) {
-			if (gkd_secret_lock (collection, NULL))
+			if (gkd_secret_lock (collection, NULL)) {
 				g_ptr_array_add (array, objpaths[i]);
+				gkd_secret_objects_emit_collection_locked (self->objects,
+				                                           collection);
+			}
 			g_object_unref (collection);
 		}
 	}
@@ -700,6 +710,9 @@ service_method_create_with_master_password (GkdSecretService *self, DBusMessage
 	if (path == NULL)
 		return gkd_secret_propagate_error (message, "Couldn't create collection", error);
 
+	/* Notify the callers that a collection was created */
+	gkd_secret_service_emit_collection_created (self, path);
+
 	reply = dbus_message_new_method_return (message);
 	dbus_message_append_args (reply, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID);
 	g_free (path);
@@ -790,17 +803,19 @@ service_method_unlock_with_master_password (GkdSecretService *self, DBusMessage
 	                                                   path);
 
 	/* No such collection */
-	if (collection == NULL)
+	if (collection == NULL) {
 		reply = dbus_message_new_error (message, SECRET_ERROR_NO_SUCH_OBJECT,
 		                                "The collection does not exist");
 
 	/* Success */
-	else if (gkd_secret_unlock_with_secret (collection, master, &error))
+	} else if (gkd_secret_unlock_with_secret (collection, master, &error)) {
 		reply = dbus_message_new_method_return (message);
+		gkd_secret_objects_emit_collection_locked (self->objects, collection);
 
 	/* Failure */
-	else
+	} else {
 		reply = gkd_secret_propagate_error (message, "Couldn't unlock collection", error);
+	}
 
 	gkd_secret_secret_free (master);
 
@@ -1469,3 +1484,70 @@ gkd_secret_service_publish_dispatch (GkdSecretService *self, const gchar *caller
 	g_return_if_fail (!g_hash_table_lookup (client->dispatch, path));
 	g_hash_table_replace (client->dispatch, (gpointer)path, g_object_ref (object));
 }
+
+static void
+emit_collections_properties_changed (GkdSecretService *self)
+{
+	const gchar *iface = SECRET_SERVICE_INTERFACE;
+	DBusMessage *message;
+	DBusMessageIter array;
+	DBusMessageIter iter;
+
+	message = dbus_message_new_signal (SECRET_SERVICE_PATH,
+	                                   DBUS_INTERFACE_PROPERTIES,
+	                                   "PropertiesChanged");
+
+	dbus_message_iter_init_append (message, &iter);
+	dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &iface);
+	service_append_all_properties (self, &iter);
+	dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "s", &array);
+	dbus_message_iter_close_container (&iter, &array);
+
+	if (!dbus_connection_send (self->connection, message, NULL))
+		g_return_if_reached ();
+	dbus_message_unref (message);
+}
+
+void
+gkd_secret_service_emit_collection_created (GkdSecretService *self,
+                                            const gchar *collection_path)
+{
+	DBusMessage *message;
+
+	g_return_if_fail (GKD_SECRET_IS_SERVICE (self));
+	g_return_if_fail (collection_path != NULL);
+
+	message = dbus_message_new_signal (SECRET_SERVICE_PATH,
+	                                   SECRET_SERVICE_INTERFACE,
+	                                   "CollectionCreated");
+	dbus_message_append_args (message, DBUS_TYPE_OBJECT_PATH, &collection_path,
+	                          DBUS_TYPE_INVALID);
+
+	if (!dbus_connection_send (self->connection, message, NULL))
+		g_return_if_reached ();
+	dbus_message_unref (message);
+
+	emit_collections_properties_changed (self);
+}
+
+void
+gkd_secret_service_emit_collection_deleted (GkdSecretService *self,
+                                            const gchar *collection_path)
+{
+	DBusMessage *message;
+
+	g_return_if_fail (GKD_SECRET_IS_SERVICE (self));
+	g_return_if_fail (collection_path != NULL);
+
+	message = dbus_message_new_signal (SECRET_SERVICE_PATH,
+	                                   SECRET_SERVICE_INTERFACE,
+	                                   "CollectionDeleted");
+	dbus_message_append_args (message, DBUS_TYPE_OBJECT_PATH, &collection_path,
+	                          DBUS_TYPE_INVALID);
+
+	if (!dbus_connection_send (self->connection, message, NULL))
+		g_return_if_reached ();
+	dbus_message_unref (message);
+
+	emit_collections_properties_changed (self);
+}
diff --git a/daemon/dbus/gkd-secret-service.h b/daemon/dbus/gkd-secret-service.h
index 9fc303d..981854b 100644
--- a/daemon/dbus/gkd-secret-service.h
+++ b/daemon/dbus/gkd-secret-service.h
@@ -79,4 +79,10 @@ void                    gkd_secret_service_publish_dispatch        (GkdSecretSer
                                                                     const gchar *caller,
                                                                     GkdSecretDispatch *object);
 
+void                    gkd_secret_service_emit_collection_created (GkdSecretService *self,
+                                                                    const gchar *collection_path);
+
+void                    gkd_secret_service_emit_collection_deleted (GkdSecretService *self,
+                                                                    const gchar *collection_path);
+
 #endif /* ___SECRET_SERVICE_H__ */
diff --git a/daemon/dbus/gkd-secret-unlock.c b/daemon/dbus/gkd-secret-unlock.c
index 2e3fbed..82330c4 100644
--- a/daemon/dbus/gkd-secret-unlock.c
+++ b/daemon/dbus/gkd-secret-unlock.c
@@ -96,6 +96,21 @@ lookup_collection (GkdSecretUnlock *self, const gchar *path)
 	return gkd_secret_objects_lookup_collection (objects, self->caller, path);
 }
 
+static void
+emit_collection_unlocked (GkdSecretUnlock *self,
+                          const gchar *path)
+{
+	GkdSecretObjects *objects;
+	GckObject *collection;
+
+	objects = gkd_secret_service_get_objects (self->service);
+	collection = gkd_secret_objects_lookup_collection (objects, self->caller, path);
+	if (collection != NULL) {
+		gkd_secret_objects_emit_collection_locked (objects, collection);
+		g_object_unref (collection);
+	}
+}
+
 static gboolean
 check_locked_collection (GckObject *collection, gboolean *locked)
 {
@@ -195,6 +210,7 @@ on_unlock_complete (GObject *object, GAsyncResult *res, gpointer user_data)
 	/* Successfully authentication */
 	if (cred) {
 		g_object_unref (cred);
+		emit_collection_unlocked (self, self->current);
 		g_array_append_val (self->results, self->current);
 		self->current = NULL;
 		perform_next_unlock (self);
diff --git a/daemon/dbus/tests/Makefile.am b/daemon/dbus/tests/Makefile.am
index ad89268..6c1daca 100644
--- a/daemon/dbus/tests/Makefile.am
+++ b/daemon/dbus/tests/Makefile.am
@@ -2,14 +2,23 @@
 INCLUDES = \
 	-I$(top_srcdir)/daemon/dbus \
 	-DSRCDIR="\"@abs_srcdir \"" \
+	-DTOP_SRCDIR="\"@abs_top_srcdir \"" \
+	-DGCR_API_SUBJECT_TO_CHANGE \
 	$(DAEMON_CFLAGS) \
+	$(GCR_BASE_CFLAGS) \
+	$(GIO_CFLAGS) \
 	$(GLIB_CFLAGS)
 
 LDADD =  \
-	$(top_builddir)/daemon/dbus/libgkd-dbus.la
+	$(top_builddir)/daemon/dbus/libgkd-dbus.la \
+	$(top_builddir)/egg/libegg-test.la \
+	$(GCR_BASE_LIBS) \
+	$(GIO_LIBS) \
+	$(GLIB_LIBS)
 
 TEST_PROGS = \
-	test-secret-util
+	test-secret-util \
+	test-secret-signals
 
 check_PROGRAMS = $(TEST_PROGS)
 
diff --git a/daemon/dbus/tests/files/test.keyring b/daemon/dbus/tests/files/test.keyring
new file mode 100644
index 0000000..f53ed5d
Binary files /dev/null and b/daemon/dbus/tests/files/test.keyring differ
diff --git a/daemon/dbus/tests/test-secret-signals.c b/daemon/dbus/tests/test-secret-signals.c
new file mode 100644
index 0000000..0a99ecb
--- /dev/null
+++ b/daemon/dbus/tests/test-secret-signals.c
@@ -0,0 +1,916 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* test-secret-util.c: Test secret utils
+
+   Copyright (C) 2012 Red Hat Inc
+
+   The Gnome Keyring Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Keyring 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Author: Stef Walter <stefw gnome org>
+*/
+
+#include "config.h"
+
+#include "gkd-secret-types.h"
+
+#include "egg/egg-testing.h"
+
+#include <gcr/gcr-base.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+
+#include <fcntl.h>
+
+typedef struct {
+	gchar *path;
+	gchar *iface;
+	gchar *name;
+	GVariant *parameters;
+} ReceivedSignal;
+
+typedef struct {
+	GDBusConnection *connection;
+	gchar *service_name;
+	const gchar *mock_prompter;
+	GPid service_pid;
+	gboolean service_available;
+	gchar *service_session;
+	guint watch_id;
+	guint signal_id;
+	gchar *directory;
+	GList *received_signals;
+} Test;
+
+static void
+on_test_service_appeared (GDBusConnection *connection,
+                          const gchar *name,
+                          const gchar *name_owner,
+                          gpointer user_data)
+{
+	Test *test = user_data;
+	if (!test->connection)
+		test->connection = g_object_ref (connection);
+	test->service_available = TRUE;
+	egg_test_wait_stop ();
+}
+
+static void
+on_test_service_vanished (GDBusConnection *connection,
+                          const gchar *name,
+                          gpointer user_data)
+{
+	Test *test = user_data;
+	if (test->service_available) {
+		test->service_available = FALSE;
+		egg_test_wait_stop ();
+	}
+}
+
+static void
+on_service_spawned (gpointer user_data)
+{
+	Test *test = user_data;
+	int fd;
+
+	g_setenv ("GNOME_KEYRING_TEST_PATH", test->directory, TRUE);
+	g_setenv ("GNOME_KEYRING_TEST_SERVICE", test->service_name, TRUE);
+	g_setenv ("GNOME_KEYRING_TEST_PROMPTER", test->mock_prompter, TRUE);
+
+	fd = g_open ("/dev/null", O_WRONLY, 0);
+	if (fd != -1)
+		dup2 (fd, 1);
+}
+
+static GVariant *
+build_secret_value (Test *test,
+                     const gchar *value)
+{
+	return g_variant_new ("(o ay@ays)", test->service_session,
+	                      g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, "", 0, 1),
+	                      g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, value, strlen (value), 1),
+	                      "text/plain");
+}
+
+static void
+on_signal_received (GDBusConnection *connection,
+                    const gchar *sender_name,
+                    const gchar *object_path,
+                    const gchar *interface_name,
+                    const gchar *signal_name,
+                    GVariant *parameters,
+                    gpointer user_data)
+{
+	Test *test = user_data;
+	ReceivedSignal *sig;
+
+	g_assert (object_path != NULL);
+	g_assert (interface_name != NULL);
+	g_assert (signal_name != NULL);
+	g_assert (parameters != NULL);
+
+	sig = g_slice_new0 (ReceivedSignal);
+	sig->path = g_strdup (object_path);
+	sig->iface = g_strdup (interface_name);
+	sig->name = g_strdup (signal_name);
+	sig->parameters = g_variant_ref (parameters);
+	test->received_signals = g_list_prepend (test->received_signals, sig);
+}
+
+static void
+received_signal_free (gpointer data)
+{
+	ReceivedSignal *sig = data;
+	g_free (sig->path);
+	g_free (sig->iface);
+	g_free (sig->name);
+	g_variant_unref (sig->parameters);
+	g_slice_free (ReceivedSignal, sig);
+}
+
+static void
+received_signals_flush (Test *test)
+{
+	g_list_free_full (test->received_signals, received_signal_free);
+	test->received_signals = NULL;
+}
+
+static void
+expect_signal_with_path (Test *test,
+                         const gchar *signal_path,
+                         const gchar *signal_iface,
+                         const gchar *signal_name,
+                         const gchar *param_path)
+{
+	ReceivedSignal *sig;
+	const gchar *path;
+	GList *l;
+
+	g_assert (signal_path != NULL);
+	g_assert (signal_iface != NULL);
+	g_assert (signal_name != NULL);
+	g_assert (param_path != NULL);
+
+	for (l = test->received_signals; l != NULL; l = g_list_next (l)) {
+		sig = l->data;
+
+		if (g_str_equal (signal_path, sig->path) &&
+		    g_str_equal (signal_iface, sig->iface) &&
+		    g_str_equal (signal_name, sig->name)) {
+			g_assert (g_variant_is_of_type (sig->parameters, G_VARIANT_TYPE ("(o)")));
+			g_variant_get (sig->parameters, "(&o)", &path);
+			if (!g_str_equal (path, param_path)) {
+				g_critical ("received invalid path from signal %s on interface %s at object %s: "
+				            "expected path %s but got %s",
+				            sig->name, sig->iface, sig->path, param_path, path);
+			}
+
+			return;
+		}
+	}
+
+	g_critical ("didn't receive signal %s on interface %s at object %s",
+	            signal_name, signal_iface, signal_path);
+}
+
+static void
+expect_property_changed (Test *test,
+                         const gchar *signal_path,
+                         const gchar *property_iface,
+                         const gchar *property_name)
+{
+	ReceivedSignal *sig;
+	const gchar *iface;
+	GVariant *properties;
+	GVariant *invalidated;
+	GVariant *value;
+	GList *l;
+
+	g_assert (signal_path != NULL);
+	g_assert (property_iface != NULL);
+	g_assert (property_name != NULL);
+
+	for (l = test->received_signals; l != NULL; l = g_list_next (l)) {
+		sig = l->data;
+
+		if (g_str_equal (signal_path, sig->path) &&
+		    g_str_equal ("org.freedesktop.DBus.Properties", sig->iface) &&
+		    g_str_equal ("PropertiesChanged", sig->name)) {
+			value = NULL;
+			g_assert (g_variant_is_of_type (sig->parameters, G_VARIANT_TYPE ("(sa{sv}as)")));
+
+			g_variant_get (sig->parameters, "(&s a{sv}@as)", &iface, &properties, &invalidated);
+			if (g_str_equal (iface, property_iface)) {
+				value = g_variant_lookup_value (properties, property_name, NULL);
+				g_variant_unref (value);
+			}
+
+			g_variant_unref (properties);
+			g_variant_unref (invalidated);
+
+			if (value != NULL)
+				return;
+		}
+	}
+
+	g_critical ("didn't receive PropertiesChanged for %s property on interface %s at object %s",
+	            property_name, property_iface, signal_path);
+}
+
+static void
+on_complete_get_result (GObject *source,
+                        GAsyncResult *result,
+                        gpointer user_data)
+{
+	GAsyncResult **res = user_data;
+	g_assert (res != NULL);
+	g_assert (*res == NULL);
+	*res = g_object_ref (result);
+	egg_test_wait_stop ();
+}
+
+static GVariant *
+dbus_call_perform (Test *test,
+                   const gchar *object_path,
+                   const gchar *interface,
+                   const gchar *member,
+                   GVariant *parameters,
+                   const GVariantType *restype,
+                   GError **error)
+{
+	GAsyncResult *result = NULL;
+	GVariant *retval;
+
+	/*
+	 * Do an async call with a full main loop, so that the signals
+	 * arrive before the method result.
+	 */
+
+	g_dbus_connection_call (test->connection,
+	                        test->service_name,
+	                        object_path,
+	                        interface,
+	                        member,
+	                        parameters,
+	                        restype,
+	                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+	                        -1, NULL,
+	                        on_complete_get_result,
+	                        &result);
+
+	g_assert (result == NULL);
+	egg_test_wait ();
+	g_assert (result != NULL);
+
+	retval = g_dbus_connection_call_finish (test->connection,
+	                                        result, error);
+	g_object_unref (result);
+
+	return retval;
+}
+
+static void
+setup (Test *test,
+       gconstpointer unused)
+{
+	GError *error = NULL;
+	GVariant *retval;
+	GVariant *output;
+
+	gchar *args[] = {
+		TOP_SRCDIR "/daemon/gnome-keyring-daemon",
+		"--foreground",
+		"--control-directory",
+		"/tmp/keyring-test",
+		"--components",
+		"secrets",
+		NULL,
+	};
+
+	test->service_name = g_strdup_printf ("org.gnome.keyring.Test.t%d", getpid ());
+
+	test->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, test->service_name,
+	                                   G_BUS_NAME_WATCHER_FLAGS_NONE,
+	                                   on_test_service_appeared,
+	                                   on_test_service_vanished,
+	                                   test, NULL);
+
+	test->mock_prompter = gcr_mock_prompter_start ();
+	g_assert (test->mock_prompter != NULL);
+
+	test->directory = egg_tests_create_scratch_directory (
+		SRCDIR "/files/test.keyring",
+		NULL);
+
+	if (!g_spawn_async (NULL, args, NULL,
+	                    G_SPAWN_LEAVE_DESCRIPTORS_OPEN | G_SPAWN_DO_NOT_REAP_CHILD,
+	                    on_service_spawned, test, &test->service_pid, &error)) {
+		g_error ("couldn't start gnome-keyring-daemon for testing: %s", error->message);
+		g_assert_not_reached ();
+	}
+
+	if (!test->service_available) {
+		egg_test_wait ();
+
+		if (!test->service_available) {
+			g_warning ("Couldn't start gnome-keyring-daemon test service. ");
+			g_assert_not_reached ();
+		}
+	}
+
+	/* Set by on_test_service_appeared */
+	g_assert (test->connection != NULL);
+
+	/* Establish a plain session with the daemon */
+	retval = g_dbus_connection_call_sync (test->connection,
+	                                      test->service_name,
+	                                      SECRET_SERVICE_PATH,
+	                                      SECRET_SERVICE_INTERFACE,
+	                                      "OpenSession",
+	                                      g_variant_new ("(s v)", "plain",
+	                                                     g_variant_new_variant (g_variant_new_string (""))),
+	                                      G_VARIANT_TYPE ("(vo)"),
+	                                      G_DBUS_CALL_FLAGS_NO_AUTO_START,
+	                                      -1, NULL, &error);
+	g_assert_no_error (error);
+
+	g_variant_get (retval, "(@vo)", &output, &test->service_session);
+	g_variant_unref (output);
+	g_variant_unref (retval);
+
+	/* Unlock the test collection */
+	retval = g_dbus_connection_call_sync (test->connection,
+	                                      test->service_name,
+	                                      SECRET_SERVICE_PATH,
+	                                      INTERNAL_SERVICE_INTERFACE,
+	                                      "UnlockWithMasterPassword",
+	                                      g_variant_new ("(o@(oayays))",
+	                                                     "/org/freedesktop/secrets/collection/test",
+	                                                     build_secret_value (test, "booo")),
+	                                      G_VARIANT_TYPE ("()"),
+	                                      G_DBUS_CALL_FLAGS_NO_AUTO_START,
+	                                      -1, NULL, &error);
+	g_assert_no_error (error);
+	g_variant_unref (retval);
+
+	/* Wait for the prompt's completed signal */
+	test->signal_id = g_dbus_connection_signal_subscribe (test->connection,
+	                                                      test->service_name,
+	                                                      NULL, NULL, NULL, NULL,
+	                                                      G_DBUS_SIGNAL_FLAGS_NONE,
+	                                                      on_signal_received,
+	                                                      test, NULL);
+
+	received_signals_flush (test);
+
+}
+
+static void
+setup_locked (Test *test,
+              gconstpointer unused)
+{
+	GVariant *element;
+	GVariant *retval;
+	GError *error = NULL;
+	const gchar *prompt;
+	GVariant *locked;
+
+	/* Main setup */
+	setup (test, unused);
+
+	element = g_variant_new_object_path ("/org/freedesktop/secrets/collection/test");
+	retval = dbus_call_perform (test,
+	                            SECRET_SERVICE_PATH,
+	                            SECRET_SERVICE_INTERFACE,
+	                            "Lock",
+	                            g_variant_new ("(@ao)",
+	                                           g_variant_new_array (G_VARIANT_TYPE ("o"), &element, 1)),
+	                            G_VARIANT_TYPE ("(aoo)"),
+	                            &error);
+	g_assert_no_error (error);
+
+	/* Not expecting a prompt */
+	g_variant_get (retval, "(@ao&o)", &locked, &prompt);
+	g_assert_cmpstr (prompt, ==, "/");
+	g_variant_unref (locked);
+	g_variant_unref (retval);
+
+	/* Don't carry over any received signals into test */
+	received_signals_flush (test);
+}
+
+static void
+teardown (Test *test,
+          gconstpointer unused)
+{
+	received_signals_flush (test);
+
+	g_dbus_connection_signal_unsubscribe (test->connection, test->signal_id);
+
+	if (test->service_pid)
+		kill (test->service_pid, SIGTERM);
+
+	if (test->service_available) {
+		egg_test_wait ();
+		if (test->service_available) {
+			g_warning ("Couldn't stop gnome-keyring-daemon test service.");
+			g_assert_not_reached ();
+		}
+	}
+
+	if (test->watch_id)
+		g_bus_unwatch_name (test->watch_id);
+
+	g_free (test->service_name);
+	g_free (test->service_session);
+
+	if (test->connection)
+		g_object_unref (test->connection);
+
+	gcr_mock_prompter_stop ();
+
+	egg_tests_remove_scratch_directory (test->directory);
+	g_free (test->directory);
+}
+
+
+static void
+on_prompt_completed (GDBusConnection *connection,
+                     const gchar *sender_name,
+                     const gchar *object_path,
+                     const gchar *interface_name,
+                     const gchar *signal_name,
+                     GVariant *parameters,
+                     gpointer user_data)
+{
+	GVariant **prompt_result = user_data;
+	gboolean dismissed;
+	GVariant *result;
+
+	g_assert (prompt_result != NULL);
+	g_assert (*prompt_result == NULL);
+
+	g_assert (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(bv)")));
+	g_variant_get (parameters, "(b v)", &dismissed, &result);
+
+	if (dismissed)
+		*prompt_result = NULL;
+	else
+		*prompt_result = g_variant_ref (result);
+	g_variant_unref (result);
+
+	egg_test_wait_stop ();
+}
+
+static GVariant *
+prompt_password_perform (Test *test,
+                         const gchar *prompt_path,
+                         const gchar *password,
+                         const GVariantType *type)
+{
+	GVariant *prompt_result = NULL;
+	GError *error = NULL;
+	GVariant *inside;
+	GVariant *retval;
+	guint sig;
+
+	/* Tell the mock prompter which password to use */
+	gcr_mock_prompter_expect_password_ok (password, NULL);
+
+	/* Wait for the prompt's completed signal */
+	sig = g_dbus_connection_signal_subscribe (test->connection,
+	                                          test->service_name,
+	                                          SECRET_PROMPT_INTERFACE,
+	                                          "Completed",
+	                                          prompt_path,
+	                                          NULL,
+	                                          G_DBUS_SIGNAL_FLAGS_NONE,
+	                                          on_prompt_completed,
+	                                          &prompt_result,
+	                                          NULL);
+
+	/* Perform the prompt, this will use the mock prompter */
+	retval = g_dbus_connection_call_sync (test->connection,
+	                                      test->service_name,
+	                                      prompt_path,
+	                                      SECRET_PROMPT_INTERFACE,
+	                                      "Prompt",
+	                                      g_variant_new ("(s)", ""),
+	                                      G_VARIANT_TYPE ("()"),
+	                                      G_DBUS_CALL_FLAGS_NONE,
+	                                      -1, NULL, &error);
+	g_assert_no_error (error);
+	g_variant_unref (retval);
+
+	egg_test_wait ();
+
+	/* Done, now stop waiting for the prompts signal, make sure mock was used */
+	g_dbus_connection_signal_unsubscribe (test->connection, sig);
+	g_assert (!gcr_mock_prompter_is_expecting ());
+
+	/* Check prompt result for right type */
+	g_assert (prompt_result != NULL);
+	inside = g_variant_get_variant (prompt_result);
+	g_assert (g_variant_is_of_type (inside, type));
+	g_variant_unref (prompt_result);
+
+	return inside;
+}
+
+static void
+test_collection_created (Test *test,
+                         gconstpointer unused)
+{
+	const gchar *collection;
+	GError *error = NULL;
+	const gchar *prompt;
+	GVariant *properties;
+	GVariant *retval;
+	GVariant *result;
+	GVariant *label;
+
+	/* Create a new collection */
+	label = g_variant_new_dict_entry (g_variant_new_string ("org.freedesktop.Secret.Collection.Label"),
+	                                  g_variant_new_variant (g_variant_new_string ("My Collection")));
+	properties = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), &label, 1);
+
+	retval = dbus_call_perform (test,
+	                            SECRET_SERVICE_PATH,
+	                            SECRET_SERVICE_INTERFACE,
+	                            "CreateCollection",
+	                            g_variant_new ("(@a{sv}s)", properties, ""),
+	                            G_VARIANT_TYPE ("(oo)"),
+	                            &error);
+	g_assert_no_error (error);
+
+	/* We expect that a prompt is necessary */
+	g_variant_get (retval, "(&o&o)", &collection, &prompt);
+	g_assert_cmpstr (collection, ==, "/");
+	g_assert_cmpstr (prompt, !=, "/");
+
+	/*
+	 * Perform the password prompt to create the collection, which returns
+	 * the new collection path
+	 */
+	result = prompt_password_perform (test, prompt, "booo", G_VARIANT_TYPE_OBJECT_PATH);
+	g_variant_unref (retval);
+
+	expect_signal_with_path (test, SECRET_SERVICE_PATH, SECRET_SERVICE_INTERFACE,
+	                         "CollectionCreated", g_variant_get_string (result, NULL));
+	expect_property_changed (test, SECRET_SERVICE_PATH,
+	                         SECRET_SERVICE_INTERFACE, "Collections");
+
+	g_variant_unref (result);
+}
+
+static void
+test_collection_created_no_prompt (Test *test,
+                                   gconstpointer unused)
+{
+	const gchar *collection;
+	GError *error = NULL;
+	GVariant *properties;
+	GVariant *retval;
+	GVariant *label;
+
+	/* Create a new collection */
+	label = g_variant_new_dict_entry (g_variant_new_string ("org.freedesktop.Secret.Collection.Label"),
+	                                  g_variant_new_variant (g_variant_new_string ("Without Prompt")));
+	properties = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), &label, 1);
+
+	retval = dbus_call_perform (test,
+	                            SECRET_SERVICE_PATH,
+	                            INTERNAL_SERVICE_INTERFACE,
+	                            "CreateWithMasterPassword",
+	                            g_variant_new ("(@a{sv}@(oayays))",
+	                                           properties,
+	                                           build_secret_value (test, "booo")),
+	                            G_VARIANT_TYPE ("(o)"),
+	                            &error);
+	g_assert_no_error (error);
+
+	g_variant_get (retval, "(&o)", &collection);
+	g_assert_cmpstr (collection, !=, "/");
+
+	expect_signal_with_path (test, SECRET_SERVICE_PATH, SECRET_SERVICE_INTERFACE,
+	                         "CollectionCreated", collection);
+	expect_property_changed (test, SECRET_SERVICE_PATH,
+	                         SECRET_SERVICE_INTERFACE, "Collections");
+
+	g_variant_unref (retval);
+}
+
+static void
+test_collection_deleted (Test *test,
+                         gconstpointer unused)
+{
+	const gchar *prompt;
+	GError *error = NULL;
+	GVariant *retval;
+
+	/* Delete a collection */
+	retval = dbus_call_perform (test,
+	                            "/org/freedesktop/secrets/collection/test",
+	                            SECRET_COLLECTION_INTERFACE,
+	                            "Delete",
+	                            g_variant_new ("()"),
+	                            G_VARIANT_TYPE ("(o)"),
+	                            &error);
+	g_assert_no_error (error);
+
+	/* Expect that no prompt is returned */
+	g_variant_get (retval, "(&o)", &prompt);
+	g_assert_cmpstr (prompt, ==, "/");
+
+	expect_signal_with_path (test, SECRET_SERVICE_PATH, SECRET_SERVICE_INTERFACE,
+	                         "CollectionDeleted", "/org/freedesktop/secrets/collection/test");
+	expect_property_changed (test, SECRET_SERVICE_PATH,
+	                         SECRET_SERVICE_INTERFACE, "Collections");
+
+	g_variant_unref (retval);
+}
+
+static void
+test_collection_changed (Test *test,
+                         gconstpointer unused)
+{
+	GError *error = NULL;
+	GVariant *retval;
+
+	retval = dbus_call_perform (test,
+	                            "/org/freedesktop/secrets/collection/test",
+	                            "org.freedesktop.DBus.Properties",
+	                            "Set",
+	                            g_variant_new ("(ssv)",
+	                                           SECRET_COLLECTION_INTERFACE,
+	                                           "Label",
+	                                           g_variant_new_string ("New label")),
+	                            G_VARIANT_TYPE ("()"),
+	                            &error);
+	g_assert_no_error (error);
+
+	expect_signal_with_path (test, SECRET_SERVICE_PATH,
+	                         SECRET_SERVICE_INTERFACE, "CollectionChanged",
+	                         "/org/freedesktop/secrets/collection/test");
+	expect_property_changed (test, "/org/freedesktop/secrets/collection/test",
+	                         SECRET_COLLECTION_INTERFACE, "Label");
+
+	g_variant_unref (retval);
+}
+
+static void
+test_collection_lock (Test *test,
+                      gconstpointer unused)
+{
+	GError *error = NULL;
+	const gchar *prompt;
+	GVariant *element;
+	GVariant *locked;
+	GVariant *retval;
+
+	element = g_variant_new_object_path ("/org/freedesktop/secrets/collection/test");
+	retval = dbus_call_perform (test,
+	                            SECRET_SERVICE_PATH,
+	                            SECRET_SERVICE_INTERFACE,
+	                            "Lock",
+	                            g_variant_new ("(@ao)",
+	                                           g_variant_new_array (G_VARIANT_TYPE ("o"), &element, 1)),
+	                            G_VARIANT_TYPE ("(aoo)"),
+	                            &error);
+	g_assert_no_error (error);
+
+	/* Not expecting a prompt */
+	g_variant_get (retval, "(@ao&o)", &locked, &prompt);
+	g_assert_cmpstr (prompt, ==, "/");
+	g_variant_unref (locked);
+
+	expect_signal_with_path (test, SECRET_SERVICE_PATH,
+	                         SECRET_SERVICE_INTERFACE, "CollectionChanged",
+	                         "/org/freedesktop/secrets/collection/test");
+	expect_signal_with_path (test, "/org/freedesktop/secrets/collection/test",
+	                         SECRET_COLLECTION_INTERFACE, "ItemChanged",
+	                         "/org/freedesktop/secrets/collection/test/1");
+	expect_property_changed (test, "/org/freedesktop/secrets/collection/test",
+	                         SECRET_COLLECTION_INTERFACE, "Locked");
+	expect_property_changed (test, "/org/freedesktop/secrets/collection/test/1",
+	                         SECRET_ITEM_INTERFACE, "Locked");
+
+	g_variant_unref (retval);
+}
+
+static void
+test_collection_unlock (Test *test,
+                        gconstpointer unused)
+{
+	GError *error = NULL;
+	const gchar *prompt;
+	GVariant *unlocked;
+	GVariant *retval;
+	GVariant *element;
+
+	element = g_variant_new_object_path ("/org/freedesktop/secrets/collection/test");
+	retval = dbus_call_perform (test,
+	                            SECRET_SERVICE_PATH,
+	                            SECRET_SERVICE_INTERFACE,
+	                            "Unlock",
+	                            g_variant_new ("(@ao)",
+	                                           g_variant_new_array (G_VARIANT_TYPE ("o"), &element, 1)),
+	                            G_VARIANT_TYPE ("(aoo)"),
+	                            &error);
+	g_assert_no_error (error);
+
+	/* Not expecting a prompt */
+	g_variant_get (retval, "(@ao&o)", &unlocked, &prompt);
+	g_assert_cmpstr (prompt, !=, "/");
+	g_variant_unref (unlocked);
+
+	unlocked = prompt_password_perform (test, prompt, "booo", G_VARIANT_TYPE ("ao"));
+	g_variant_unref (unlocked);
+
+	expect_signal_with_path (test, SECRET_SERVICE_PATH,
+	                         SECRET_SERVICE_INTERFACE, "CollectionChanged",
+	                         "/org/freedesktop/secrets/collection/test");
+	expect_signal_with_path (test, "/org/freedesktop/secrets/collection/test",
+	                         SECRET_COLLECTION_INTERFACE, "ItemChanged",
+	                         "/org/freedesktop/secrets/collection/test/1");
+	expect_property_changed (test, "/org/freedesktop/secrets/collection/test",
+	                         SECRET_COLLECTION_INTERFACE, "Locked");
+	expect_property_changed (test, "/org/freedesktop/secrets/collection/test/1",
+	                         SECRET_ITEM_INTERFACE, "Locked");
+
+	g_variant_unref (retval);
+}
+
+static void
+test_collection_unlock_no_prompt (Test *test,
+                                  gconstpointer unused)
+{
+	GError *error = NULL;
+	GVariant *retval;
+
+	retval = dbus_call_perform (test,
+	                            SECRET_SERVICE_PATH,
+	                            INTERNAL_SERVICE_INTERFACE,
+	                            "UnlockWithMasterPassword",
+	                            g_variant_new ("(o@(oayays))",
+	                                           "/org/freedesktop/secrets/collection/test",
+	                                           build_secret_value (test, "booo")),
+	                            G_VARIANT_TYPE ("()"),
+	                            &error);
+	g_assert_no_error (error);
+
+	expect_signal_with_path (test, SECRET_SERVICE_PATH,
+	                         SECRET_SERVICE_INTERFACE, "CollectionChanged",
+	                         "/org/freedesktop/secrets/collection/test");
+	expect_signal_with_path (test, "/org/freedesktop/secrets/collection/test",
+	                         SECRET_COLLECTION_INTERFACE, "ItemChanged",
+	                         "/org/freedesktop/secrets/collection/test/1");
+	expect_property_changed (test, "/org/freedesktop/secrets/collection/test",
+	                         SECRET_COLLECTION_INTERFACE, "Locked");
+	expect_property_changed (test, "/org/freedesktop/secrets/collection/test/1",
+	                         SECRET_ITEM_INTERFACE, "Locked");
+
+	g_variant_unref (retval);
+}
+
+static void
+test_item_created (Test *test,
+                   gconstpointer unused)
+{
+	const gchar *item;
+	const gchar *prompt;
+	GError *error = NULL;
+	GVariant *properties;
+	GVariant *retval;
+	GVariant *label;
+
+	/* Create a new collection */
+	label = g_variant_new_dict_entry (g_variant_new_string ("org.freedesktop.Secret.Item.Label"),
+	                                  g_variant_new_variant (g_variant_new_string ("My Item")));
+	properties = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), &label, 1);
+
+	retval = dbus_call_perform (test,
+	                            "/org/freedesktop/secrets/collection/test",
+	                            SECRET_COLLECTION_INTERFACE,
+	                            "CreateItem",
+	                            g_variant_new ("(@a{sv}@(oayays)b)",
+	                                           properties,
+	                                           build_secret_value (test, "booo"),
+	                                           FALSE),
+	                            G_VARIANT_TYPE ("(oo)"),
+	                            &error);
+	g_assert_no_error (error);
+
+	/* Not expecting a prompt */
+	g_variant_get (retval, "(&o&o)", &item, &prompt);
+	g_assert_cmpstr (item, !=, "/");
+	g_assert_cmpstr (prompt, ==, "/");
+
+	expect_signal_with_path (test, "/org/freedesktop/secrets/collection/test",
+	                         SECRET_COLLECTION_INTERFACE, "ItemCreated", item);
+	expect_property_changed (test, "/org/freedesktop/secrets/collection/test",
+	                         SECRET_COLLECTION_INTERFACE, "Items");
+
+	g_variant_unref (retval);
+}
+
+static void
+test_item_deleted (Test *test,
+                   gconstpointer unused)
+{
+	const gchar *prompt;
+	GError *error = NULL;
+	GVariant *retval;
+
+	retval = dbus_call_perform (test,
+	                            "/org/freedesktop/secrets/collection/test/1",
+	                            SECRET_ITEM_INTERFACE,
+	                            "Delete",
+	                            g_variant_new ("()"),
+	                            G_VARIANT_TYPE ("(o)"),
+	                            &error);
+	g_assert_no_error (error);
+
+	/* Not expecting a prompt */
+	g_variant_get (retval, "(&o)", &prompt);
+	g_assert_cmpstr (prompt, ==, "/");
+
+	expect_signal_with_path (test, "/org/freedesktop/secrets/collection/test",
+	                         SECRET_COLLECTION_INTERFACE, "ItemDeleted",
+	                         "/org/freedesktop/secrets/collection/test/1");
+	expect_property_changed (test, "/org/freedesktop/secrets/collection/test",
+	                         SECRET_COLLECTION_INTERFACE, "Items");
+
+	g_variant_unref (retval);
+}
+
+static void
+test_item_changed (Test *test,
+                   gconstpointer unused)
+{
+	GError *error = NULL;
+	GVariant *retval;
+
+	retval = dbus_call_perform (test,
+	                            "/org/freedesktop/secrets/collection/test/1",
+	                            "org.freedesktop.DBus.Properties",
+	                            "Set",
+	                            g_variant_new ("(ssv)",
+	                                           SECRET_ITEM_INTERFACE,
+	                                           "Label",
+	                                           g_variant_new_string ("New label")),
+	                            G_VARIANT_TYPE ("()"),
+	                            &error);
+	g_assert_no_error (error);
+
+	expect_signal_with_path (test, "/org/freedesktop/secrets/collection/test",
+	                         SECRET_COLLECTION_INTERFACE, "ItemChanged",
+	                         "/org/freedesktop/secrets/collection/test/1");
+	expect_property_changed (test, "/org/freedesktop/secrets/collection/test/1",
+	                         SECRET_ITEM_INTERFACE, "Label");
+
+	g_variant_unref (retval);
+}
+
+int
+main (int argc, char **argv)
+{
+	g_type_init ();
+	g_test_init (&argc, &argv, NULL);
+
+	g_test_add ("/secret-signals/collection-created", Test, NULL,
+	            setup, test_collection_created, teardown);
+	g_test_add ("/secret-signals/collection-created-no-prompt", Test, NULL,
+	            setup, test_collection_created_no_prompt, teardown);
+	g_test_add ("/secret-signals/collection-changed", Test, NULL,
+	            setup, test_collection_changed, teardown);
+	g_test_add ("/secret-signals/collection-deleted", Test, NULL,
+	            setup, test_collection_deleted, teardown);
+	g_test_add ("/secret-signals/collection-lock", Test, NULL,
+	            setup, test_collection_lock, teardown);
+	g_test_add ("/secret-signals/collection-unlock", Test, NULL,
+	            setup_locked, test_collection_unlock, teardown);
+	g_test_add ("/secret-signals/collection-unlock-no-prompt", Test, NULL,
+	            setup_locked, test_collection_unlock_no_prompt, teardown);
+	g_test_add ("/secret-signals/item-created", Test, NULL,
+	            setup, test_item_created, teardown);
+	g_test_add ("/secret-signals/item-changed", Test, NULL,
+	            setup, test_item_changed, teardown);
+	g_test_add ("/secret-signals/item-deleted", Test, NULL,
+	            setup, test_item_deleted, teardown);
+
+	return egg_tests_run_with_loop ();
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]