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);
......
This diff is collapsed.
/* 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
*/
#if !defined (__SECRET_INSIDE_HEADER__) && !defined (SECRET_COMPILATION)
#error "Only <libsecret/secret.h> can be included directly."
#endif
#ifndef __SECRET_FILE_BACKEND_H__
#define __SECRET_FILE_BACKEND_H__
#include <glib-object.h>
G_BEGIN_DECLS
#define SECRET_TYPE_FILE_BACKEND (secret_file_backend_get_type ())
G_DECLARE_FINAL_TYPE (SecretFileBackend, secret_file_backend, SECRET, FILE_BACKEND, GObject)
gboolean _secret_file_backend_check_portal_version (void);
G_END_DECLS
#endif /* __SECRET_FILE_BACKEND_H__ */
This diff is collapsed.
/* 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
*/
#if !defined (__SECRET_INSIDE_HEADER__) && !defined (SECRET_COMPILATION)
#error "Only <libsecret/secret.h> can be included directly."
#endif
#ifndef __SECRET_FILE_COLLECTION_H__
#define __SECRET_FILE_COLLECTION_H__
#include "secret-file-item.h"
#include "secret-value.h"
G_BEGIN_DECLS
#define SECRET_TYPE_FILE_COLLECTION (secret_file_collection_get_type ())
G_DECLARE_FINAL_TYPE (SecretFileCollection, secret_file_collection, SECRET, FILE_COLLECTION, GObject)
gboolean secret_file_collection_replace (SecretFileCollection *self,
GHashTable *attributes,
const gchar *label,
SecretValue *value,
GError **error);
GList *secret_file_collection_search (SecretFileCollection *self,
GHashTable *attributes);
gboolean secret_file_collection_clear (SecretFileCollection *self,
GHashTable *attributes,
GError **error);
void secret_file_collection_write (SecretFileCollection *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean secret_file_collection_write_finish
(SecretFileCollection *self,
GAsyncResult *result,
GError **error);
SecretFileItem *_secret_file_item_decrypt
(GVariant *encrypted,
SecretFileCollection *collection,
GError **error);
G_END_DECLS
#endif /* __SECRET_FILE_COLLECTION_H__ */
/* 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-file-item.h"
#include "secret-retrievable.h"
#include "secret-value.h"
struct _SecretFileItem
{
GObject parent;
GHashTable *attributes;
gchar *label;
guint64 created;
guint64 modified;
SecretValue *value;
GVariant *encrypted;
};
static void secret_file_item_retrievable_iface (SecretRetrievableInterface *iface);
G_DEFINE_TYPE_WITH_CODE (SecretFileItem, secret_file_item, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (SECRET_TYPE_RETRIEVABLE, secret_file_item_retrievable_iface);
);
enum {
PROP_0,
PROP_ATTRIBUTES,
PROP_LABEL,
PROP_CREATED,
PROP_MODIFIED,
PROP_VALUE
};
static void
secret_file_item_init (SecretFileItem *self)
{
}
static void
secret_file_item_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SecretFileItem *self = SECRET_FILE_ITEM (object);
switch (prop_id) {
case PROP_ATTRIBUTES:
self->attributes = g_value_dup_boxed (value);
break;
case PROP_LABEL:
self->label = g_value_dup_string (value);
break;
case PROP_CREATED:
self->created = g_value_get_uint64 (value);
break;
case PROP_MODIFIED:
self->modified = g_value_get_uint64 (value);
break;
case PROP_VALUE:
self->value = g_value_dup_boxed (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
secret_file_item_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SecretFileItem *self = SECRET_FILE_ITEM (object);
switch (prop_id) {
case PROP_ATTRIBUTES:
g_value_set_boxed (value, self->attributes);
break;
case PROP_LABEL:
g_value_set_string (value, self->label);
break;
case PROP_CREATED:
g_value_set_uint64 (value, self->created);
break;
case PROP_MODIFIED:
g_value_set_uint64 (value, self->modified);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
secret_file_item_finalize (GObject *object)
{
SecretFileItem *self = SECRET_FILE_ITEM (object);
g_hash_table_unref (self->attributes);
g_free (self->label);
secret_value_unref (self->value);
G_OBJECT_CLASS (secret_file_item_parent_class)->finalize (object);
}
static void
secret_file_item_class_init (SecretFileItemClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->set_property = secret_file_item_set_property;
gobject_class->get_property = secret_file_item_get_property;
gobject_class->finalize = secret_file_item_finalize;
g_object_class_override_property (gobject_class, PROP_ATTRIBUTES, "attributes");
g_object_class_override_property (gobject_class, PROP_LABEL, "label");
g_object_class_override_property (gobject_class, PROP_CREATED, "created");
g_object_class_override_property (gobject_class, PROP_MODIFIED, "modified");
g_object_class_install_property (gobject_class, PROP_VALUE,
g_param_spec_boxed ("value", "Value", "Value",
SECRET_TYPE_VALUE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
}
static void
secret_file_item_retrieve_secret (SecretRetrievable *retrievable,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
SecretFileItem *self = SECRET_FILE_ITEM (retrievable);
GTask *task = g_task_new (retrievable, cancellable, callback, user_data);
g_task_return_pointer (task,
secret_value_ref (self->value),
secret_value_unref);
g_object_unref (task);
}
static SecretValue *
secret_file_item_retrieve_secret_finish (SecretRetrievable *retrievable,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, retrievable), NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
static void
secret_file_item_retrievable_iface (SecretRetrievableInterface *iface)
{
iface->retrieve_secret = secret_file_item_retrieve_secret;
iface->retrieve_secret_finish = secret_file_item_retrieve_secret_finish;
}
static GHashTable *
variant_to_attributes (GVariant *variant)
{
GVariantIter iter;
gchar *key;
gchar *value;
GHashTable *attributes;
attributes = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_free);
g_variant_iter_init (&iter, variant);
while (g_variant_iter_next (&iter, "{ss}", &key, &value))
g_hash_table_insert (attributes, key, value);
return attributes;
}
SecretFileItem *
secret_file_item_deserialize (GVariant *serialized)
{
GVariant *attributes_variant;
GHashTable *attributes;
const gchar *label;
guint64 created;
guint64 modified;
GVariant *array;
const gchar *secret;
gsize n_secret;
SecretValue *value;
SecretFileItem *result;
g_variant_get (serialized, "(@a{ss}&stt@ay)",
&attributes_variant, &label, &created, &modified, &array);
secret = g_variant_get_fixed_array (array, &n_secret, sizeof(gchar));
value = secret_value_new (secret, n_secret, "text/plain");
attributes = variant_to_attributes (attributes_variant);
g_variant_unref (attributes_variant);
result = g_object_new (SECRET_TYPE_FILE_ITEM,
"attributes", attributes,
"label", label,
"created", created,
"modified", modified,
"value", value,
NULL);
g_hash_table_unref (attributes);
g_variant_unref (array);
secret_value_unref (value);
return result;
}
GVariant *
secret_file_item_serialize (SecretFileItem *self)
{
GVariantBuilder builder;
GHashTableIter iter;
gpointer key;
gpointer value;
GVariant *variant;
const gchar *secret;
gsize n_secret;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
g_hash_table_iter_init (&iter, self->attributes);
while (g_hash_table_iter_next (&iter, &key, &value))
g_variant_builder_add (&builder, "{ss}", key, value);
secret = secret_value_get (self->value, &n_secret);
variant = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
secret, n_secret, sizeof(guint8));
variant = g_variant_new ("(@a{ss}stt@ay)",
g_variant_builder_end (&builder),
self->label,
self->created,
self->modified,
variant);
g_variant_get_data (variant); /* force serialize */
return g_variant_ref_sink (variant);
}
/* 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
*/
#if !defined (__SECRET_INSIDE_HEADER__) && !defined (SECRET_COMPILATION)
#error "Only <libsecret/secret.h> can be included directly."
#endif
#ifndef __SECRET_FILE_ITEM_H__
#define __SECRET_FILE_ITEM_H__
#include <glib-object.h>
G_BEGIN_DECLS
#define SECRET_TYPE_FILE_ITEM (secret_file_item_get_type ())
G_DECLARE_FINAL_TYPE (SecretFileItem, secret_file_item, SECRET, FILE_ITEM, GObject)
SecretFileItem *secret_file_item_deserialize (GVariant *serialized);
GVariant *secret_file_item_serialize (SecretFileItem *self);
G_END_DECLS
#endif /* __SECRET_FILE_ITEM_H__ */
......@@ -32,6 +32,7 @@ typedef enum {
SECRET_ERROR_IS_LOCKED = 2,
SECRET_ERROR_NO_SUCH_OBJECT = 3,
SECRET_ERROR_ALREADY_EXISTS = 4,
SECRET_ERROR_INVALID_FILE_FORMAT = 5,
} SecretError;
#define SECRET_COLLECTION_DEFAULT "default"
......
#include "config.h"
#include "egg/egg-testing.h"
#include "secret-file-collection.h"
#include "secret-retrievable.h"
#include "secret-schema.h"
typedef struct {
gchar *directory;
GMainLoop *loop;
SecretFileCollection *collection;
} Test;
static void
on_new_async (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
Test *test = user_data;
GObject *object;
GError *error = NULL;
object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
result,
&error);
test->collection = SECRET_FILE_COLLECTION (object);
g_main_loop_quit (test->loop);
g_assert_no_error (error);
}
static void
setup (Test *test,
gconstpointer data)
{
GFile *file;
gchar *path;
SecretValue *password;
gchar *fixture = NULL;
if (data != NULL)
fixture = g_build_filename (SRCDIR, "libsecret", "fixtures", data, NULL);
test->directory = egg_tests_create_scratch_directory (fixture, NULL);
g_free (fixture);
test->loop = g_main_loop_new (NULL, TRUE);
path = g_build_filename (test->directory, "default.keyring", NULL);
file = g_file_new_for_path (path);
g_free (path);
password = secret_value_new ("password", -1, "text/plain");
g_async_initable_new_async (SECRET_TYPE_FILE_COLLECTION,
G_PRIORITY_DEFAULT,
NULL,
on_new_async,
test,
"file", file,
"password", password,
NULL);
g_object_unref (file);
secret_value_unref (password);
g_main_loop_run (test->loop);
}
static void
teardown (Test *test,
gconstpointer unused)
{
egg_tests_remove_scratch_directory (test->directory);
g_free (test->directory);
g_clear_object (&test->collection);
g_main_loop_unref (test->loop);
}
static void
test_init (Test *test,
gconstpointer unused)
{
}
static void
test_replace (Test *test,
gconstpointer unused)
{
GHashTable *attributes;
SecretValue *value;
GError *error = NULL;
gboolean ret;
attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
g_hash_table_insert (attributes, g_strdup ("foo"), g_strdup ("a"));
g_hash_table_insert (attributes, g_strdup ("bar"), g_strdup ("b"));
g_hash_table_insert (attributes, g_strdup ("baz"), g_strdup ("c"));
value = secret_value_new ("test1", -1, "text/plain");
ret = secret_file_collection_replace (test->collection,
attributes, "label", value,
&error);
g_assert_no_error (error);
g_assert_true (ret);
secret_value_unref (value);
value = secret_value_new ("test2", -1, "text/plain");
ret = secret_file_collection_replace (test->collection,
attributes, "label", value,
&error);
g_assert_no_error (error);
g_assert_true (ret);
secret_value_unref (value);
g_hash_table_unref (attributes);