mirror of
https://gitlab.gnome.org/GNOME/libsecret.git
synced 2025-01-03 02:28:53 +00:00
0b2844da47
It is already closed a few lines above
830 lines
21 KiB
C
830 lines
21 KiB
C
/* libsecret - GLib wrapper for Secret Service
|
|
*
|
|
* Copyright 2019 Red Hat, Inc.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published
|
|
* by the Free Software Foundation; either version 2.1 of the licence or (at
|
|
* your option) any later version.
|
|
*
|
|
* See the included COPYING file for more information.
|
|
*
|
|
* Author: Daiki Ueno
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "secret-backend.h"
|
|
#include "secret-file-backend.h"
|
|
#include "secret-file-collection.h"
|
|
#include "secret-file-item.h"
|
|
#include "secret-private.h"
|
|
#include "secret-retrievable.h"
|
|
|
|
#include "egg/egg-secure-memory.h"
|
|
|
|
EGG_SECURE_DECLARE (secret_file_backend);
|
|
|
|
#include <gio/gunixfdlist.h>
|
|
#include <gio/gunixinputstream.h>
|
|
#include <glib-unix.h>
|
|
|
|
#define PORTAL_BUS_NAME "org.freedesktop.portal.Desktop"
|
|
#define PORTAL_OBJECT_PATH "/org/freedesktop/portal/desktop"
|
|
#define PORTAL_REQUEST_INTERFACE "org.freedesktop.portal.Request"
|
|
#define PORTAL_SECRET_INTERFACE "org.freedesktop.portal.Secret"
|
|
#define PORTAL_SECRET_VERSION 1
|
|
|
|
static void secret_file_backend_async_initable_iface (GAsyncInitableIface *iface);
|
|
static void secret_file_backend_backend_iface (SecretBackendInterface *iface);
|
|
|
|
struct _SecretFileBackend {
|
|
GObject parent;
|
|
SecretFileCollection *collection;
|
|
SecretServiceFlags init_flags;
|
|
};
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (SecretFileBackend, secret_file_backend, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, secret_file_backend_async_initable_iface);
|
|
G_IMPLEMENT_INTERFACE (SECRET_TYPE_BACKEND, secret_file_backend_backend_iface);
|
|
_secret_backend_ensure_extension_point ();
|
|
g_io_extension_point_implement (SECRET_BACKEND_EXTENSION_POINT_NAME,
|
|
g_define_type_id,
|
|
"file",
|
|
0)
|
|
);
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_FLAGS
|
|
};
|
|
|
|
static void
|
|
secret_file_backend_init (SecretFileBackend *self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
secret_file_backend_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
SecretFileBackend *self = SECRET_FILE_BACKEND (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_FLAGS:
|
|
self->init_flags = g_value_get_flags (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
secret_file_backend_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
SecretFileBackend *self = SECRET_FILE_BACKEND (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_FLAGS:
|
|
g_value_set_flags (value, self->init_flags);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
secret_file_backend_finalize (GObject *object)
|
|
{
|
|
SecretFileBackend *self = SECRET_FILE_BACKEND (object);
|
|
|
|
g_clear_object (&self->collection);
|
|
|
|
G_OBJECT_CLASS (secret_file_backend_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
secret_file_backend_class_init (SecretFileBackendClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->set_property = secret_file_backend_set_property;
|
|
object_class->get_property = secret_file_backend_get_property;
|
|
object_class->finalize = secret_file_backend_finalize;
|
|
|
|
/**
|
|
* SecretFileBackend:flags:
|
|
*
|
|
* A set of flags describing which parts of the secret file have
|
|
* been initialized.
|
|
*/
|
|
g_object_class_override_property (object_class, PROP_FLAGS, "flags");
|
|
}
|
|
|
|
static void
|
|
on_collection_new_async (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task = G_TASK (user_data);
|
|
SecretFileBackend *self = g_task_get_source_object (task);
|
|
GObject *object;
|
|
GError *error = NULL;
|
|
|
|
object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
|
|
result,
|
|
&error);
|
|
if (object == NULL) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
self->collection = SECRET_FILE_COLLECTION (object);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
typedef struct {
|
|
gint io_priority;
|
|
GFile *file;
|
|
GInputStream *stream;
|
|
gchar *buffer;
|
|
GDBusConnection *connection;
|
|
gchar *request_path;
|
|
guint portal_signal_id;
|
|
gulong cancellable_signal_id;
|
|
} InitClosure;
|
|
|
|
static void
|
|
init_closure_free (gpointer data)
|
|
{
|
|
InitClosure *init = data;
|
|
g_object_unref (init->file);
|
|
g_clear_object (&init->stream);
|
|
g_clear_pointer (&init->buffer, egg_secure_free);
|
|
g_clear_object (&init->connection);
|
|
g_clear_pointer (&init->request_path, g_free);
|
|
g_slice_free (InitClosure, init);
|
|
}
|
|
|
|
#define PASSWORD_SIZE 64
|
|
|
|
static void
|
|
on_read_all (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GInputStream *stream = G_INPUT_STREAM (source_object);
|
|
GTask *task = G_TASK (user_data);
|
|
InitClosure *init = g_task_get_task_data (task);
|
|
gsize bytes_read;
|
|
SecretValue *password;
|
|
GError *error = NULL;
|
|
|
|
if (!g_input_stream_read_all_finish (stream, result, &bytes_read,
|
|
&error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (bytes_read != PASSWORD_SIZE) {
|
|
g_task_return_new_error (task,
|
|
SECRET_ERROR,
|
|
SECRET_ERROR_PROTOCOL,
|
|
"invalid password returned from portal");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
password = secret_value_new (init->buffer, bytes_read, "text/plain");
|
|
g_async_initable_new_async (SECRET_TYPE_FILE_COLLECTION,
|
|
init->io_priority,
|
|
g_task_get_cancellable (task),
|
|
on_collection_new_async,
|
|
task,
|
|
"file", g_object_ref (init->file),
|
|
"password", password,
|
|
NULL);
|
|
g_object_unref (init->file);
|
|
secret_value_unref (password);
|
|
}
|
|
|
|
static void
|
|
on_portal_response (GDBusConnection *connection,
|
|
const gchar *sender_name,
|
|
const gchar *object_path,
|
|
const gchar *interface_name,
|
|
const gchar *signal_name,
|
|
GVariant *parameters,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task = G_TASK (user_data);
|
|
InitClosure *init = g_task_get_task_data (task);
|
|
guint32 response;
|
|
|
|
if (init->cancellable_signal_id) {
|
|
g_cancellable_disconnect (g_task_get_cancellable (task), init->cancellable_signal_id);
|
|
init->cancellable_signal_id = 0;
|
|
}
|
|
|
|
g_dbus_connection_signal_unsubscribe (connection,
|
|
init->portal_signal_id);
|
|
|
|
g_variant_get (parameters, "(ua{sv})", &response, NULL);
|
|
|
|
switch (response) {
|
|
case 0:
|
|
init->buffer = egg_secure_alloc (PASSWORD_SIZE);
|
|
g_input_stream_read_all_async (init->stream,
|
|
init->buffer, PASSWORD_SIZE,
|
|
G_PRIORITY_DEFAULT,
|
|
g_task_get_cancellable (task),
|
|
on_read_all,
|
|
task);
|
|
break;
|
|
case 1:
|
|
g_task_return_new_error (task,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_CANCELLED,
|
|
"user interaction cancelled");
|
|
g_object_unref (task);
|
|
break;
|
|
case 2:
|
|
g_task_return_new_error (task,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"user interaction failed");
|
|
g_object_unref (task);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_portal_request_close (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GDBusConnection *connection = G_DBUS_CONNECTION (source_object);
|
|
GTask *task = G_TASK (user_data);
|
|
GError *error = NULL;
|
|
|
|
if (!g_dbus_connection_call_finish (connection, result, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
on_portal_cancel (GCancellable *cancellable,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task = G_TASK (user_data);
|
|
InitClosure *init = g_task_get_task_data (task);
|
|
|
|
g_dbus_connection_call (init->connection,
|
|
PORTAL_BUS_NAME,
|
|
init->request_path,
|
|
PORTAL_REQUEST_INTERFACE,
|
|
"Close",
|
|
NULL,
|
|
NULL,
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
-1,
|
|
cancellable,
|
|
on_portal_request_close,
|
|
task);
|
|
|
|
g_cancellable_disconnect (cancellable, init->cancellable_signal_id);
|
|
init->cancellable_signal_id = 0;
|
|
}
|
|
|
|
static void
|
|
on_portal_retrieve_secret (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GDBusConnection *connection = G_DBUS_CONNECTION (source_object);
|
|
GTask *task = G_TASK (user_data);
|
|
InitClosure *init = g_task_get_task_data (task);
|
|
GCancellable *cancellable = g_task_get_cancellable (task);
|
|
GVariant *reply;
|
|
GError *error = NULL;
|
|
|
|
reply = g_dbus_connection_call_with_unix_fd_list_finish (connection,
|
|
NULL,
|
|
result,
|
|
&error);
|
|
if (reply == NULL) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
g_variant_get (reply, "(o)", &init->request_path);
|
|
g_variant_unref (reply);
|
|
|
|
init->portal_signal_id =
|
|
g_dbus_connection_signal_subscribe (connection,
|
|
PORTAL_BUS_NAME,
|
|
PORTAL_REQUEST_INTERFACE,
|
|
"Response",
|
|
init->request_path,
|
|
NULL,
|
|
G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
|
|
on_portal_response,
|
|
task,
|
|
NULL);
|
|
|
|
if (cancellable != NULL)
|
|
init->cancellable_signal_id =
|
|
g_cancellable_connect (cancellable,
|
|
G_CALLBACK (on_portal_cancel),
|
|
task,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
on_bus_get (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GDBusConnection *connection;
|
|
GTask *task = G_TASK (user_data);
|
|
InitClosure *init = g_task_get_task_data (task);
|
|
GUnixFDList *fd_list;
|
|
gint fds[2];
|
|
gint fd_index;
|
|
GVariantBuilder options;
|
|
GError *error = NULL;
|
|
|
|
connection = g_bus_get_finish (result, &error);
|
|
if (connection == NULL) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
init->connection = connection;
|
|
|
|
if (!g_unix_open_pipe (fds, FD_CLOEXEC, &error)) {
|
|
g_object_unref (connection);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
fd_list = g_unix_fd_list_new ();
|
|
fd_index = g_unix_fd_list_append (fd_list, fds[1], &error);
|
|
close (fds[1]);
|
|
if (fd_index < 0) {
|
|
close (fds[0]);
|
|
g_object_unref (fd_list);
|
|
g_object_unref (connection);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
init->stream = g_unix_input_stream_new (fds[0], TRUE);
|
|
|
|
g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
|
|
g_dbus_connection_call_with_unix_fd_list (connection,
|
|
PORTAL_BUS_NAME,
|
|
PORTAL_OBJECT_PATH,
|
|
PORTAL_SECRET_INTERFACE,
|
|
"RetrieveSecret",
|
|
g_variant_new ("(h@a{sv})",
|
|
fd_index,
|
|
g_variant_builder_end (&options)),
|
|
G_VARIANT_TYPE ("(o)"),
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
-1,
|
|
fd_list,
|
|
g_task_get_cancellable (task),
|
|
on_portal_retrieve_secret,
|
|
task);
|
|
g_object_unref (fd_list);
|
|
}
|
|
|
|
static void
|
|
secret_file_backend_real_init_async (GAsyncInitable *initable,
|
|
int io_priority,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
gchar *path;
|
|
GFile *file;
|
|
GFile *dir;
|
|
SecretValue *password;
|
|
const gchar *envvar;
|
|
GTask *task;
|
|
GError *error = NULL;
|
|
InitClosure *init;
|
|
gboolean ret;
|
|
|
|
task = g_task_new (initable, cancellable, callback, user_data);
|
|
|
|
envvar = g_getenv ("SECRET_FILE_TEST_PATH");
|
|
if (envvar != NULL && *envvar != '\0')
|
|
path = g_strdup (envvar);
|
|
else {
|
|
path = g_build_filename (g_get_user_data_dir (),
|
|
"keyrings",
|
|
SECRET_COLLECTION_DEFAULT ".keyring",
|
|
NULL);
|
|
}
|
|
|
|
file = g_file_new_for_path (path);
|
|
g_free (path);
|
|
|
|
dir = g_file_get_parent (file);
|
|
if (dir == NULL) {
|
|
g_task_return_new_error (task,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_ARGUMENT,
|
|
"not a valid path");
|
|
g_object_unref (file);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ret = g_file_make_directory_with_parents (dir, cancellable, &error);
|
|
g_object_unref (dir);
|
|
if (!ret) {
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
|
|
g_clear_error (&error);
|
|
else {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (file);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
}
|
|
|
|
envvar = g_getenv ("SECRET_FILE_TEST_PASSWORD");
|
|
if (envvar != NULL && *envvar != '\0') {
|
|
password = secret_value_new (envvar, -1, "text/plain");
|
|
g_async_initable_new_async (SECRET_TYPE_FILE_COLLECTION,
|
|
io_priority,
|
|
cancellable,
|
|
on_collection_new_async,
|
|
task,
|
|
"file", file,
|
|
"password", password,
|
|
NULL);
|
|
g_object_unref (file);
|
|
secret_value_unref (password);
|
|
} else if (g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS) || g_getenv ("SNAP_NAME") != NULL) {
|
|
init = g_slice_new0 (InitClosure);
|
|
init->io_priority = io_priority;
|
|
init->file = file;
|
|
g_task_set_task_data (task, init, init_closure_free);
|
|
g_bus_get (G_BUS_TYPE_SESSION, cancellable, on_bus_get, task);
|
|
} else {
|
|
g_task_return_new_error (task,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_ARGUMENT,
|
|
"master password is not retrievable");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
secret_file_backend_real_init_finish (GAsyncInitable *initable,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (g_task_is_valid (result, initable), FALSE);
|
|
|
|
return g_task_propagate_boolean (G_TASK (result), error);
|
|
}
|
|
|
|
static void
|
|
secret_file_backend_async_initable_iface (GAsyncInitableIface *iface)
|
|
{
|
|
iface->init_async = secret_file_backend_real_init_async;
|
|
iface->init_finish = secret_file_backend_real_init_finish;
|
|
}
|
|
|
|
static void
|
|
on_collection_write (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
SecretFileCollection *collection =
|
|
SECRET_FILE_COLLECTION (source_object);
|
|
GTask *task = G_TASK (user_data);
|
|
GError *error = NULL;
|
|
|
|
if (!secret_file_collection_write_finish (collection, result, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
secret_file_backend_real_store (SecretBackend *backend,
|
|
const SecretSchema *schema,
|
|
GHashTable *attributes,
|
|
const gchar *collection,
|
|
const gchar *label,
|
|
SecretValue *value,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
SecretFileBackend *self = SECRET_FILE_BACKEND (backend);
|
|
GTask *task;
|
|
GError *error = NULL;
|
|
|
|
/* Warnings raised already */
|
|
if (schema != NULL && !_secret_attributes_validate (schema, attributes, G_STRFUNC, FALSE))
|
|
return;
|
|
|
|
task = g_task_new (self, cancellable, callback, user_data);
|
|
|
|
if (!secret_file_collection_replace (self->collection,
|
|
attributes,
|
|
label,
|
|
value,
|
|
&error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
secret_file_collection_write (self->collection,
|
|
cancellable,
|
|
on_collection_write,
|
|
task);
|
|
}
|
|
|
|
static gboolean
|
|
secret_file_backend_real_store_finish (SecretBackend *backend,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (g_task_is_valid (result, backend), FALSE);
|
|
|
|
return g_task_propagate_boolean (G_TASK (result), error);
|
|
}
|
|
|
|
static void
|
|
on_retrieve_secret (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
SecretRetrievable *retrievable = SECRET_RETRIEVABLE (source_object);
|
|
GTask *task = G_TASK (user_data);
|
|
SecretValue *value;
|
|
GError *error;
|
|
|
|
value = secret_retrievable_retrieve_secret_finish (retrievable,
|
|
result,
|
|
&error);
|
|
g_object_unref (retrievable);
|
|
if (value == NULL) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
}
|
|
g_task_return_pointer (task, value, secret_value_unref);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
secret_file_backend_real_lookup (SecretBackend *backend,
|
|
const SecretSchema *schema,
|
|
GHashTable *attributes,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
SecretFileBackend *self = SECRET_FILE_BACKEND (backend);
|
|
GTask *task;
|
|
GList *matches;
|
|
GVariant *variant;
|
|
SecretFileItem *item;
|
|
GError *error = NULL;
|
|
|
|
/* Warnings raised already */
|
|
if (schema != NULL && !_secret_attributes_validate (schema, attributes, G_STRFUNC, TRUE))
|
|
return;
|
|
|
|
task = g_task_new (self, cancellable, callback, user_data);
|
|
|
|
matches = secret_file_collection_search (self->collection, attributes);
|
|
|
|
if (matches == NULL) {
|
|
g_task_return_pointer (task, NULL, NULL);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
variant = g_variant_ref (matches->data);
|
|
g_list_free_full (matches, (GDestroyNotify)g_variant_unref);
|
|
|
|
item = _secret_file_item_decrypt (variant, self->collection, &error);
|
|
g_variant_unref (variant);
|
|
if (item == NULL) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
secret_retrievable_retrieve_secret (SECRET_RETRIEVABLE (item),
|
|
cancellable,
|
|
on_retrieve_secret,
|
|
task);
|
|
}
|
|
|
|
static SecretValue *
|
|
secret_file_backend_real_lookup_finish (SecretBackend *backend,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (g_task_is_valid (result, backend), NULL);
|
|
|
|
return g_task_propagate_pointer (G_TASK (result), error);
|
|
}
|
|
|
|
static void
|
|
secret_file_backend_real_clear (SecretBackend *backend,
|
|
const SecretSchema *schema,
|
|
GHashTable *attributes,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
SecretFileBackend *self = SECRET_FILE_BACKEND (backend);
|
|
GTask *task;
|
|
GError *error = NULL;
|
|
gboolean ret;
|
|
|
|
/* Warnings raised already */
|
|
if (schema != NULL && !_secret_attributes_validate (schema, attributes, G_STRFUNC, TRUE))
|
|
return;
|
|
|
|
task = g_task_new (self, cancellable, callback, user_data);
|
|
|
|
ret = secret_file_collection_clear (self->collection, attributes, &error);
|
|
if (error != NULL) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* No need to write as nothing has been removed. */
|
|
if (!ret) {
|
|
g_task_return_boolean (task, FALSE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
secret_file_collection_write (self->collection,
|
|
cancellable,
|
|
on_collection_write,
|
|
task);
|
|
}
|
|
|
|
static gboolean
|
|
secret_file_backend_real_clear_finish (SecretBackend *backend,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (g_task_is_valid (result, backend), FALSE);
|
|
|
|
return g_task_propagate_boolean (G_TASK (result), error);
|
|
}
|
|
|
|
static void
|
|
unref_objects (gpointer data)
|
|
{
|
|
GList *list = data;
|
|
|
|
g_list_free_full (list, g_object_unref);
|
|
}
|
|
|
|
static void
|
|
secret_file_backend_real_search (SecretBackend *backend,
|
|
const SecretSchema *schema,
|
|
GHashTable *attributes,
|
|
SecretSearchFlags flags,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
SecretFileBackend *self = SECRET_FILE_BACKEND (backend);
|
|
GTask *task;
|
|
GList *matches;
|
|
GList *results = NULL;
|
|
GList *l;
|
|
GError *error = NULL;
|
|
|
|
/* Warnings raised already */
|
|
if (schema != NULL && !_secret_attributes_validate (schema, attributes, G_STRFUNC, FALSE))
|
|
return;
|
|
|
|
task = g_task_new (self, cancellable, callback, user_data);
|
|
|
|
matches = secret_file_collection_search (self->collection, attributes);
|
|
for (l = matches; l; l = g_list_next (l)) {
|
|
SecretFileItem *item = _secret_file_item_decrypt (l->data, self->collection, &error);
|
|
if (item == NULL) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
results = g_list_append (results, item);
|
|
}
|
|
g_list_free_full (matches, (GDestroyNotify)g_variant_unref);
|
|
|
|
g_task_return_pointer (task, results, unref_objects);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static GList *
|
|
secret_file_backend_real_search_finish (SecretBackend *backend,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (g_task_is_valid (result, backend), NULL);
|
|
|
|
return g_task_propagate_pointer (G_TASK (result), error);
|
|
}
|
|
|
|
static void
|
|
secret_file_backend_backend_iface (SecretBackendInterface *iface)
|
|
{
|
|
iface->store = secret_file_backend_real_store;
|
|
iface->store_finish = secret_file_backend_real_store_finish;
|
|
iface->lookup = secret_file_backend_real_lookup;
|
|
iface->lookup_finish = secret_file_backend_real_lookup_finish;
|
|
iface->clear = secret_file_backend_real_clear;
|
|
iface->clear_finish = secret_file_backend_real_clear_finish;
|
|
iface->search = secret_file_backend_real_search;
|
|
iface->search_finish = secret_file_backend_real_search_finish;
|
|
}
|
|
|
|
gboolean
|
|
_secret_file_backend_check_portal_version (void)
|
|
{
|
|
GDBusConnection *connection;
|
|
GVariant *ret;
|
|
GVariant *value;
|
|
guint32 version;
|
|
GError *error = NULL;
|
|
|
|
connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
|
|
if (!connection) {
|
|
g_warning ("couldn't get session bus: %s", error->message);
|
|
g_error_free (error);
|
|
return FALSE;
|
|
}
|
|
|
|
ret = g_dbus_connection_call_sync (connection,
|
|
PORTAL_BUS_NAME,
|
|
PORTAL_OBJECT_PATH,
|
|
"org.freedesktop.DBus.Properties",
|
|
"Get",
|
|
g_variant_new ("(ss)",
|
|
PORTAL_SECRET_INTERFACE,
|
|
"version"),
|
|
G_VARIANT_TYPE ("(v)"),
|
|
0, -1, NULL, &error);
|
|
g_object_unref (connection);
|
|
if (!ret) {
|
|
g_info ("secret portal is not available: %s", error->message);
|
|
g_error_free (error);
|
|
return FALSE;
|
|
}
|
|
|
|
g_variant_get (ret, "(v)", &value);
|
|
g_variant_unref (ret);
|
|
version = g_variant_get_uint32 (value);
|
|
g_variant_unref (value);
|
|
if (version != PORTAL_SECRET_VERSION) {
|
|
g_info ("secret portal version mismatch: %u != %u", version, PORTAL_SECRET_VERSION);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|