From 9c2eca05ff0432ddc9267b693c91d690e7285095 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Mon, 9 Jul 2012 08:46:11 +0200 Subject: [PATCH] Implement per collection search * Use the SearchItems dbus method on the Collection interface * Tweak some issues with the service search code as well --- .../libsecret/libsecret-sections.txt | 6 + library/secret-collection.c | 381 ++++++++++++++++++ library/secret-collection.h | 20 + library/secret-methods.c | 60 +-- library/secret-paths.c | 150 ++++++- library/secret-paths.h | 16 + library/secret-service.h | 7 - library/secret-types.h | 7 + library/tests/mock/service.py | 5 + library/tests/test-collection.c | 348 ++++++++++++++++ 10 files changed, 964 insertions(+), 36 deletions(-) diff --git a/docs/reference/libsecret/libsecret-sections.txt b/docs/reference/libsecret/libsecret-sections.txt index f9bc9f5..c134423 100644 --- a/docs/reference/libsecret/libsecret-sections.txt +++ b/docs/reference/libsecret/libsecret-sections.txt @@ -10,6 +10,9 @@ secret_collection_load_items_sync secret_collection_create secret_collection_create_finish secret_collection_create_sync +secret_collection_search +secret_collection_search_finish +secret_collection_search_sync secret_collection_delete secret_collection_delete_finish secret_collection_delete_sync @@ -244,6 +247,9 @@ secret_service_get_session_dbus_path secret_service_search_for_dbus_paths secret_service_search_for_dbus_paths_finish secret_service_search_for_dbus_paths_sync +secret_collection_search_for_dbus_paths +secret_collection_search_for_dbus_paths_finish +secret_collection_search_for_dbus_paths_sync secret_service_get_secrets_for_dbus_paths secret_service_get_secrets_for_dbus_paths_finish secret_service_get_secrets_for_dbus_paths_sync diff --git a/library/secret-collection.c b/library/secret-collection.c index 057547f..4b1932b 100644 --- a/library/secret-collection.c +++ b/library/secret-collection.c @@ -1204,6 +1204,387 @@ secret_collection_create_sync (SecretService *service, return collection; } +typedef struct { + SecretCollection *collection; + GCancellable *cancellable; + GHashTable *items; + gchar **paths; + guint loading; + SecretSearchFlags flags; +} SearchClosure; + +static void +search_closure_free (gpointer data) +{ + SearchClosure *closure = data; + g_object_unref (closure->collection); + g_clear_object (&closure->cancellable); + g_hash_table_unref (closure->items); + g_strfreev (closure->paths); + g_slice_free (SearchClosure, closure); +} + +static void +search_closure_take_item (SearchClosure *closure, + SecretItem *item) +{ + const gchar *path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (item)); + g_hash_table_insert (closure->items, (gpointer)path, item); +} + +static void +on_search_secrets (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data); + + /* Note that we ignore any unlock failure */ + secret_item_load_secrets_finish (result, NULL); + + g_simple_async_result_complete (async); + g_object_unref (async); +} + +static void +on_search_unlocked (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data); + SearchClosure *search = g_simple_async_result_get_op_res_gpointer (async); + GList *items; + + /* Note that we ignore any unlock failure */ + secret_service_unlock_finish (SECRET_SERVICE (source), result, NULL, NULL); + + /* If loading secrets ... locked items automatically ignored */ + if (search->flags & SECRET_SEARCH_LOAD_SECRETS) { + items = g_hash_table_get_values (search->items); + secret_item_load_secrets (items, search->cancellable, + on_search_secrets, g_object_ref (async)); + g_list_free (items); + + /* No additional options, just complete */ + } else { + g_simple_async_result_complete (async); + } + + g_object_unref (async); +} + +static void +secret_search_unlock_load_or_complete (GSimpleAsyncResult *async, + SearchClosure *search) +{ + GList *items; + + /* If unlocking then unlock all the locked items */ + if (search->flags & SECRET_SEARCH_UNLOCK) { + items = g_hash_table_get_values (search->items); + secret_service_unlock (secret_collection_get_service (search->collection), + items, search->cancellable, + on_search_unlocked, g_object_ref (async)); + g_list_free (items); + + /* If loading secrets ... locked items automatically ignored */ + } else if (search->flags & SECRET_SEARCH_LOAD_SECRETS) { + items = g_hash_table_get_values (search->items); + secret_item_load_secrets (items, search->cancellable, + on_search_secrets, g_object_ref (async)); + g_list_free (items); + + /* No additional options, just complete */ + } else { + g_simple_async_result_complete (async); + } +} + +static void +on_search_loaded (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data); + SearchClosure *search = g_simple_async_result_get_op_res_gpointer (async); + GError *error = NULL; + SecretItem *item; + + search->loading--; + + item = secret_item_new_for_dbus_path_finish (result, &error); + if (error != NULL) + g_simple_async_result_take_error (async, error); + + if (item != NULL) + search_closure_take_item (search, item); + + /* We're done loading, lets go to the next step */ + if (search->loading == 0) + secret_search_unlock_load_or_complete (async, search); + + g_object_unref (async); +} + +static void +on_search_paths (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data); + SearchClosure *search = g_simple_async_result_get_op_res_gpointer (async); + SecretCollection *self = search->collection; + SecretService *service = secret_collection_get_service (self); + GError *error = NULL; + SecretItem *item; + gint want = 1; + gint i; + + search->paths = secret_collection_search_for_dbus_paths_finish (self, result, &error); + if (error == NULL) { + want = 1; + if (search->flags & SECRET_SEARCH_ALL) + want = G_MAXINT; + + for (i = 0; i < want && search->paths[i] != NULL; i++) { + item = _secret_collection_find_item_instance (self, search->paths[i]); + if (item == NULL) { + secret_item_new_for_dbus_path (service, search->paths[i], SECRET_ITEM_NONE, + search->cancellable, on_search_loaded, + g_object_ref (async)); + search->loading++; + } else { + search_closure_take_item (search, item); + } + + } + + /* No items loading, complete operation now */ + if (search->loading == 0) + secret_search_unlock_load_or_complete (async, search); + + } else { + g_simple_async_result_take_error (async, error); + g_simple_async_result_complete (async); + } + + g_object_unref (async); +} + +/** + * secret_collection_search: + * @self: a secret collection + * @schema: (allow-none): the schema for the attributes + * @attributes: (element-type utf8 utf8): search for items matching these attributes + * @flags: search option flags + * @cancellable: optional cancellation object + * @callback: called when the operation completes + * @user_data: data to pass to the callback + * + * Search for items matching the @attributes in the @collection. + * The @attributes should be a table of string keys and string values. + * + * If %SECRET_SEARCH_ALL is set in @flags, then all the items matching the + * search will be returned. Otherwise only the first item will be returned. + * This is almost always the unlocked item that was most recently stored. + * + * If %SECRET_SEARCH_UNLOCK is set in @flags, then items will be unlocked + * if necessary. In either case, locked and unlocked items will match the + * search and be returned. If the unlock fails, the search does not fail. + * + * If %SECRET_SEARCH_LOAD_SECRETS is set in @flags, then the items will have + * their secret values loaded and available via secret_item_get_secret(). + * + * This function returns immediately and completes asynchronously. + */ +void +secret_collection_search (SecretCollection *self, + const SecretSchema *schema, + GHashTable *attributes, + SecretSearchFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *async; + SearchClosure *search; + + g_return_if_fail (SECRET_IS_COLLECTION (self)); + g_return_if_fail (attributes != NULL); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + /* Warnings raised already */ + if (schema != NULL && !_secret_attributes_validate (schema, attributes)) + return; + + async = g_simple_async_result_new (G_OBJECT (self), callback, user_data, + secret_collection_search); + search = g_slice_new0 (SearchClosure); + search->collection = g_object_ref (self); + search->cancellable = cancellable ? g_object_ref (cancellable) : NULL; + search->items = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref); + search->flags = flags; + g_simple_async_result_set_op_res_gpointer (async, search, search_closure_free); + + secret_collection_search_for_dbus_paths (self, schema, attributes, + cancellable, on_search_paths, + g_object_ref (async)); + + g_object_unref (async); +} + +/** + * secret_collection_search_finish: + * @self: the secret collection + * @result: asynchronous result passed to callback + * @error: location to place error on failure + * + * Complete asynchronous operation to search for items in a collection. + * + * Returns: (transfer full) (element-type Secret.Item): + * a list of items that matched the search + */ +GList * +secret_collection_search_finish (SecretCollection *self, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *async; + SearchClosure *search; + GList *items = NULL; + SecretItem *item; + guint i; + + g_return_val_if_fail (SECRET_IS_COLLECTION (self), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), + secret_collection_search), NULL); + + async = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (async, error)) + return NULL; + + search = g_simple_async_result_get_op_res_gpointer (async); + + for (i = 0; search->paths[i]; i++) { + item = g_hash_table_lookup (search->items, search->paths[i]); + if (item != NULL) + items = g_list_prepend (items, g_object_ref (item)); + } + + return g_list_reverse (items); +} + +static gboolean +collection_load_items_sync (SecretCollection *self, + GCancellable *cancellable, + gchar **paths, + GList **items, + gint want, + GError **error) +{ + SecretService *service = secret_collection_get_service (self); + SecretItem *item; + gint have = 0; + guint i; + + for (i = 0; have < want && paths[i] != NULL; i++) { + item = _secret_collection_find_item_instance (self, paths[i]); + if (item == NULL) + item = secret_item_new_for_dbus_path_sync (service, paths[i], SECRET_ITEM_NONE, + cancellable, error); + if (item == NULL) { + return FALSE; + + } else { + *items = g_list_prepend (*items, item); + have++; + } + } + + return TRUE; +} + +/** + * secret_collection_search_sync: + * @self: a secret collection + * @schema: (allow-none): the schema for the attributes + * @attributes: (element-type utf8 utf8): search for items matching these attributes + * @flags: search option flags + * @cancellable: optional cancellation object + * @error: location to place error on failure + * + * Search for items matching the @attributes in the @collection. + * The @attributes should be a table of string keys and string values. + * + * If %SECRET_SEARCH_ALL is set in @flags, then all the items matching the + * search will be returned. Otherwise only the first item will be returned. + * This is almost always the unlocked item that was most recently stored. + * + * If %SECRET_SEARCH_UNLOCK is set in @flags, then items will be unlocked + * if necessary. In either case, locked and unlocked items will match the + * search and be returned. If the unlock fails, the search does not fail. + * + * If %SECRET_SEARCH_LOAD_SECRETS is set in @flags, then the items will have + * their secret values loaded and available via secret_item_get_secret(). + * + * This function may block indefinetely. Use the asynchronous version + * in user interface threads. + * + * Returns: (transfer full) (element-type Secret.Item): + * a list of items that matched the search + */ +GList * +secret_collection_search_sync (SecretCollection *self, + const SecretSchema *schema, + GHashTable *attributes, + SecretSearchFlags flags, + GCancellable *cancellable, + GError **error) +{ + gchar **paths = NULL; + GList *items = NULL; + gboolean ret; + gint want; + + g_return_val_if_fail (SECRET_IS_COLLECTION (self), NULL); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + /* Warnings raised already */ + if (schema != NULL && !_secret_attributes_validate (schema, attributes)) + return NULL; + + paths = secret_collection_search_for_dbus_paths_sync (self, schema, attributes, + cancellable, error); + if (paths == NULL) + return NULL; + + ret = TRUE; + + want = 1; + if (flags & SECRET_SEARCH_ALL) + want = G_MAXINT; + + ret = collection_load_items_sync (self, cancellable, paths, + &items, want, error); + + g_strfreev (paths); + + if (!ret) + return NULL; + + if (flags & SECRET_SEARCH_UNLOCK) { + secret_service_unlock_sync (secret_collection_get_service (self), + items, cancellable, NULL, NULL); + } + + if (flags & SECRET_SEARCH_LOAD_SECRETS) + secret_item_load_secrets_sync (items, NULL, NULL); + + return items; +} + static void on_service_delete_path (GObject *source, GAsyncResult *result, diff --git a/library/secret-collection.h b/library/secret-collection.h index ca9c15b..2b58f39 100644 --- a/library/secret-collection.h +++ b/library/secret-collection.h @@ -21,6 +21,7 @@ #include +#include "secret-schema.h" #include "secret-types.h" G_BEGIN_DECLS @@ -87,6 +88,25 @@ SecretCollection * secret_collection_create_sync (SecretService *s GCancellable *cancellable, GError **error); +void secret_collection_search (SecretCollection *self, + const SecretSchema *schema, + GHashTable *attributes, + SecretSearchFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GList * secret_collection_search_finish (SecretCollection *self, + GAsyncResult *result, + GError **error); + +GList * secret_collection_search_sync (SecretCollection *self, + const SecretSchema *schema, + GHashTable *attributes, + SecretSearchFlags flags, + GCancellable *cancellable, + GError **error); + void secret_collection_delete (SecretCollection *self, GCancellable *cancellable, GAsyncReadyCallback callback, diff --git a/library/secret-methods.c b/library/secret-methods.c index e989908..13af30c 100644 --- a/library/secret-methods.c +++ b/library/secret-methods.c @@ -124,6 +124,32 @@ on_search_unlocked (GObject *source, g_object_unref (async); } +static void +secret_search_unlock_load_or_complete (GSimpleAsyncResult *async, + SearchClosure *search) +{ + GList *items; + + /* If unlocking then unlock all the locked items */ + if (search->flags & SECRET_SEARCH_UNLOCK) { + items = search_closure_build_items (search, search->locked); + secret_service_unlock (search->service, items, search->cancellable, + on_search_unlocked, g_object_ref (async)); + g_list_free_full (items, g_object_unref); + + /* If loading secrets ... locked items automatically ignored */ + } else if (search->flags & SECRET_SEARCH_LOAD_SECRETS) { + items = g_hash_table_get_values (search->items); + secret_item_load_secrets (items, search->cancellable, + on_search_secrets, g_object_ref (async)); + g_list_free (items); + + /* No additional options, just complete */ + } else { + g_simple_async_result_complete (async); + } +} + static void on_search_loaded (GObject *source, GAsyncResult *result, @@ -133,7 +159,6 @@ on_search_loaded (GObject *source, SearchClosure *closure = g_simple_async_result_get_op_res_gpointer (res); GError *error = NULL; SecretItem *item; - GList *items; closure->loading--; @@ -145,27 +170,8 @@ on_search_loaded (GObject *source, search_closure_take_item (closure, item); /* We're done loading, lets go to the next step */ - if (closure->loading == 0) { - - /* If unlocking then unlock all the locked items */ - if (closure->flags & SECRET_SEARCH_UNLOCK) { - items = search_closure_build_items (closure, closure->locked); - secret_service_unlock (closure->service, items, closure->cancellable, - on_search_unlocked, g_object_ref (res)); - g_list_free_full (items, g_object_unref); - - /* If loading secrets ... locked items automatically ignored */ - } else if (closure->flags & SECRET_SEARCH_LOAD_SECRETS) { - items = g_hash_table_get_values (closure->items); - secret_item_load_secrets (items, closure->cancellable, - on_search_secrets, g_object_ref (res)); - g_list_free (items); - - /* No additional options, just complete */ - } else { - g_simple_async_result_complete (res); - } - } + if (closure->loading == 0) + secret_search_unlock_load_or_complete (res, closure); g_object_unref (res); } @@ -198,7 +204,8 @@ on_search_paths (GObject *source, SecretService *self = closure->service; GError *error = NULL; gint want = 1; - guint i; + gint count; + gint i; secret_service_search_for_dbus_paths_finish (self, result, &closure->unlocked, &closure->locked, &error); @@ -206,15 +213,16 @@ on_search_paths (GObject *source, want = 1; if (closure->flags & SECRET_SEARCH_ALL) want = G_MAXINT; + count = 0; - for (i = 0; closure->loading < want && closure->unlocked[i] != NULL; i++) + for (i = 0; count < want && closure->unlocked[i] != NULL; i++, count++) search_load_item_async (self, res, closure, closure->unlocked[i]); - for (i = 0; closure->loading < want && closure->locked[i] != NULL; i++) + for (i = 0; count < want && closure->locked[i] != NULL; i++, count++) search_load_item_async (self, res, closure, closure->locked[i]); /* No items loading, complete operation now */ if (closure->loading == 0) - g_simple_async_result_complete (res); + secret_search_unlock_load_or_complete (res, closure); } else { g_simple_async_result_take_error (res, error); diff --git a/library/secret-paths.c b/library/secret-paths.c index 6b1fb34..1d48836 100644 --- a/library/secret-paths.c +++ b/library/secret-paths.c @@ -286,7 +286,6 @@ secret_item_new_for_dbus_path_sync (SecretService *service, NULL); } - static void on_search_items_complete (GObject *source, GAsyncResult *result, @@ -297,16 +296,161 @@ on_search_items_complete (GObject *source, GVariant *response; response = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), result, &error); - if (error != NULL) + if (error != NULL) { + _secret_util_strip_remote_error (&error); g_simple_async_result_take_error (res, error); - else + } else { g_simple_async_result_set_op_res_gpointer (res, response, (GDestroyNotify)g_variant_unref); + } g_simple_async_result_complete (res); g_object_unref (res); } +/** + * secret_collection_search_for_dbus_paths: + * @collection: the secret collection + * @schema: (allow-none): the schema for the attributes + * @attributes: (element-type utf8 utf8): search for items matching these attributes + * @cancellable: optional cancellation object + * @callback: called when the operation completes + * @user_data: data to pass to the callback + * + * Search for items in @collection matching the @attributes, and return their + * DBus object paths. Only the specified collection is searched. The @attributes + * should be a table of string keys and string values. + * + * This function returns immediately and completes asynchronously. + * + * When your callback is called use secret_collection_search_for_dbus_paths_finish() + * to get the results of this function. Only the DBus object paths of the + * items will be returned. If you would like #SecretItem objects to be returned + * instead, then use the secret_collection_search() function. + */ +void +secret_collection_search_for_dbus_paths (SecretCollection *collection, + const SecretSchema *schema, + GHashTable *attributes, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *async; + const gchar *schema_name = NULL; + + g_return_if_fail (SECRET_IS_COLLECTION (collection)); + g_return_if_fail (attributes != NULL); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + /* Warnings raised already */ + if (schema != NULL && !_secret_attributes_validate (schema, attributes)) + return; + + if (schema != NULL && !(schema->flags & SECRET_SCHEMA_DONT_MATCH_NAME)) + schema_name = schema->name; + + async = g_simple_async_result_new (G_OBJECT (collection), callback, user_data, + secret_collection_search_for_dbus_paths); + + g_dbus_proxy_call (G_DBUS_PROXY (collection), "SearchItems", + g_variant_new ("(@a{ss})", _secret_attributes_to_variant (attributes, schema_name)), + G_DBUS_CALL_FLAGS_NONE, -1, cancellable, + on_search_items_complete, g_object_ref (async)); + + g_object_unref (async); +} + +/** + * secret_collection_search_for_dbus_paths_finish: + * @collection: the secret collection + * @result: asynchronous result passed to callback + * @error: location to place error on failure + * + * Complete asynchronous operation to search for items in a collection. + * + * DBus object paths of the items will be returned. If you would to have + * #SecretItem objects to be returned instead, then use the + * secret_collection_search() and secret_collection_search_finish() functions. + * + * Returns: (transfer full) (array zero-terminated=1): an array of DBus object + * paths for matching items. + */ +gchar ** +secret_collection_search_for_dbus_paths_finish (SecretCollection *collection, + GAsyncResult *result, + GError **error) +{ + GVariant *retval; + GSimpleAsyncResult *async; + gchar **paths = NULL; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (collection), + secret_collection_search_for_dbus_paths), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + async = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (async, error)) + return FALSE; + + retval= g_simple_async_result_get_op_res_gpointer (async); + g_variant_get (retval, "(^ao)", &paths); + return paths; +} + +/** + * secret_collection_search_for_dbus_paths_sync: + * @collection: the secret collection + * @schema: (allow-none): the schema for the attributes + * @attributes: (element-type utf8 utf8): search for items matching these attributes + * @cancellable: optional cancellation object + * @error: location to place error on failure + * + * Search for items matching the @attributes in @collection, and return their + * DBus object paths. The @attributes should be a table of string keys and + * string values. + * + * This function may block indefinetely. Use the asynchronous version + * in user interface threads. + * + * DBus object paths of the items will be returned. If you would to have + * #SecretItem objects to be returned instead, then use the + * secret_collection_search_sync() function. + * + * Returns: (transfer full) (array zero-terminated=1): an array of DBus object + * paths for matching items. + */ +gchar ** +secret_collection_search_for_dbus_paths_sync (SecretCollection *collection, + const SecretSchema *schema, + GHashTable *attributes, + GCancellable *cancellable, + GError **error) +{ + SecretSync *sync; + gchar **paths; + + g_return_val_if_fail (SECRET_IS_COLLECTION (collection), NULL); + g_return_val_if_fail (attributes != NULL, NULL); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + sync = _secret_sync_new (); + g_main_context_push_thread_default (sync->context); + + secret_collection_search_for_dbus_paths (collection, schema, attributes, cancellable, + _secret_sync_on_result, sync); + + g_main_loop_run (sync->loop); + + paths = secret_collection_search_for_dbus_paths_finish (collection, sync->result, error); + + g_main_context_pop_thread_default (sync->context); + _secret_sync_free (sync); + + return paths; +} + /** * secret_service_search_for_dbus_paths: * @self: the secret service diff --git a/library/secret-paths.h b/library/secret-paths.h index e61c9d5..644bf00 100644 --- a/library/secret-paths.h +++ b/library/secret-paths.h @@ -48,6 +48,22 @@ SecretCollection * secret_collection_new_for_dbus_path_sync (SecretSe GCancellable *cancellable, GError **error); +void secret_collection_search_for_dbus_paths (SecretCollection *collection, + const SecretSchema *schema, + GHashTable *attributes, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gchar ** secret_collection_search_for_dbus_paths_finish (SecretCollection *collection, + GAsyncResult *result, + GError **error); + +gchar ** secret_collection_search_for_dbus_paths_sync (SecretCollection *collection, + const SecretSchema *schema, + GHashTable *attributes, + GCancellable *cancellable, + GError **error); void secret_item_new_for_dbus_path (SecretService *service, const gchar *item_path, diff --git a/library/secret-service.h b/library/secret-service.h index 6c8d7ca..79ae773 100644 --- a/library/secret-service.h +++ b/library/secret-service.h @@ -35,13 +35,6 @@ typedef enum { SECRET_SERVICE_LOAD_COLLECTIONS = 1 << 2, } SecretServiceFlags; -typedef enum { - SECRET_SEARCH_NONE = 0, - SECRET_SEARCH_ALL = 1 << 1, - SECRET_SEARCH_UNLOCK = 1 << 2, - SECRET_SEARCH_LOAD_SECRETS = 1 << 3, -} SecretSearchFlags; - #define SECRET_TYPE_SERVICE (secret_service_get_type ()) #define SECRET_SERVICE(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), SECRET_TYPE_SERVICE, SecretService)) #define SECRET_SERVICE_CLASS(class) (G_TYPE_CHECK_CLASS_CAST ((class), SECRET_TYPE_SERVICE, SecretServiceClass)) diff --git a/library/secret-types.h b/library/secret-types.h index c0373ee..a7977df 100644 --- a/library/secret-types.h +++ b/library/secret-types.h @@ -44,6 +44,13 @@ typedef struct _SecretValue SecretValue; #define SECRET_COLLECTION_SESSION "session" +typedef enum { + SECRET_SEARCH_NONE = 0, + SECRET_SEARCH_ALL = 1 << 1, + SECRET_SEARCH_UNLOCK = 1 << 2, + SECRET_SEARCH_LOAD_SECRETS = 1 << 3, +} SecretSearchFlags; + G_END_DECLS #endif /* __G_SERVICE_H___ */ diff --git a/library/tests/mock/service.py b/library/tests/mock/service.py index 6ba8ea3..d5a4976 100644 --- a/library/tests/mock/service.py +++ b/library/tests/mock/service.py @@ -416,6 +416,11 @@ class SecretCollection(dbus.service.Object): item.content_type = content_type return (dbus.ObjectPath(item.path), dbus.ObjectPath("/")) + @dbus.service.method('org.freedesktop.Secret.Collection') + def SearchItems(self, attributes): + items = self.search_items(attributes) + return (dbus.Array([item.path for item in items], "o")) + @dbus.service.method('org.freedesktop.Secret.Collection', sender_keyword='sender') def Delete(self, sender=None): collection = self diff --git a/library/tests/test-collection.c b/library/tests/test-collection.c index 4385548..039c14b 100644 --- a/library/tests/test-collection.c +++ b/library/tests/test-collection.c @@ -33,6 +33,16 @@ typedef struct { SecretService *service; } Test; +static const SecretSchema MOCK_SCHEMA = { + "org.mock.Schema", + SECRET_SCHEMA_NONE, + { + { "number", SECRET_SCHEMA_ATTRIBUTE_INTEGER }, + { "string", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { "even", SECRET_SCHEMA_ATTRIBUTE_BOOLEAN }, + } +}; + static void setup (Test *test, gconstpointer data) @@ -525,6 +535,335 @@ test_delete_async (Test *test, g_assert (collection == NULL); } +static void +test_search_sync (Test *test, + gconstpointer used) +{ + const gchar *collection_path = "/org/freedesktop/secrets/collection/english"; + SecretCollection *collection; + GHashTable *attributes; + GError *error = NULL; + GList *items; + + collection = secret_collection_new_for_dbus_path_sync (test->service, collection_path, + SECRET_COLLECTION_NONE, NULL, &error); + g_assert_no_error (error); + + attributes = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (attributes, "number", "1"); + + items = secret_collection_search_sync (collection, &MOCK_SCHEMA, attributes, + SECRET_SEARCH_NONE, NULL, &error); + g_assert_no_error (error); + g_hash_table_unref (attributes); + + g_assert (items != NULL); + g_assert_cmpstr (g_dbus_proxy_get_object_path (items->data), ==, "/org/freedesktop/secrets/collection/english/1"); + + g_assert (items->next == NULL); + g_list_free_full (items, g_object_unref); + + g_object_unref (collection); +} + +static void +test_search_async (Test *test, + gconstpointer used) +{ + const gchar *collection_path = "/org/freedesktop/secrets/collection/english"; + SecretCollection *collection; + GAsyncResult *result = NULL; + GHashTable *attributes; + GError *error = NULL; + GList *items; + + collection = secret_collection_new_for_dbus_path_sync (test->service, collection_path, + SECRET_COLLECTION_NONE, NULL, &error); + g_assert_no_error (error); + + attributes = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (attributes, "number", "1"); + + secret_collection_search (collection, &MOCK_SCHEMA, attributes, + SECRET_SEARCH_NONE, NULL, + on_async_result, &result); + g_hash_table_unref (attributes); + g_assert (result == NULL); + + egg_test_wait (); + + g_assert (G_IS_ASYNC_RESULT (result)); + items = secret_collection_search_finish (collection, result, &error); + g_assert_no_error (error); + g_object_unref (result); + + g_assert (items != NULL); + g_assert_cmpstr (g_dbus_proxy_get_object_path (items->data), ==, "/org/freedesktop/secrets/collection/english/1"); + + g_assert (items->next == NULL); + g_list_free_full (items, g_object_unref); + + g_object_unref (collection); +} + +static gint +sort_by_object_path (gconstpointer a, + gconstpointer b) +{ + const gchar *pa = g_dbus_proxy_get_object_path ((GDBusProxy *)a); + const gchar *pb = g_dbus_proxy_get_object_path ((GDBusProxy *)b); + + return g_strcmp0 (pa, pb); +} + +static void +test_search_all_sync (Test *test, + gconstpointer used) +{ + const gchar *collection_path = "/org/freedesktop/secrets/collection/english"; + SecretCollection *collection; + GHashTable *attributes; + GError *error = NULL; + GList *items; + + collection = secret_collection_new_for_dbus_path_sync (test->service, collection_path, + SECRET_COLLECTION_NONE, NULL, &error); + g_assert_no_error (error); + + attributes = g_hash_table_new (g_str_hash, g_str_equal); + + items = secret_collection_search_sync (collection, &MOCK_SCHEMA, attributes, + SECRET_SEARCH_ALL, NULL, &error); + g_assert_no_error (error); + g_hash_table_unref (attributes); + + items = g_list_sort (items, sort_by_object_path); + + g_assert (items != NULL); + g_assert_cmpstr (g_dbus_proxy_get_object_path (items->data), ==, "/org/freedesktop/secrets/collection/english/1"); + g_assert (secret_item_get_secret (items->data) == NULL); + + g_assert (items->next != NULL); + g_assert_cmpstr (g_dbus_proxy_get_object_path (items->next->data), ==, "/org/freedesktop/secrets/collection/english/2"); + g_assert (secret_item_get_secret (items->next->data) == NULL); + + g_assert (items->next->next != NULL); + g_assert_cmpstr (g_dbus_proxy_get_object_path (items->next->next->data), ==, "/org/freedesktop/secrets/collection/english/3"); + g_assert (secret_item_get_secret (items->next->next->data) == NULL); + + g_assert (items->next->next->next == NULL); + g_list_free_full (items, g_object_unref); + + g_object_unref (collection); +} + +static void +test_search_all_async (Test *test, + gconstpointer used) +{ + const gchar *collection_path = "/org/freedesktop/secrets/collection/english"; + SecretCollection *collection; + GAsyncResult *result = NULL; + GHashTable *attributes; + GError *error = NULL; + GList *items; + + collection = secret_collection_new_for_dbus_path_sync (test->service, collection_path, + SECRET_COLLECTION_NONE, NULL, &error); + g_assert_no_error (error); + + attributes = g_hash_table_new (g_str_hash, g_str_equal); + + secret_collection_search (collection, &MOCK_SCHEMA, attributes, + SECRET_SEARCH_ALL, NULL, + on_async_result, &result); + g_hash_table_unref (attributes); + g_assert (result == NULL); + + egg_test_wait (); + + g_assert (G_IS_ASYNC_RESULT (result)); + items = secret_collection_search_finish (collection, result, &error); + g_assert_no_error (error); + g_object_unref (result); + + items = g_list_sort (items, sort_by_object_path); + + g_assert (items != NULL); + g_assert_cmpstr (g_dbus_proxy_get_object_path (items->data), ==, "/org/freedesktop/secrets/collection/english/1"); + g_assert (secret_item_get_secret (items->data) == NULL); + + g_assert (items->next != NULL); + g_assert_cmpstr (g_dbus_proxy_get_object_path (items->next->data), ==, "/org/freedesktop/secrets/collection/english/2"); + g_assert (secret_item_get_secret (items->next->data) == NULL); + + g_assert (items->next->next != NULL); + g_assert_cmpstr (g_dbus_proxy_get_object_path (items->next->next->data), ==, "/org/freedesktop/secrets/collection/english/3"); + g_assert (secret_item_get_secret (items->next->next->data) == NULL); + + g_assert (items->next->next->next == NULL); + g_list_free_full (items, g_object_unref); + + g_object_unref (collection); +} + +static void +test_search_unlock_sync (Test *test, + gconstpointer used) +{ + const gchar *collection_path = "/org/freedesktop/secrets/collection/spanish"; + SecretCollection *collection; + GHashTable *attributes; + GError *error = NULL; + GList *items; + + collection = secret_collection_new_for_dbus_path_sync (test->service, collection_path, + SECRET_COLLECTION_NONE, NULL, &error); + g_assert_no_error (error); + + attributes = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (attributes, "number", "1"); + + items = secret_collection_search_sync (collection, &MOCK_SCHEMA, attributes, + SECRET_SEARCH_UNLOCK, NULL, &error); + g_assert_no_error (error); + g_hash_table_unref (attributes); + + g_assert (items != NULL); + g_assert_cmpstr (g_dbus_proxy_get_object_path (items->data), ==, "/org/freedesktop/secrets/collection/spanish/10"); + g_assert (secret_item_get_locked (items->data) == FALSE); + g_assert (secret_item_get_secret (items->data) == NULL); + + g_assert (items->next == NULL); + g_list_free_full (items, g_object_unref); + + g_object_unref (collection); +} + +static void +test_search_unlock_async (Test *test, + gconstpointer used) +{ + const gchar *collection_path = "/org/freedesktop/secrets/collection/spanish"; + SecretCollection *collection; + GAsyncResult *result = NULL; + GHashTable *attributes; + GError *error = NULL; + GList *items; + + collection = secret_collection_new_for_dbus_path_sync (test->service, collection_path, + SECRET_COLLECTION_NONE, NULL, &error); + g_assert_no_error (error); + + attributes = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (attributes, "number", "1"); + + secret_collection_search (collection, &MOCK_SCHEMA, attributes, + SECRET_SEARCH_UNLOCK, NULL, + on_async_result, &result); + g_hash_table_unref (attributes); + g_assert (result == NULL); + + egg_test_wait (); + + g_assert (G_IS_ASYNC_RESULT (result)); + items = secret_collection_search_finish (collection, result, &error); + g_assert_no_error (error); + g_object_unref (result); + + g_assert (items != NULL); + g_assert_cmpstr (g_dbus_proxy_get_object_path (items->data), ==, "/org/freedesktop/secrets/collection/spanish/10"); + g_assert (secret_item_get_locked (items->data) == FALSE); + g_assert (secret_item_get_secret (items->data) == NULL); + + g_assert (items->next == NULL); + g_list_free_full (items, g_object_unref); + + g_object_unref (collection); +} + +static void +test_search_secrets_sync (Test *test, + gconstpointer used) +{ + const gchar *collection_path = "/org/freedesktop/secrets/collection/english"; + SecretCollection *collection; + GHashTable *attributes; + GError *error = NULL; + SecretValue *value; + GList *items; + + collection = secret_collection_new_for_dbus_path_sync (test->service, collection_path, + SECRET_COLLECTION_NONE, NULL, &error); + g_assert_no_error (error); + + attributes = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (attributes, "number", "1"); + + items = secret_collection_search_sync (collection, &MOCK_SCHEMA, attributes, + SECRET_SEARCH_LOAD_SECRETS, + NULL, &error); + g_assert_no_error (error); + g_hash_table_unref (attributes); + + g_assert (items != NULL); + g_assert_cmpstr (g_dbus_proxy_get_object_path (items->data), ==, "/org/freedesktop/secrets/collection/english/1"); + g_assert (secret_item_get_locked (items->data) == FALSE); + value = secret_item_get_secret (items->data); + g_assert (value != NULL); + secret_value_unref (value); + + g_assert (items->next == NULL); + g_list_free_full (items, g_object_unref); + + g_object_unref (collection); +} + +static void +test_search_secrets_async (Test *test, + gconstpointer used) +{ + const gchar *collection_path = "/org/freedesktop/secrets/collection/english"; + SecretCollection *collection; + GAsyncResult *result = NULL; + GHashTable *attributes; + GError *error = NULL; + SecretValue *value; + GList *items; + + collection = secret_collection_new_for_dbus_path_sync (test->service, collection_path, + SECRET_COLLECTION_NONE, NULL, &error); + g_assert_no_error (error); + + attributes = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (attributes, "number", "1"); + + secret_collection_search (collection, &MOCK_SCHEMA, attributes, + SECRET_SEARCH_LOAD_SECRETS, NULL, + on_async_result, &result); + g_hash_table_unref (attributes); + g_assert (result == NULL); + + egg_test_wait (); + + g_assert (G_IS_ASYNC_RESULT (result)); + items = secret_collection_search_finish (collection, result, &error); + g_assert_no_error (error); + g_object_unref (result); + + g_assert (items != NULL); + g_assert_cmpstr (g_dbus_proxy_get_object_path (items->data), ==, "/org/freedesktop/secrets/collection/english/1"); + g_assert (secret_item_get_locked (items->data) == FALSE); + value = secret_item_get_secret (items->data); + g_assert (value != NULL); + secret_value_unref (value); + + g_assert (items->next == NULL); + g_list_free_full (items, g_object_unref); + + g_object_unref (collection); +} + int main (int argc, char **argv) { @@ -548,5 +887,14 @@ main (int argc, char **argv) g_test_add ("/collection/delete-sync", Test, "mock-service-normal.py", setup, test_delete_sync, teardown); g_test_add ("/collection/delete-async", Test, "mock-service-normal.py", setup, test_delete_async, teardown); + g_test_add ("/collection/search-sync", Test, "mock-service-normal.py", setup, test_search_sync, teardown); + g_test_add ("/collection/search-async", Test, "mock-service-normal.py", setup, test_search_async, teardown); + g_test_add ("/collection/search-all-sync", Test, "mock-service-normal.py", setup, test_search_all_sync, teardown); + g_test_add ("/collection/search-all-async", Test, "mock-service-normal.py", setup, test_search_all_async, teardown); + g_test_add ("/collection/search-unlock-sync", Test, "mock-service-normal.py", setup, test_search_unlock_sync, teardown); + g_test_add ("/collection/search-unlock-async", Test, "mock-service-normal.py", setup, test_search_unlock_async, teardown); + g_test_add ("/collection/search-secrets-sync", Test, "mock-service-normal.py", setup, test_search_secrets_sync, teardown); + g_test_add ("/collection/search-secrets-async", Test, "mock-service-normal.py", setup, test_search_secrets_async, teardown); + return egg_tests_run_with_loop (); }