[gnome-keyring/wip/dueno/secret-portal] dbus: Implement secret portal backend



commit 9402c0a95a5a1a6e8f0a34b2a48d966b52bd03c6
Author: Daiki Ueno <dueno src gnome org>
Date:   Sat Jul 27 05:36:41 2019 +0200

    dbus: Implement secret portal backend

 .gitignore                                         |   4 +
 daemon/.gitignore                                  |   1 +
 daemon/Makefile.am                                 |   6 +-
 daemon/dbus/Makefile.am                            |  35 +-
 daemon/dbus/gkd-secret-portal.c                    | 438 +++++++++++++++++++++
 daemon/dbus/gkd-secret-portal.h                    |  33 ++
 daemon/dbus/gkd-secret-service.c                   |   7 +
 daemon/dbus/gkd-secret-types.h                     |   3 +
 .../dbus/org.freedesktop.impl.portal.Request.xml   |  47 +++
 daemon/dbus/org.freedesktop.impl.portal.Secret.xml |  65 +++
 daemon/dbus/test-dbus-portal.c                     | 215 ++++++++++
 daemon/dbus/test-service.c                         |   1 +
 daemon/gnome-keyring.portal                        |   4 +
 .../org.freedesktop.impl.portal.Secret.service.in  |   3 +
 14 files changed, 859 insertions(+), 3 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index f153d290..1f57c5a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -126,6 +126,10 @@ depcomp
 /daemon/dbus/gkd-internal-generated.h
 /daemon/dbus/gkd-secrets-generated.c
 /daemon/dbus/gkd-secrets-generated.h
+/daemon/dbus/gkd-portal-generated.c
+/daemon/dbus/gkd-portal-generated.h
+/daemon/dbus/gkd-portal-request-generated.c
+/daemon/dbus/gkd-portal-request-generated.h
 
 /pkcs11/gkm/tests/test-attributes
 /pkcs11/gkm/tests/test-certificate
diff --git a/daemon/.gitignore b/daemon/.gitignore
index a6d75155..4db92394 100644
--- a/daemon/.gitignore
+++ b/daemon/.gitignore
@@ -1,6 +1,7 @@
 /gnome-keyring-daemon
 /org.gnome.keyring.service
 /org.freedesktop.secrets.service
+/org.freedesktop.impl.portal.Secret.service
 /gnome-keyring-pkcs11.desktop
 /gnome-keyring-pkcs11.desktop.in
 /gnome-keyring-secrets.desktop
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 48c1652a..60b2f794 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -32,7 +32,8 @@ gnome_keyring_daemon_CFLAGS = \
 
 service_in_files = \
        daemon/org.gnome.keyring.service.in \
-       daemon/org.freedesktop.secrets.service.in
+       daemon/org.freedesktop.secrets.service.in \
+       daemon/org.freedesktop.impl.portal.Secret.service.in
 servicedir = $(DBUS_SERVICES_DIR)
 service_DATA = $(service_in_files:.service.in=.service)
 
@@ -47,6 +48,9 @@ desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
 .desktop.in.in.desktop.in:
        $(AM_V_GEN) $(MSGFMT) --desktop --template $< -d $(top_srcdir)/po -o $@
 
+portaldir = $(datadir)/xdg-desktop-portal/portals
+dist_portal_DATA = daemon/gnome-keyring.portal
+
 EXTRA_DIST += \
        $(service_in_files) \
        $(desktop_in_files) \
diff --git a/daemon/dbus/Makefile.am b/daemon/dbus/Makefile.am
index 9d0b2e1a..8e03be2c 100644
--- a/daemon/dbus/Makefile.am
+++ b/daemon/dbus/Makefile.am
@@ -32,10 +32,30 @@ daemon/dbus/gkd-internal-generated.h: daemon/dbus/org.gnome.keyring.InternalUnsu
 daemon/dbus/gkd-internal-generated.c: daemon/dbus/gkd-internal-generated.h
        @: # generated as a side-effect
 
+daemon/dbus/gkd-portal-generated.h: daemon/dbus/org.freedesktop.impl.portal.Secret.xml
+       $(AM_V_GEN) gdbus-codegen --interface-prefix org.freedesktop.impl.portal.Secret. \
+       --generate-c-code $(srcdir)/daemon/dbus/gkd-portal-generated \
+       --c-namespace Gkd \
+       --annotate "org.freedesktop.impl.portal.Secret" "org.gtk.GDBus.C.Name" ExportedPortal \
+       $(srcdir)/daemon/dbus/org.freedesktop.impl.portal.Secret.xml
+daemon/dbus/gkd-portal-generated.c: daemon/dbus/gkd-portal-generated.h
+       @: # generated as a side-effect
+
+daemon/dbus/gkd-portal-request-generated.h: daemon/dbus/org.freedesktop.impl.portal.Request.xml
+       $(AM_V_GEN) gdbus-codegen --interface-prefix org.freedesktop.impl.portal.Request. \
+       --generate-c-code $(srcdir)/daemon/dbus/gkd-portal-request-generated \
+       --c-namespace Gkd \
+       --annotate "org.freedesktop.impl.portal.Request" "org.gtk.GDBus.C.Name" ExportedPortalRequest \
+       $(srcdir)/daemon/dbus/org.freedesktop.impl.portal.Request.xml
+daemon/dbus/gkd-portal-request-generated.c: daemon/dbus/gkd-portal-request-generated.h
+       @: # generated as a side-effect
+
 EXTRA_DIST += \
        daemon/dbus/org.freedesktop.Secrets.xml \
        daemon/dbus/org.gnome.keyring.Daemon.xml \
        daemon/dbus/org.gnome.keyring.InternalUnsupportedGuiltRiddenInterface.xml \
+       daemon/dbus/org.freedesktop.impl.portal.Secret.xml \
+       daemon/dbus/org.freedesktop.impl.portal.Request.xml \
        $(NULL)
 
 BUILT_SOURCES += \
@@ -44,7 +64,11 @@ BUILT_SOURCES += \
        daemon/dbus/gkd-internal-generated.c \
        daemon/dbus/gkd-internal-generated.h \
        daemon/dbus/gkd-secrets-generated.c \
-       daemon/dbus/gkd-secrets-generated.h
+       daemon/dbus/gkd-secrets-generated.h \
+       daemon/dbus/gkd-portal-generated.c \
+       daemon/dbus/gkd-portal-generated.h \
+       daemon/dbus/gkd-portal-request-generated.c \
+       daemon/dbus/gkd-portal-request-generated.h
 
 libgkd_dbus_la_SOURCES = \
        $(BUILT_SOURCES) \
@@ -68,6 +92,8 @@ libgkd_dbus_la_SOURCES = \
        daemon/dbus/gkd-secret-lock.h \
        daemon/dbus/gkd-secret-objects.c \
        daemon/dbus/gkd-secret-objects.h \
+       daemon/dbus/gkd-secret-portal.c \
+       daemon/dbus/gkd-secret-portal.h \
        daemon/dbus/gkd-secret-property.c \
        daemon/dbus/gkd-secret-property.h \
        daemon/dbus/gkd-secret-prompt.c \
@@ -125,7 +151,8 @@ daemon_dbus_TESTS = \
        test-dbus-search \
        test-dbus-items \
        test-dbus-signals \
-       test-dbus-lock
+       test-dbus-lock \
+       test-dbus-portal
 
 test_dbus_util_SOURCES = daemon/dbus/test-dbus-util.c
 test_dbus_util_LDADD = $(daemon_dbus_LIBS)
@@ -144,5 +171,9 @@ test_dbus_signals_CFLAGS = $(daemon_dbus_CFLAGS)
 test_dbus_lock_SOURCES = daemon/dbus/test-dbus-lock.c
 test_dbus_lock_LDADD = $(daemon_dbus_LIBS)
 
+test_dbus_portal_SOURCES = daemon/dbus/test-dbus-portal.c
+test_dbus_portal_LDADD = $(daemon_dbus_LIBS)
+test_dbus_portal_CFLAGS = $(daemon_dbus_CFLAGS)
+
 check_PROGRAMS += $(daemon_dbus_TESTS)
 TESTS += $(daemon_dbus_TESTS)
diff --git a/daemon/dbus/gkd-secret-portal.c b/daemon/dbus/gkd-secret-portal.c
new file mode 100644
index 00000000..4663b7b9
--- /dev/null
+++ b/daemon/dbus/gkd-secret-portal.c
@@ -0,0 +1,438 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 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 License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gkd-secret-portal.h"
+
+#include <gck/gck.h>
+#include "pkcs11/pkcs11i.h"
+#include <gcrypt.h>
+
+#include "gkd-portal-generated.h"
+#include "gkd-portal-request-generated.h"
+#include "gkd-secret-property.h"
+#include "gkd-secret-service.h"
+#include <gio/gunixfdlist.h>
+#include <gio/gunixoutputstream.h>
+
+#define PORTAL_DEFAULT_KEY_SIZE 64
+
+static gboolean
+portal_method_retrieve_secret (GkdExportedPortal *skeleton,
+                              GDBusMethodInvocation *invocation,
+                              GUnixFDList *fd_list,
+                              const gchar *arg_handle,
+                              const gchar *arg_app_id,
+                              GVariant *arg_fd,
+                              GVariant *arg_options,
+                              GkdSecretPortal *self);
+
+struct _GkdSecretPortal {
+       GObject parent;
+       GkdSecretService *service;
+       GkdExportedPortal *skeleton;
+       GkdExportedPortalRequest *request_skeleton;
+       gchar *collection;
+       GCancellable *cancellable;
+};
+
+G_DEFINE_TYPE (GkdSecretPortal, gkd_secret_portal, G_TYPE_OBJECT);
+
+enum {
+       PROP_0,
+       PROP_SERVICE
+};
+
+static void
+gkd_secret_portal_init (GkdSecretPortal *self)
+{
+#if WITH_DEBUG
+       const gchar *collection = g_getenv ("GNOME_KEYRING_TEST_LOGIN");
+       if (collection && collection[0])
+               self->collection = g_strdup (collection);
+       else
+#endif
+               self->collection = g_strdup ("login");
+       self->cancellable = g_cancellable_new ();
+}
+
+static void
+gkd_secret_portal_constructed (GObject *object)
+{
+       GkdSecretPortal *self = GKD_SECRET_PORTAL (object);
+       GDBusConnection *connection = gkd_secret_service_get_connection (self->service);
+       GError *error = NULL;
+
+       self->skeleton = gkd_exported_portal_skeleton_new ();
+       g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->skeleton),
+                                         connection,
+                                         PORTAL_SERVICE_PATH,
+                                         &error);
+
+       if (error != NULL) {
+               g_warning ("could not register portal interface service on session bus: %s", error->message);
+               g_clear_error (&error);
+       }
+
+       g_signal_connect (self->skeleton, "handle-retrieve-secret",
+                         G_CALLBACK (portal_method_retrieve_secret), self);
+
+       G_OBJECT_CLASS (gkd_secret_portal_parent_class)->constructed (object);
+}
+
+static void
+gkd_secret_portal_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+       GkdSecretPortal *self = GKD_SECRET_PORTAL (object);
+
+       switch (prop_id) {
+       case PROP_SERVICE:
+               self->service = g_value_dup_object (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gkd_secret_portal_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+gkd_secret_portal_finalize (GObject *object)
+{
+       GkdSecretPortal *self = GKD_SECRET_PORTAL (object);
+
+       g_clear_object (&self->skeleton);
+       g_clear_object (&self->request_skeleton);
+       g_free (self->collection);
+       g_object_unref (self->cancellable);
+
+       G_OBJECT_CLASS (gkd_secret_portal_parent_class)->finalize (object);
+}
+
+static void
+gkd_secret_portal_class_init (GkdSecretPortalClass *klass)
+{
+       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+       gobject_class->constructed = gkd_secret_portal_constructed;
+       gobject_class->set_property = gkd_secret_portal_set_property;
+       gobject_class->get_property = gkd_secret_portal_get_property;
+       gobject_class->finalize = gkd_secret_portal_finalize;
+
+       g_object_class_install_property (gobject_class, PROP_SERVICE,
+               g_param_spec_object ("service", "Service", "Secret Service",
+                                    GKD_SECRET_TYPE_SERVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static gboolean
+request_method_close (GkdExportedPortalRequest *skeleton,
+                     GDBusMethodInvocation *invocation,
+                     GkdSecretPortal *self)
+{
+       g_cancellable_cancel (self->cancellable);
+       g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (skeleton));
+       return TRUE;
+}
+
+static gboolean
+create_application_attributes (const char *app_id,
+                              GckBuilder *builder)
+{
+       GVariantBuilder attributes;
+
+       g_variant_builder_init (&attributes, G_VARIANT_TYPE ("a{ss}"));
+       g_variant_builder_add (&attributes, "{ss}", "app_id", app_id);
+
+       return gkd_secret_property_parse_fields (g_variant_builder_end (&attributes),
+                                                builder);
+}
+
+static gboolean
+unlock_collection (GkdSecretPortal *self,
+                  GckObject *collection,
+                  GError **error)
+{
+       GckBuilder builder = GCK_BUILDER_INIT;
+       GckSession *session;
+       GckObject *object;
+
+       session = gkd_secret_service_internal_pkcs11_session (self->service);
+       gck_builder_add_ulong (&builder, CKA_CLASS, CKO_G_CREDENTIAL);
+       gck_builder_add_ulong (&builder, CKA_G_OBJECT,
+                              gck_object_get_handle (collection));
+       gck_builder_add_boolean (&builder, CKA_GNOME_TRANSIENT, TRUE);
+       gck_builder_add_data (&builder, CKA_VALUE, NULL, 0);
+
+       object = gck_session_create_object (session, gck_builder_end (&builder),
+                                           self->cancellable, error);
+       if (object == NULL)
+               return FALSE;
+       g_object_unref (object);
+
+       return TRUE;
+}
+
+static gboolean
+ensure_collection (GkdSecretPortal *self,
+                  GError **error)
+{
+       GckBuilder builder = GCK_BUILDER_INIT;
+       GckSession *session;
+       GList *objects;
+       gpointer data;
+       gsize n_data;
+       gboolean retval = TRUE;
+
+       /* Find login collection */
+       session = gkd_secret_service_internal_pkcs11_session (self->service);
+       gck_builder_add_ulong (&builder, CKA_CLASS, CKO_G_COLLECTION);
+       gck_builder_add_string (&builder, CKA_ID, self->collection);
+       objects = gck_session_find_objects (session, gck_builder_end (&builder),
+                                           NULL, error);
+       if (*error != NULL)
+               return FALSE;
+       if (objects == NULL) {
+               g_set_error (error,
+                            G_DBUS_ERROR,
+                            G_DBUS_ERROR_FAILED,
+                            "Collection %s doesn't exist",
+                            self->collection);
+               return FALSE;
+       }
+
+       /* Check if it is locked */
+       data = gck_object_get_data (objects->data, CKA_G_LOCKED,
+                                   self->cancellable, &n_data, error);
+       if (data == NULL)
+               return FALSE;
+       if (n_data != 1) {
+               g_set_error (error,
+                            G_DBUS_ERROR,
+                            G_DBUS_ERROR_FAILED,
+                            "couldn't check if %s is locked",
+                            self->collection);
+               return FALSE;
+       }
+
+       /* Unlock the collection if it is locked */
+       if (*((CK_BBOOL*)data) == CK_TRUE)
+               retval = unlock_collection (self, objects->data, error);
+       gck_list_unref_free (objects);
+
+       return retval;
+}
+
+static guint8 *
+lookup_secret_value (GkdSecretPortal *self,
+                    const char *app_id,
+                    gsize *n_value,
+                    GError **error)
+{
+       GckBuilder builder = GCK_BUILDER_INIT;
+       GckObject *search;
+       GckSession *session;
+       gpointer data;
+       gsize n_data;
+
+       if (!create_application_attributes (app_id, &builder)) {
+               gck_builder_clear (&builder);
+               g_set_error (error,
+                            G_DBUS_ERROR,
+                            G_DBUS_ERROR_FAILED,
+                            "Invalid data in attributes argument");
+               return NULL;
+       }
+
+       /* Find items matching the collection and fields */
+       gck_builder_add_ulong (&builder, CKA_CLASS, CKO_G_SEARCH);
+       gck_builder_add_boolean (&builder, CKA_TOKEN, FALSE);
+       gck_builder_add_string (&builder, CKA_G_COLLECTION, self->collection);
+
+       /* Create the search object */
+       session = gkd_secret_service_internal_pkcs11_session (self->service);
+       search = gck_session_create_object (session,
+                                           gck_builder_end (&builder),
+                                           NULL, error);
+       if (search == NULL)
+               return NULL;
+
+       /* Get the matched item handles, and delete the search object */
+       data = gck_object_get_data (search, CKA_G_MATCHED, NULL, &n_data, error);
+       gck_object_destroy (search, NULL, NULL);
+       g_object_unref (search);
+
+       if (data == NULL)
+               return NULL;
+
+       if (n_data > 0) {
+               /* Return the first matching item if any */
+               GList *items;
+               guint8 *value;
+
+               /* Build a list of object handles */
+               items = gck_objects_from_handle_array (session,
+                                                      data,
+                                                      n_data / sizeof (CK_OBJECT_HANDLE));
+               g_free (data);
+
+               value = gck_object_get_data (GCK_OBJECT (items->data),
+                                            CKA_VALUE,
+                                            NULL,
+                                            n_value,
+                                            error);
+               gck_list_unref_free (items);
+               return value;
+       }
+
+       return NULL;
+}
+
+static guint8 *
+create_secret_value (GkdSecretPortal *self,
+                    const char *app_id,
+                    gsize *n_value,
+                    GError **error)
+{
+       GckBuilder builder = GCK_BUILDER_INIT;
+       GckObject *item;
+       GckSession *session;
+       guint8 *value;
+
+       value = g_new0 (guint8, PORTAL_DEFAULT_KEY_SIZE);
+       *n_value = PORTAL_DEFAULT_KEY_SIZE;
+
+       gcry_randomize (value, *n_value, GCRY_STRONG_RANDOM);
+
+       /* Create a new item */
+       if (!create_application_attributes (app_id, &builder)) {
+               gck_builder_clear (&builder);
+               g_free (value);
+               g_set_error (error,
+                            G_DBUS_ERROR,
+                            G_DBUS_ERROR_FAILED,
+                            "Invalid data in attributes argument");
+               return NULL;
+       }
+
+       gck_builder_add_string (&builder, CKA_G_COLLECTION, self->collection);
+       gck_builder_add_ulong (&builder, CKA_CLASS, CKO_SECRET_KEY);
+       gck_builder_add_boolean (&builder, CKA_TOKEN, TRUE);
+       gck_builder_add_data (&builder, CKA_VALUE, value, *n_value);
+
+       session = gkd_secret_service_internal_pkcs11_session (self->service);
+       item = gck_session_create_object (session,
+                                         gck_builder_end (&builder),
+                                         self->cancellable,
+                                         error);
+       if (item == NULL) {
+               g_free (value);
+               return NULL;
+       }
+       g_object_unref (item);
+
+       return value;
+}
+
+static gboolean
+portal_method_retrieve_secret (GkdExportedPortal *skeleton,
+                              GDBusMethodInvocation *invocation,
+                              GUnixFDList *fd_list,
+                              const gchar *arg_handle,
+                              const gchar *arg_app_id,
+                              GVariant *arg_fd,
+                              GVariant *arg_options,
+                              GkdSecretPortal *self)
+{
+       int idx, fd;
+       GError *error = NULL;
+       guint8 *value = NULL;
+       gsize n_value = 0;
+       GOutputStream *stream;
+       GVariantBuilder builder;
+
+       g_variant_get (arg_fd, "h", &idx);
+       fd = g_unix_fd_list_get (fd_list, idx, NULL);
+
+       g_clear_object (&self->request_skeleton);
+       self->request_skeleton = gkd_exported_portal_request_skeleton_new ();
+       if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->request_skeleton),
+                                              g_dbus_method_invocation_get_connection (invocation),
+                                              arg_handle, &error)) {
+               g_warning ("error exporting request: %s\n", error->message);
+               g_clear_error (&error);
+       } else {
+               g_signal_connect (self->request_skeleton, "handle-close",
+                                 G_CALLBACK (request_method_close), self);
+       }
+
+       if (!ensure_collection (self, &error)) {
+               g_clear_object (&self->request_skeleton);
+               g_dbus_method_invocation_take_error (invocation, error);
+               return TRUE;
+       }
+
+       value = lookup_secret_value (self, arg_app_id, &n_value, &error);
+       if (error != NULL) {
+               g_clear_object (&self->request_skeleton);
+               g_dbus_method_invocation_take_error (invocation, error);
+               return TRUE;
+       }
+
+       /* If secret is not found, create a new random key */
+       if (value == NULL) {
+               value = create_secret_value (self, arg_app_id, &n_value, &error);
+               if (value == NULL) {
+                       g_clear_object (&self->request_skeleton);
+                       g_dbus_method_invocation_take_error (invocation, error);
+                       return TRUE;
+               }
+       }
+
+       /* Write the secret value to the file descriptor */
+       stream = g_unix_output_stream_new (fd, TRUE);
+       if (!g_output_stream_write_all (stream, value, n_value, NULL, NULL, &error)) {
+               g_free (value);
+               g_object_unref (stream);
+               g_dbus_method_invocation_take_error (invocation, error);
+               return TRUE;
+       }
+       g_free (value);
+       g_object_unref (stream);
+
+       g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+       gkd_exported_portal_complete_retrieve_secret (skeleton,
+                                                     invocation,
+                                                     NULL,
+                                                     0,
+                                                     g_variant_builder_end (&builder));
+
+       return TRUE;
+}
diff --git a/daemon/dbus/gkd-secret-portal.h b/daemon/dbus/gkd-secret-portal.h
new file mode 100644
index 00000000..732353dc
--- /dev/null
+++ b/daemon/dbus/gkd-secret-portal.h
@@ -0,0 +1,33 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 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 License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GKD_SECRET_PORTAL_H__
+#define __GKD_SECRET_PORTAL_H__
+
+#include <glib-object.h>
+
+#define GKD_SECRET_TYPE_PORTAL (gkd_secret_portal_get_type ())
+G_DECLARE_FINAL_TYPE (GkdSecretPortal, gkd_secret_portal, GKD_SECRET, PORTAL, GObject);
+
+struct _GkdSecretPortalClass {
+       GObjectClass parent_class;
+};
+
+#endif /* __GKD_SECRET_PORTAL_H__ */
diff --git a/daemon/dbus/gkd-secret-service.c b/daemon/dbus/gkd-secret-service.c
index 69a42b4b..a0cd9e80 100644
--- a/daemon/dbus/gkd-secret-service.c
+++ b/daemon/dbus/gkd-secret-service.c
@@ -26,6 +26,7 @@
 #include "gkd-secret-error.h"
 #include "gkd-secret-lock.h"
 #include "gkd-secret-objects.h"
+#include "gkd-secret-portal.h"
 #include "gkd-secret-prompt.h"
 #include "gkd-secret-property.h"
 #include "gkd-secret-secret.h"
@@ -42,6 +43,7 @@
 #include "egg/egg-unix-credentials.h"
 
 #include <gck/gck.h>
+#include <gcrypt.h>
 
 #include "pkcs11/pkcs11i.h"
 
@@ -126,6 +128,7 @@ struct _GkdSecretService {
        GDBusConnection *connection;
        GkdExportedService *skeleton;
        GkdExportedInternal *internal_skeleton;
+       GkdSecretPortal *portal;
        guint name_owner_id;
        guint filter_id;
 
@@ -1034,6 +1037,8 @@ gkd_secret_service_constructor (GType type,
        g_signal_connect (self->internal_skeleton, "handle-unlock-with-master-password",
                          G_CALLBACK (service_method_unlock_with_master_password), self);
 
+       self->portal = g_object_new (GKD_SECRET_TYPE_PORTAL, "service", self, NULL);
+
        self->name_owner_id = g_dbus_connection_signal_subscribe (self->connection,
                                                                  NULL,
                                                                  "org.freedesktop.DBus",
@@ -1092,6 +1097,8 @@ gkd_secret_service_dispose (GObject *obj)
                self->internal_session = NULL;
        }
 
+       g_clear_object (&self->portal);
+
        G_OBJECT_CLASS (gkd_secret_service_parent_class)->dispose (obj);
 }
 
diff --git a/daemon/dbus/gkd-secret-types.h b/daemon/dbus/gkd-secret-types.h
index 1f756d23..4005108e 100644
--- a/daemon/dbus/gkd-secret-types.h
+++ b/daemon/dbus/gkd-secret-types.h
@@ -23,6 +23,9 @@
 
 #define INTERNAL_SERVICE_INTERFACE     "org.gnome.keyring.InternalUnsupportedGuiltRiddenInterface"
 
+#define PORTAL_SERVICE_INTERFACE       "org.freedesktop.impl.portal.Secret"
+#define PORTAL_SERVICE_PATH            "/org/freedesktop/portal/desktop"
+
 #define SECRET_COLLECTION_INTERFACE    "org.freedesktop.Secret.Collection"
 #define SECRET_ITEM_INTERFACE          "org.freedesktop.Secret.Item"
 #define SECRET_PROMPT_INTERFACE        "org.freedesktop.Secret.Prompt"
diff --git a/daemon/dbus/org.freedesktop.impl.portal.Request.xml 
b/daemon/dbus/org.freedesktop.impl.portal.Request.xml
new file mode 100644
index 00000000..4402c40a
--- /dev/null
+++ b/daemon/dbus/org.freedesktop.impl.portal.Request.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<!--
+ Copyright (C) 2016 Red Hat, Inc.
+
+ This library 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 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+ Author: Alexander Larsson <alexl redhat com>
+         Matthias Clasen <mclasen redhat com>
+-->
+
+<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd";>
+  <!--
+      org.freedesktop.impl.portal.Request:
+      @short_description: Shared request interface
+
+      The Request interface is shared by all portal backend interfaces.
+      When a backend method is called, the backend exports a Request object
+      on the object path that was sent with the method call. The Request
+      will stay alive for the duration of the user interaction related to
+      the method call.
+
+      The portal can abort the interaction calling
+      org.freedesktop.impl.portal.Request.Close() on the Request object.
+  -->
+  <interface name="org.freedesktop.impl.portal.Request">
+
+    <!--
+        Close:
+
+        Ends the user interaction to which this object refers.
+        Dialogs and other UIs related to the request should be closed.
+    -->
+    <method name="Close">
+    </method>
+  </interface>
+</node>
diff --git a/daemon/dbus/org.freedesktop.impl.portal.Secret.xml 
b/daemon/dbus/org.freedesktop.impl.portal.Secret.xml
new file mode 100644
index 00000000..c33d85e2
--- /dev/null
+++ b/daemon/dbus/org.freedesktop.impl.portal.Secret.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<!--
+ Copyright (C) 2019 Red Hat, Inc.
+
+ This library 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 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+ Author: Daiki Ueno <dueno redhat com>
+-->
+
+<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd";>
+  <!--
+      org.freedesktop.impl.portal.Secret:
+      @short_description: Secret portal backend interface
+
+      The Secret portal allows sandboxed applications to retrieve a
+      per-application master secret.
+  -->
+  <interface name="org.freedesktop.impl.portal.Secret">
+
+    <!--
+        RetrieveSecret:
+        @handle: Object path for the #org.freedesktop.impl.portal.Request object representing this call
+        @app_id: App id of the application
+        @fd: File descriptor for reading the secret
+        @options: Vardict with optional further information
+        @results: Vardict with the results of the call
+
+        Retrieves a master secret for a sandboxed application.
+
+        Supported keys in the @options vardict include:
+        <variablelist>
+          <varlistentry>
+            <term>token s</term>
+            <listitem><para>
+             An opaque string associated with the retrieve secret.
+            </para></listitem>
+          </varlistentry>
+        </variablelist>
+    -->
+    <method name="RetrieveSecret">
+      <annotation name="org.gtk.GDBus.C.Name" value="retrieve_secret"/>
+      <annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
+      <arg type="o" name="handle" direction="in"/>
+      <arg type="s" name="app_id" direction="in"/>
+      <arg type="h" name="fd" direction="in"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.In3" value="QVariantMap"/>
+      <arg type="a{sv}" name="options" direction="in"/>
+      <arg type="u" name="response" direction="out"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
+      <arg type="a{sv}" name="results" direction="out"/>
+    </method>
+    <property name="version" type="u" access="read"/>
+  </interface>
+</node>
diff --git a/daemon/dbus/test-dbus-portal.c b/daemon/dbus/test-dbus-portal.c
new file mode 100644
index 00000000..aeb912fb
--- /dev/null
+++ b/daemon/dbus/test-dbus-portal.c
@@ -0,0 +1,215 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* test-secret-portal.c: Test secret portal
+
+   Copyright (C) 2013-2019 Red Hat, Inc
+
+   The Gnome Keyring Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Keyring Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   <http://www.gnu.org/licenses/>.
+
+   Author: Stef Walter <stefw gnome org>, Daiki Ueno
+*/
+
+#include "config.h"
+
+#include "test-service.h"
+
+#include "gkd-secret-types.h"
+
+#include "egg/egg-testing.h"
+
+#include <gcr/gcr-base.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include <glib-unix.h>
+#include <gio/gunixfdlist.h>
+#include <gio/gunixinputstream.h>
+#include <fcntl.h>
+
+typedef struct {
+       TestService service;
+       guint8 buffer[128];
+       gsize bytes_read;
+} Test;
+
+static void
+setup (Test *test,
+       gconstpointer unused)
+{
+       GVariant *retval;
+       GError *error = NULL;
+
+       test->service.mock_prompter = gcr_mock_prompter_start ();
+       g_assert (test->service.mock_prompter != NULL);
+
+       test_service_setup (&test->service);
+
+       /* Unlock the test collection */
+       retval = g_dbus_connection_call_sync (test->service.connection,
+                                             test->service.bus_name,
+                                             SECRET_SERVICE_PATH,
+                                             INTERNAL_SERVICE_INTERFACE,
+                                             "UnlockWithMasterPassword",
+                                             g_variant_new ("(o@(oayays))",
+                                                            "/org/freedesktop/secrets/collection/test",
+                                                            test_service_build_secret (&test->service, 
"booo")),
+                                             G_VARIANT_TYPE ("()"),
+                                             G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                                             -1, NULL, &error);
+       g_assert_no_error (error);
+       g_variant_unref (retval);
+}
+
+static void
+teardown (Test *test,
+          gconstpointer unused)
+{
+       test_service_teardown (&test->service);
+
+       gcr_mock_prompter_stop ();
+}
+
+static void
+setup_locked (Test *test,
+              gconstpointer unused)
+{
+       GVariant *element;
+       GVariant *retval;
+       GError *error = NULL;
+       const gchar *prompt;
+       GVariant *locked;
+
+       /* Main setup */
+       setup (test, unused);
+
+       element = g_variant_new_object_path ("/org/freedesktop/secrets/collection/test");
+       retval = g_dbus_connection_call_sync (test->service.connection,
+                                             test->service.bus_name,
+                                             SECRET_SERVICE_PATH,
+                                             SECRET_SERVICE_INTERFACE,
+                                             "Lock",
+                                             g_variant_new ("(@ao)",
+                                                            g_variant_new_array (G_VARIANT_TYPE ("o"), 
&element, 1)),
+                                             G_VARIANT_TYPE ("(aoo)"),
+                                             G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                                             -1,
+                                             NULL,
+                                             &error);
+       g_assert_no_error (error);
+
+       /* Not expecting a prompt */
+       g_variant_get (retval, "(@ao&o)", &locked, &prompt);
+       g_assert_cmpstr (prompt, ==, "/");
+       g_variant_unref (locked);
+       g_variant_unref (retval);
+}
+
+static void
+call_retrieve_secret (Test *test)
+{
+       GUnixFDList *fd_list;
+       gint fds[2];
+       gint fd_index;
+       GError *error = NULL;
+       gboolean ret;
+       GVariant *reply = NULL;
+       GInputStream *stream = NULL;
+       GVariantBuilder options;
+
+       ret = g_unix_open_pipe (fds, FD_CLOEXEC, &error);
+       g_assert_true (ret);
+       g_assert_no_error (error);
+
+       fd_list = g_unix_fd_list_new ();
+       fd_index = g_unix_fd_list_append (fd_list, fds[1], &error);
+       g_assert_no_error (error);
+       close (fds[1]);
+
+       g_variant_builder_init (&options, G_VARIANT_TYPE ("a{sv}"));
+       reply = g_dbus_connection_call_with_unix_fd_list_sync (test->service.connection,
+                                                              test->service.bus_name,
+                                                              PORTAL_SERVICE_PATH,
+                                                              PORTAL_SERVICE_INTERFACE,
+                                                              "RetrieveSecret",
+                                                              g_variant_new ("(osh@a{sv})",
+                                                                             
"/org/gnome/keyring/Portal/Request",
+                                                                             "org.gnome.keyring.Test",
+                                                                             fd_index,
+                                                                             g_variant_builder_end 
(&options)),
+                                                              G_VARIANT_TYPE ("(ua{sv})"),
+                                                              G_DBUS_CALL_FLAGS_NONE,
+                                                              30000,
+                                                              fd_list, NULL,
+                                                              NULL,
+                                                              &error);
+       g_object_unref (fd_list);
+       g_assert_no_error (error);
+       g_assert_nonnull (reply);
+
+       stream = g_unix_input_stream_new (fds[0], TRUE);
+       ret = g_input_stream_read_all (stream, test->buffer, sizeof(test->buffer),
+                                      &test->bytes_read, NULL, &error);
+       g_assert_no_error (error);
+       g_assert_true (ret);
+
+       g_object_unref (stream);
+}
+
+static void
+test_portal_retrieve_secret (Test *test,
+                            gconstpointer unused)
+{
+       guint8 buffer[128];
+       gsize bytes_read;
+
+       call_retrieve_secret (test);
+       memcpy (buffer, test->buffer, sizeof(test->buffer));
+       bytes_read = test->bytes_read;
+
+       call_retrieve_secret (test);
+       g_assert_cmpmem (buffer, bytes_read, test->buffer, test->bytes_read);
+}
+
+static void
+test_portal_retrieve_secret_locked (Test *test,
+                                   gconstpointer unused)
+{
+       guint8 buffer[128];
+       gsize bytes_read;
+
+       gcr_mock_prompter_expect_password_ok ("booo", NULL);
+
+       call_retrieve_secret (test);
+       memcpy (buffer, test->buffer, sizeof(test->buffer));
+       bytes_read = test->bytes_read;
+
+       call_retrieve_secret (test);
+       g_assert_cmpmem (buffer, bytes_read, test->buffer, test->bytes_read);
+}
+
+int
+main (int argc, char **argv)
+{
+#if !GLIB_CHECK_VERSION(2,35,0)
+       g_type_init ();
+#endif
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_add ("/secret-portal/portal-retrieve-secret", Test, NULL,
+                   setup, test_portal_retrieve_secret, teardown);
+       g_test_add ("/secret-portal/portal-retrieve-secret-locked", Test, NULL,
+                   setup_locked, test_portal_retrieve_secret_locked, teardown);
+
+       return egg_tests_run_with_loop ();
+}
diff --git a/daemon/dbus/test-service.c b/daemon/dbus/test-service.c
index 212e12f1..46c85be1 100644
--- a/daemon/dbus/test-service.c
+++ b/daemon/dbus/test-service.c
@@ -91,6 +91,7 @@ test_service_setup (TestService *test)
 
        env = gkd_test_launch_daemon (test->directory, args, &test->pid,
                                      "GNOME_KEYRING_TEST_SERVICE", test->bus_name,
+                                     "GNOME_KEYRING_TEST_LOGIN", "test",
                                      test->mock_prompter ? "GNOME_KEYRING_TEST_PROMPTER" : NULL, 
test->mock_prompter,
                                      NULL);
        g_strfreev (env);
diff --git a/daemon/gnome-keyring.portal b/daemon/gnome-keyring.portal
new file mode 100644
index 00000000..07eb3d24
--- /dev/null
+++ b/daemon/gnome-keyring.portal
@@ -0,0 +1,4 @@
+[portal]
+DBusName=org.freedesktop.secrets
+Interfaces=org.freedesktop.impl.portal.Secret
+UseIn=gnome
diff --git a/daemon/org.freedesktop.impl.portal.Secret.service.in 
b/daemon/org.freedesktop.impl.portal.Secret.service.in
new file mode 100644
index 00000000..bc4d921c
--- /dev/null
+++ b/daemon/org.freedesktop.impl.portal.Secret.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.impl.portal.Secret
+Exec=@bindir@/gnome-keyring-daemon --start --foreground --components=secrets


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]