Commit a6530135 authored by Daiki Ueno's avatar Daiki Ueno

Merge branch 'wip/dueno/local-file' into 'master'

secret-backend: Add local-storage backend

See merge request !6
parents 29cc1415 a278adc2
Pipeline #122981 passed with stages
in 13 minutes and 7 seconds
......@@ -52,7 +52,10 @@ dist-hook: dist-check-valac
distcleancheck_listfiles = \
find . -name '*.gc[dn][oa]' -prune -o -type f -print
TESTS_ENVIRONMENT = LD_LIBRARY_PATH=$(builddir)/.libs GI_TYPELIB_PATH=$(builddir)
TESTS_ENVIRONMENT = \
LD_LIBRARY_PATH=$(builddir)/.libs \
GI_TYPELIB_PATH=$(builddir) \
abs_top_builddir=$(abs_top_builddir)
TEST_EXTENSIONS = .py .js
# Default executable tests
......
......@@ -171,3 +171,71 @@ egg_tests_run_with_loop (void)
return ret;
}
void
egg_tests_copy_scratch_file (const gchar *directory,
const gchar *filename)
{
GError *error = NULL;
gchar *basename;
gchar *contents;
gchar *destination;
gsize length;
g_assert (directory);
g_file_get_contents (filename, &contents, &length, &error);
g_assert_no_error (error);
basename = g_path_get_basename (filename);
destination = g_build_filename (directory, basename, NULL);
g_free (basename);
g_file_set_contents (destination, contents, length, &error);
g_assert_no_error (error);
g_free (destination);
g_free (contents);
}
gchar *
egg_tests_create_scratch_directory (const gchar *file_to_copy,
...)
{
gchar *basename;
gchar *directory;
va_list va;
basename = g_path_get_basename (g_get_prgname ());
directory = g_strdup_printf ("/tmp/scratch-%s.XXXXXX", basename);
g_free (basename);
if (!g_mkdtemp (directory))
g_assert_not_reached ();
va_start (va, file_to_copy);
while (file_to_copy != NULL) {
egg_tests_copy_scratch_file (directory, file_to_copy);
file_to_copy = va_arg (va, const gchar *);
}
va_end (va);
return directory;
}
void
egg_tests_remove_scratch_directory (const gchar *directory)
{
gchar *argv[] = { "rm", "-rf", (gchar *)directory, NULL };
GError *error = NULL;
gint rm_status;
g_assert_cmpstr (directory, !=, "");
g_assert_cmpstr (directory, !=, "/");
g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL,
NULL, NULL, NULL, &rm_status, &error);
g_assert_no_error (error);
g_assert_cmpint (rm_status, ==, 0);
}
......@@ -56,4 +56,12 @@ void egg_test_wait_idle (void);
gint egg_tests_run_with_loop (void);
#endif /* EGG_DH_H_ */
void egg_tests_copy_scratch_file (const gchar *directory,
const gchar *file_to_copy);
gchar * egg_tests_create_scratch_directory (const gchar *file_to_copy,
...) G_GNUC_NULL_TERMINATED;
void egg_tests_remove_scratch_directory (const gchar *directory);
#endif /* EGG_TESTING_H_ */
......@@ -60,6 +60,17 @@ libsecret_PRIVATE = \
libsecret/secret-util.c \
$(NULL)
if WITH_GCRYPT
libsecret_PRIVATE += \
libsecret/secret-file-backend.h \
libsecret/secret-file-backend.c \
libsecret/secret-file-collection.h \
libsecret/secret-file-collection.c \
libsecret/secret-file-item.h \
libsecret/secret-file-item.c \
$(NULL)
endif
libsecret_@SECRET_MAJOR@_la_SOURCES = \
$(libsecret_PUBLIC) \
$(libsecret_PRIVATE) \
......@@ -247,6 +258,15 @@ test_session_LDADD = $(libsecret_LIBS)
test_value_SOURCES = libsecret/test-value.c
test_value_LDADD = $(libsecret_LIBS)
if WITH_GCRYPT
C_TESTS += \
test-file-collection \
$(NULL)
test_file_collection_SOURCES = libsecret/test-file-collection.c
test_file_collection_LDADD = $(libsecret_LIBS)
endif
JS_TESTS = \
libsecret/test-js-lookup.js \
libsecret/test-js-clear.js \
......@@ -377,4 +397,5 @@ EXTRA_DIST += \
libsecret/mock-service-prompt.py \
$(JS_TESTS) \
$(PY_TESTS) \
libsecret/fixtures \
$(NULL)
......@@ -35,6 +35,14 @@ libsecret_headers = [
'secret-value.h',
]
if with_gcrypt
libsecret_sources += [
'secret-file-backend.c',
'secret-file-collection.c',
'secret-file-item.c',
]
endif
version_numbers = meson.project_version().split('.')
version_major = version_numbers[0].to_int()
version_minor = version_numbers[1].to_int()
......@@ -168,7 +176,7 @@ pkg.generate(description: 'GObject bindings for Secret Service API (Unstable)',
requires: libsecret)
# Tests
mock_cflags = [
test_cflags = [
libsecret_cflags,
'-DSRCDIR="@0@"'.format(meson.source_root()),
]
......@@ -176,7 +184,7 @@ mock_cflags = [
mock_service_lib = static_library('mock-service',
'mock-service.c',
dependencies: glib_deps,
c_args: mock_cflags,
c_args: test_cflags,
include_directories: config_h_dir,
)
......@@ -193,6 +201,12 @@ test_names = [
'test-collection',
]
if with_gcrypt
test_names += [
'test-file-collection',
]
endif
foreach _test : test_names
test_bin = executable(_test,
......@@ -200,7 +214,7 @@ foreach _test : test_names
dependencies: libsecret_dep,
link_with: mock_service_lib,
include_directories: config_h_dir,
c_args: libsecret_cflags,
c_args: test_cflags,
)
test(_test, test_bin)
......
......@@ -15,6 +15,11 @@
#include "config.h"
#include "secret-backend.h"
#ifdef WITH_GCRYPT
#include "secret-file-backend.h"
#endif
#include "secret-private.h"
#include "libsecret/secret-enum-types.h"
......@@ -144,13 +149,24 @@ backend_get_impl_type (void)
GIOExtension *e;
GIOExtensionPoint *ep;
envvar = g_getenv ("SECRET_BACKEND");
if (envvar == NULL || *envvar == '\0')
extension_name = "service";
else
extension_name = envvar;
g_type_ensure (secret_service_get_type ());
#ifdef WITH_GCRYPT
g_type_ensure (secret_file_backend_get_type ());
#endif
#ifdef WITH_GCRYPT
if (g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS) &&
_secret_file_backend_check_portal_version ())
extension_name = "file";
else
#endif
{
envvar = g_getenv ("SECRET_BACKEND");
if (envvar == NULL || *envvar == '\0')
extension_name = "service";
else
extension_name = envvar;
}
ep = g_io_extension_point_lookup (SECRET_BACKEND_EXTENSION_POINT_NAME);
e = g_io_extension_point_get_extension_by_name (ep, extension_name);
......
/* 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;
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);
}
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);