diff --git a/library/gsecret-collection.c b/library/gsecret-collection.c index 7a5f0aa..b9a8dbc 100644 --- a/library/gsecret-collection.c +++ b/library/gsecret-collection.c @@ -729,7 +729,7 @@ collection_properties_new (const gchar *label) (GDestroyNotify)g_variant_unref); value = g_variant_new_string (label); g_hash_table_insert (properties, - GSECRET_COLLECTION_INTERFACE "Label", + GSECRET_COLLECTION_INTERFACE ".Label", g_variant_ref_sink (value)); return properties; diff --git a/library/gsecret-item.c b/library/gsecret-item.c index a2eb773..2f2bcb6 100644 --- a/library/gsecret-item.c +++ b/library/gsecret-item.c @@ -518,17 +518,17 @@ item_properties_new (const gchar *schema_name, value = g_variant_new_string (label); g_hash_table_insert (properties, - GSECRET_COLLECTION_INTERFACE "Label", + GSECRET_ITEM_INTERFACE ".Label", g_variant_ref_sink (value)); value = g_variant_new_string (schema_name); g_hash_table_insert (properties, - GSECRET_COLLECTION_INTERFACE "Schema", + GSECRET_ITEM_INTERFACE ".Schema", g_variant_ref_sink (value)); value = _gsecret_util_variant_for_attributes (attributes); g_hash_table_insert (properties, - GSECRET_COLLECTION_INTERFACE "Attributes", + GSECRET_ITEM_INTERFACE ".Attributes", g_variant_ref_sink (value)); return properties; diff --git a/library/gsecret-methods.c b/library/gsecret-methods.c index 0dc8f1a..bef8e58 100644 --- a/library/gsecret-methods.c +++ b/library/gsecret-methods.c @@ -1201,25 +1201,6 @@ gsecret_service_unlock_sync (GSecretService *self, return count; } -typedef struct { - gchar *collection_path; - GSecretValue *value; - GCancellable *cancellable; - GSecretPrompt *prompt; - gboolean created; -} StoreClosure; - -static void -store_closure_free (gpointer data) -{ - StoreClosure *closure = data; - g_free (closure->collection_path); - gsecret_value_unref (closure->value); - g_clear_object (&closure->cancellable); - g_clear_object (&closure->prompt); - g_free (closure); -} - void gsecret_service_store (GSecretService *self, const GSecretSchema *schema, @@ -1250,59 +1231,6 @@ gsecret_service_store (GSecretService *self, g_hash_table_unref (attributes); } -static void -on_store_prompt (GObject *source, - GAsyncResult *result, - gpointer user_data) -{ - GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data); - StoreClosure *closure = g_simple_async_result_get_op_res_gpointer (res); - GError *error = NULL; - - closure->created = gsecret_service_prompt_finish (GSECRET_SERVICE (source), result, &error); - if (error != NULL) - g_simple_async_result_take_error (res, error); - - g_simple_async_result_complete (res); - g_object_unref (res); -} - -static void -on_store_create (GObject *source, - GAsyncResult *result, - gpointer user_data) -{ - GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data); - StoreClosure *closure = g_simple_async_result_get_op_res_gpointer (res); - GSecretService *self = GSECRET_SERVICE (g_async_result_get_source_object (result)); - const gchar *prompt_path = NULL; - const gchar *item_path = NULL; - GError *error = NULL; - GVariant *retval; - - retval = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error); - if (error == NULL) { - g_variant_get (retval, "(&o&o)", &item_path, &prompt_path); - if (!_gsecret_util_empty_path (prompt_path)) { - closure->prompt = gsecret_prompt_instance (self, prompt_path); - gsecret_service_prompt (self, closure->prompt, closure->cancellable, - on_store_prompt, g_object_ref (res)); - - } else { - g_simple_async_result_complete (res); - } - - g_variant_unref (retval); - - } else { - g_simple_async_result_take_error (res, error); - g_simple_async_result_complete (res); - } - - g_object_unref (self); - g_object_unref (res); -} - void gsecret_service_storev (GSecretService *self, const GSecretSchema *schema, @@ -1314,13 +1242,8 @@ gsecret_service_storev (GSecretService *self, GAsyncReadyCallback callback, gpointer user_data) { - GSimpleAsyncResult *res; - GSecretSession *session; - GVariant *attrs; - StoreClosure *closure; - GVariantBuilder builder; - GVariant *params; - GDBusProxy *proxy; + GHashTable *properties; + GVariant *propval; g_return_if_fail (GSECRET_IS_SERVICE (self)); g_return_if_fail (schema != NULL); @@ -1330,39 +1253,28 @@ gsecret_service_storev (GSecretService *self, g_return_if_fail (value != NULL); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); - /* Build up the attributes */ - attrs = _gsecret_util_variant_for_attributes (attributes); + properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, + (GDestroyNotify)g_variant_unref); - /* Build up the various properties */ - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); - g_variant_builder_add (&builder, "{sv}", GSECRET_SERVICE_INTERFACE "Attributes", attrs); - g_variant_builder_add (&builder, "{sv}", GSECRET_SERVICE_INTERFACE "Label", g_variant_new_string (label)); - g_variant_builder_add (&builder, "{sv}", GSECRET_SERVICE_INTERFACE "Schema", g_variant_new_string (schema->schema_name)); + propval = g_variant_new_string (label); + g_hash_table_insert (properties, + GSECRET_ITEM_INTERFACE ".Label", + g_variant_ref_sink (propval)); - res = g_simple_async_result_new (G_OBJECT (self), callback, user_data, - gsecret_service_storev); - closure = g_new0 (StoreClosure, 1); - closure->collection_path = g_strdup (collection_path); - closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL; - g_simple_async_result_set_op_res_gpointer (res, closure, store_closure_free); + propval = g_variant_new_string (schema->schema_name); + g_hash_table_insert (properties, + GSECRET_ITEM_INTERFACE ".Schema", + g_variant_ref_sink (propval)); - session = _gsecret_service_get_session (self); - params = g_variant_new ("(&a{sv}&(oayays)b)", - g_variant_builder_end (&builder), - _gsecret_session_encode_secret (session, value), - TRUE); + propval = _gsecret_util_variant_for_attributes (attributes); + g_hash_table_insert (properties, + GSECRET_ITEM_INTERFACE ".Attributes", + g_variant_ref_sink (propval)); - proxy = G_DBUS_PROXY (self); - g_dbus_connection_call (g_dbus_proxy_get_connection (proxy), - g_dbus_proxy_get_name (proxy), - closure->collection_path, - GSECRET_COLLECTION_INTERFACE, - "CreateItem", params, G_VARIANT_TYPE ("(oo)"), - G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, - closure->cancellable, on_store_create, - g_object_ref (res)); + gsecret_service_create_item_path (self, collection_path, properties, value, + TRUE, cancellable, callback, user_data); - g_object_unref (res); + g_hash_table_unref (properties); } gboolean @@ -1370,20 +1282,15 @@ gsecret_service_store_finish (GSecretService *self, GAsyncResult *result, GError **error) { - GSimpleAsyncResult *res; - StoreClosure *closure; + gchar *path; g_return_val_if_fail (GSECRET_IS_SERVICE (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), - gsecret_service_storev), FALSE); - res = G_SIMPLE_ASYNC_RESULT (result); - if (!g_simple_async_result_propagate_error (res, error)) - return FALSE; + path = gsecret_service_create_item_path_finish (self, result, error); - closure = g_simple_async_result_get_op_res_gpointer (res); - return closure->created; + g_free (path); + return path != NULL; } gboolean @@ -2334,7 +2241,7 @@ on_create_item_session (GObject *source, gsecret_service_ensure_session_finish (self, result, &error); if (error == NULL) { session = _gsecret_service_get_session (self); - params = g_variant_new ("@a{sv}@(oayays)b", + params = g_variant_new ("(@a{sv}@(oayays)b)", closure->properties, _gsecret_session_encode_secret (session, closure->value), closure->replace); @@ -2377,7 +2284,7 @@ gsecret_service_create_item_path (GSecretService *self, res = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gsecret_service_create_item_path); - closure = g_new0 (ItemClosure, 1); + closure = g_slice_new0 (ItemClosure); closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL; closure->properties = _gsecret_util_variant_for_properties (properties); g_variant_ref_sink (closure->properties); @@ -2399,7 +2306,7 @@ gsecret_service_create_item_path_finish (GSecretService *self, GError **error) { GSimpleAsyncResult *res; - CollectionClosure *closure; + ItemClosure *closure; gchar *path; g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), @@ -2412,8 +2319,8 @@ gsecret_service_create_item_path_finish (GSecretService *self, return NULL; closure = g_simple_async_result_get_op_res_gpointer (res); - path = closure->collection_path; - closure->collection_path = NULL; + path = closure->item_path; + closure->item_path = NULL; return path; } diff --git a/library/gsecret-session.c b/library/gsecret-session.c index e691459..a73ab81 100644 --- a/library/gsecret-session.c +++ b/library/gsecret-session.c @@ -583,11 +583,9 @@ service_encode_aes_secret (GSecretSession *session, child = g_variant_new_from_data (G_VARIANT_TYPE ("ay"), iv, 16, TRUE, g_free, iv); g_variant_builder_add_value (builder, child); - g_variant_unref (child); child = g_variant_new_from_data (G_VARIANT_TYPE ("ay"), padded, n_padded, TRUE, egg_secure_free, padded); g_variant_builder_add_value (builder, child); - g_variant_unref (child); g_variant_builder_add (builder, "s", gsecret_value_get_content_type (value)); return TRUE; diff --git a/library/tests/mock/service.py b/library/tests/mock/service.py index 0b85f3b..e15140c 100644 --- a/library/tests/mock/service.py +++ b/library/tests/mock/service.py @@ -45,10 +45,10 @@ class IsLocked(dbus.exceptions.DBusException): dbus.exceptions.DBusException.__init__(self, msg, name="org.freedesktop.Secret.Error.IsLocked") unique_identifier = 0 -def next_identifier(): +def next_identifier(prefix='x'): global unique_identifier unique_identifier += 1 - return unique_identifier + return "%s%d" % (prefix, unique_identifier) def hex_encode(string): return "".join([hex(ord(c))[2:].zfill(2) for c in string]) @@ -64,6 +64,11 @@ class PlainAlgorithm(): def encrypt(self, key, data): return ("", data) + def decrypt(self, param, data): + if params == "": + raise InvalidArgs("invalid secret plain parameter") + return data + class AesAlgorithm(): def negotiate(self, service, sender, param): @@ -91,6 +96,16 @@ class AesAlgorithm(): return ("".join([chr(i) for i in iv]), "".join([chr(i) for i in ciph])) + def decrypt(self, key, param, data): + key = map(ord, key) + keysize = len(key) + iv = map(ord, param[:16]) + data = map(ord, data) + moo = aes.AESModeOfOperation() + mode = aes.AESModeOfOperation.modeOfOperation["CBC"] + decr = moo.decrypt(data, None, mode, key, keysize, iv) + return aes.strip_PKCS7_padding(decr) + class SecretPrompt(dbus.service.Object): def __init__(self, service, sender, prompt_name=None, delay=0, @@ -106,7 +121,7 @@ class SecretPrompt(dbus.service.Object): if prompt_name: self.path = "/org/freedesktop/secrets/prompts/%s" % prompt_name else: - self.path = "/org/freedesktop/secrets/prompts/p%d" % next_identifier() + self.path = "/org/freedesktop/secrets/prompts/%s" % next_identifier('p') dbus.service.Object.__init__(self, service.bus_name, self.path) service.add_prompt(self) assert self.path not in objects @@ -140,7 +155,7 @@ class SecretSession(dbus.service.Object): self.service = service self.algorithm = algorithm self.key = key - self.path = "/org/freedesktop/secrets/sessions/%d" % next_identifier() + self.path = "/org/freedesktop/secrets/sessions/%s" % next_identifier('s') dbus.service.Object.__init__(self, service.bus_name, self.path) service.add_session(self) objects[self.path] = self @@ -153,6 +168,10 @@ class SecretSession(dbus.service.Object): dbus.ByteArray(data), dbus.String(content_type)), signature="oayays") + def decode_secret(self, value): + plain = self.algorithm.decrypt(self.key, value[1], value[2]) + return (plain, value[3]) + @dbus.service.method('org.freedesktop.Secret.Session') def Close(self): self.remove_from_connection() @@ -282,6 +301,33 @@ class SecretCollection(dbus.service.Object): del objects[self.path] self.remove_from_connection() + @dbus.service.method('org.freedesktop.Secret.Collection', byte_arrays=True, sender_keyword='sender') + def CreateItem(self, properties, value, replace, sender=None): + session_path = value[0] + session = objects.get(session_path, None) + if not session or session.sender != sender: + raise InvalidArgs("session invalid: %s" % session_path) + + attributes = properties.get("org.freedesktop.Secret.Item.Attributes", None) + label = properties.get("org.freedesktop.Secret.Item.Label", None) + schema = properties.get("org.freedesktop.Secret.Item.Schema", None) + (secret, content_type) = session.decode_secret(value) + item = None + + if replace and attributes: + items = self.search_items(attributes) + if items: + item = items[0] + if item is None: + item = SecretItem(self, next_identifier('i'), label, attributes, + secret=secret, confirm=False, content_type=content_type) + else: + item.label = label + item.secret = secret + item.attributes = attributes + item.content_type = content_type + return (dbus.ObjectPath(item.path), dbus.ObjectPath("/")) + @dbus.service.method('org.freedesktop.Secret.Collection', sender_keyword='sender') def Delete(self, sender=None): if self.confirm: diff --git a/library/tests/test-service.c b/library/tests/test-service.c index 854bfec..fe60a48 100644 --- a/library/tests/test-service.c +++ b/library/tests/test-service.c @@ -1467,6 +1467,162 @@ test_lookup_no_match (Test *test, g_assert (value == NULL); } +static void +test_store_sync (Test *test, + gconstpointer used) +{ + const gchar *collection_path = "/org/freedesktop/secrets/collection/english"; + GSecretValue *value = gsecret_value_new ("apassword", -1, "text/plain"); + GHashTable *attributes; + GError *error = NULL; + gchar **paths; + gboolean ret; + gsize length; + + ret = gsecret_service_store_sync (test->service, &STORE_SCHEMA, collection_path, + "New Item Label", value, NULL, &error, + "even", FALSE, + "string", "seventeen", + "number", 17, + NULL); + g_assert_no_error (error); + gsecret_value_unref (value); + + attributes = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (attributes, "even", "false"); + g_hash_table_insert (attributes, "string", "seventeen"); + g_hash_table_insert (attributes, "number", "17"); + + ret = gsecret_service_search_for_paths_sync (test->service, attributes, NULL, + &paths, NULL, &error); + g_hash_table_unref (attributes); + g_assert (ret == TRUE); + + g_assert (paths != NULL); + g_assert (paths[0] != NULL); + g_assert (paths[1] == NULL); + + value = gsecret_service_get_secret_for_path_sync (test->service, paths[0], + NULL, &error); + g_assert_no_error (error); + + g_assert (value != NULL); + g_assert_cmpstr (gsecret_value_get (value, &length), ==, "apassword"); + g_assert_cmpuint (length, ==, 9); + + gsecret_value_unref (value); + g_strfreev (paths); +} + +static void +test_store_replace (Test *test, + gconstpointer used) +{ + const gchar *collection_path = "/org/freedesktop/secrets/collection/english"; + GSecretValue *value = gsecret_value_new ("apassword", -1, "text/plain"); + GHashTable *attributes; + GError *error = NULL; + gchar **paths; + gboolean ret; + gsize length; + + ret = gsecret_service_store_sync (test->service, &STORE_SCHEMA, collection_path, + "New Item Label", value, NULL, &error, + "even", FALSE, + "string", "seventeen", + "number", 17, + NULL); + g_assert_no_error (error); + + ret = gsecret_service_store_sync (test->service, &STORE_SCHEMA, collection_path, + "Another Label", value, NULL, &error, + "even", FALSE, + "string", "seventeen", + "number", 17, + NULL); + g_assert_no_error (error); + gsecret_value_unref (value); + + attributes = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (attributes, "even", "false"); + g_hash_table_insert (attributes, "string", "seventeen"); + g_hash_table_insert (attributes, "number", "17"); + + ret = gsecret_service_search_for_paths_sync (test->service, attributes, NULL, + &paths, NULL, &error); + g_hash_table_unref (attributes); + g_assert (ret == TRUE); + + g_assert (paths != NULL); + g_assert (paths[0] != NULL); + g_assert (paths[1] == NULL); + + value = gsecret_service_get_secret_for_path_sync (test->service, paths[0], + NULL, &error); + g_assert_no_error (error); + + g_assert (value != NULL); + g_assert_cmpstr (gsecret_value_get (value, &length), ==, "apassword"); + g_assert_cmpuint (length, ==, 9); + + gsecret_value_unref (value); + g_strfreev (paths); +} + +static void +test_store_async (Test *test, + gconstpointer used) +{ + const gchar *collection_path = "/org/freedesktop/secrets/collection/english"; + GSecretValue *value = gsecret_value_new ("apassword", -1, "text/plain"); + GAsyncResult *result = NULL; + GHashTable *attributes; + GError *error = NULL; + gchar **paths; + gboolean ret; + gsize length; + + gsecret_service_store (test->service, &STORE_SCHEMA, collection_path, + "New Item Label", value, NULL, on_complete_get_result, &result, + "even", FALSE, + "string", "seventeen", + "number", 17, + NULL); + g_assert (result == NULL); + gsecret_value_unref (value); + + egg_test_wait (); + + ret = gsecret_service_store_finish (test->service, result, &error); + g_assert_no_error (error); + g_object_unref (result); + + attributes = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (attributes, "even", "false"); + g_hash_table_insert (attributes, "string", "seventeen"); + g_hash_table_insert (attributes, "number", "17"); + + ret = gsecret_service_search_for_paths_sync (test->service, attributes, NULL, + &paths, NULL, &error); + g_hash_table_unref (attributes); + g_assert (ret == TRUE); + + g_assert (paths != NULL); + g_assert (paths[0] != NULL); + g_assert (paths[1] == NULL); + + value = gsecret_service_get_secret_for_path_sync (test->service, paths[0], + NULL, &error); + g_assert_no_error (error); + + g_assert (value != NULL); + g_assert_cmpstr (gsecret_value_get (value, &length), ==, "apassword"); + g_assert_cmpuint (length, ==, 9); + + gsecret_value_unref (value); + g_strfreev (paths); +} + int main (int argc, char **argv) { @@ -1525,5 +1681,9 @@ main (int argc, char **argv) g_test_add ("/service/remove-locked", Test, "mock-service-delete.py", setup, test_remove_locked, teardown); g_test_add ("/service/remove-no-match", Test, "mock-service-delete.py", setup, test_remove_no_match, teardown); + g_test_add ("/service/store-sync", Test, "mock-service-normal.py", setup, test_store_sync, teardown); + g_test_add ("/service/store-async", Test, "mock-service-normal.py", setup, test_store_async, teardown); + g_test_add ("/service/store-replace", Test, "mock-service-normal.py", setup, test_store_replace, teardown); + return egg_tests_run_with_loop (); }