1 /* libsecret - GLib wrapper for Secret Service
2  *
3  * Copyright 2019 Red Hat, Inc.
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published
7  * by the Free Software Foundation; either version 2.1 of the licence or (at
8  * your option) any later version.
9  *
10  * See the included COPYING file for more information.
11  *
12  * Author: Daiki Ueno
13  */
14 
15 #include "config.h"
16 
17 #include "secret-backend.h"
18 
19 #ifdef WITH_GCRYPT
20 #include "secret-file-backend.h"
21 #endif
22 
23 #include "secret-private.h"
24 
25 #include "libsecret/secret-enum-types.h"
26 
27 /**
28  * SECTION:secret-backend
29  * @title: SecretBackend
30  * @short_description: A backend implementation of password storage
31  *
32  * #SecretBackend represents a backend implementation of password
33  * storage.
34  *
35  * Stability: Stable
36  */
37 
38 /**
39  * SecretBackend:
40  *
41  * An object representing a backend implementation of password storage.
42  *
43  * Since: 0.19.0
44  */
45 
46 /**
47  * SecretBackendInterface:
48  * @parent_iface: the parent interface
49  * @ensure_for_flags: implementation of reinitialization step in constructor, optional
50  * @ensure_for_flags_finish: implementation of reinitialization step in constructor, optional
51  * @store: implementation of secret_password_store(), required
52  * @store_finish: implementation of secret_password_store_finish(), required
53  * @lookup: implementation of secret_password_lookup(), required
54  * @lookup_finish: implementation of secret_password_lookup_finish(), required
55  * @clear: implementation of secret_password_clear(), required
56  * @clear_finish: implementation of secret_password_clear_finish(), required
57  * @search: implementation of secret_password_search(), required
58  * @search_finish: implementation of secret_password_search_finish(), required
59  *
60  * The interface for #SecretBackend.
61  *
62  * Since: 0.19.0
63  */
64 
65 /**
66  * SecretBackendFlags:
67  * @SECRET_BACKEND_NONE: no flags for initializing the #SecretBackend
68  * @SECRET_BACKEND_OPEN_SESSION: establish a session for transfer of secrets
69  *                               while initializing the #SecretBackend
70  * @SECRET_BACKEND_LOAD_COLLECTIONS: load collections while initializing the
71  *                                   #SecretBackend
72  *
73  * Flags which determine which parts of the #SecretBackend are initialized.
74  *
75  * Since: 0.19.0
76  */
77 
78 G_DEFINE_INTERFACE_WITH_CODE (SecretBackend, secret_backend, G_TYPE_OBJECT,
79 			      g_type_interface_add_prerequisite(g_define_type_id, G_TYPE_ASYNC_INITABLE);
80 );
81 
82 static void
secret_backend_default_init(SecretBackendInterface * iface)83 secret_backend_default_init (SecretBackendInterface *iface)
84 {
85 	/**
86 	 * SecretBackend:flags:
87 	 *
88 	 * A set of flags describing which parts of the secret backend have
89 	 * been initialized.
90 	 *
91 	 * Since: 0.19.0
92 	 */
93 	g_object_interface_install_property (iface,
94 		     g_param_spec_flags ("flags", "Flags", "Service flags",
95 					 secret_service_flags_get_type (), SECRET_SERVICE_NONE,
96 					 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
97 }
98 
99 void
_secret_backend_ensure_extension_point(void)100 _secret_backend_ensure_extension_point (void)
101 {
102 	GIOExtensionPoint *ep;
103 	static gboolean registered = FALSE;
104 
105 	if (registered)
106 		return;
107 
108 	ep = g_io_extension_point_register (SECRET_BACKEND_EXTENSION_POINT_NAME);
109 	g_io_extension_point_set_required_type (ep, SECRET_TYPE_BACKEND);
110 
111 	registered = TRUE;
112 }
113 
114 G_LOCK_DEFINE (backend_instance);
115 static gpointer backend_instance = NULL;
116 
117 static SecretBackend *
backend_get_instance(void)118 backend_get_instance (void)
119 {
120 	SecretBackend *instance = NULL;
121 
122 	G_LOCK (backend_instance);
123 	if (backend_instance != NULL)
124 		instance = g_object_ref (backend_instance);
125 	G_UNLOCK (backend_instance);
126 
127 	return instance;
128 }
129 
130 void
_secret_backend_uncache_instance(void)131 _secret_backend_uncache_instance (void)
132 {
133 	SecretBackend *instance = NULL;
134 
135 	G_LOCK (backend_instance);
136 	instance = backend_instance;
137 	backend_instance = NULL;
138 	G_UNLOCK (backend_instance);
139 
140 	if (instance != NULL)
141 		g_object_unref (instance);
142 }
143 
144 static GType
backend_get_impl_type(void)145 backend_get_impl_type (void)
146 {
147 	const gchar *envvar;
148 	const gchar *extension_name;
149 	GIOExtension *e;
150 	GIOExtensionPoint *ep;
151 
152 	g_type_ensure (secret_service_get_type ());
153 #ifdef WITH_GCRYPT
154 	g_type_ensure (secret_file_backend_get_type ());
155 #endif
156 
157 #ifdef WITH_GCRYPT
158 	if (g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS) &&
159 	    _secret_file_backend_check_portal_version ())
160 		extension_name = "file";
161 	else
162 #endif
163 	{
164 		envvar = g_getenv ("SECRET_BACKEND");
165 		if (envvar == NULL || *envvar == '\0')
166 			extension_name = "service";
167 		else
168 			extension_name = envvar;
169 	}
170 
171 	ep = g_io_extension_point_lookup (SECRET_BACKEND_EXTENSION_POINT_NAME);
172 	e = g_io_extension_point_get_extension_by_name (ep, extension_name);
173 	if (e == NULL) {
174 		g_warning ("Backend extension \"%s\" from SECRET_BACKEND_EXTENSION_POINT_NAME environment variable not found.", extension_name);
175 		return G_TYPE_NONE;
176 	}
177 
178 	return g_io_extension_get_type (e);
179 }
180 
181 static void
on_ensure_for_flags(GObject * source_object,GAsyncResult * result,gpointer user_data)182 on_ensure_for_flags (GObject *source_object,
183 		     GAsyncResult *result,
184 		     gpointer user_data)
185 {
186 	SecretBackendInterface *iface;
187 	SecretBackend *self = SECRET_BACKEND (source_object);
188 	GTask *task = G_TASK (user_data);
189 	GError *error = NULL;
190 
191 	iface = SECRET_BACKEND_GET_IFACE (self);
192 	if (iface->ensure_for_flags_finish) {
193 		if (!iface->ensure_for_flags_finish (self, result, &error)) {
194 			g_task_return_error (task, error);
195 			g_object_unref (task);
196 			return;
197 		}
198 	}
199 
200 	g_task_return_boolean (task, TRUE);
201 	g_object_unref (task);
202 }
203 
204 /**
205  * secret_backend_get:
206  * @flags: flags for which service functionality to ensure is initialized
207  * @cancellable: optional cancellation object
208  * @callback: called when the operation completes
209  * @user_data: data to be passed to the callback
210  *
211  * Get a #SecretBackend instance. If such a backend already exists,
212  * then the same backend is returned.
213  *
214  * If @flags contains any flags of which parts of the secret backend to
215  * ensure are initialized, then those will be initialized before completing.
216  *
217  * This method will return immediately and complete asynchronously.
218  *
219  * Since: 0.19.0
220  */
221 void
secret_backend_get(SecretBackendFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)222 secret_backend_get (SecretBackendFlags flags,
223 		    GCancellable *cancellable,
224 		    GAsyncReadyCallback callback,
225 		    gpointer user_data)
226 {
227 	SecretBackend *backend = NULL;
228 	SecretBackendInterface *iface;
229 	GTask *task;
230 
231 	backend = backend_get_instance ();
232 
233 	/* Create a whole new backend */
234 	if (backend == NULL) {
235 		GType impl_type = backend_get_impl_type ();
236 		g_return_if_fail (g_type_is_a (impl_type, G_TYPE_ASYNC_INITABLE));
237 		g_async_initable_new_async (impl_type,
238 					    G_PRIORITY_DEFAULT,
239 					    cancellable, callback, user_data,
240 					    "flags", flags,
241 					    NULL);
242 
243 	/* Just have to ensure that the backend matches flags */
244 	} else {
245 		task = g_task_new (backend, cancellable, callback, user_data);
246 		iface = SECRET_BACKEND_GET_IFACE (backend);
247 		if (iface->ensure_for_flags) {
248 			g_task_set_source_tag (task, secret_backend_get);
249 			iface->ensure_for_flags (backend, flags, cancellable,
250 						 on_ensure_for_flags, task);
251 		} else {
252 			g_task_return_boolean (task, TRUE);
253 			g_object_unref (task);
254 		}
255 		g_object_unref (backend);
256 	}
257 }
258 
259 /**
260  * secret_backend_get_finish:
261  * @result: the asynchronous result passed to the callback
262  * @error: location to place an error on failure
263  *
264  * Complete an asynchronous operation to get a #SecretBackend.
265  *
266  * Returns: (transfer full): a new reference to a #SecretBackend proxy, which
267  *          should be released with g_object_unref().
268  *
269  * Since: 0.19.0
270  */
271 SecretBackend *
secret_backend_get_finish(GAsyncResult * result,GError ** error)272 secret_backend_get_finish (GAsyncResult *result,
273 			   GError **error)
274 {
275 	GTask *task;
276 	GObject *backend = NULL;
277 	GObject *source_object;
278 
279 	g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
280 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
281 
282 	task = G_TASK (result);
283 	source_object = g_task_get_source_object (task);
284 
285 	g_return_val_if_fail (g_task_is_valid (result, source_object), NULL);
286 
287 	/* Just ensuring that the backend matches flags */
288 	if (g_task_get_source_tag (task) == secret_backend_get) {
289 		if (g_task_had_error (task)) {
290 			g_task_propagate_pointer (task, error);
291 		} else {
292 			backend = g_object_ref (source_object);
293 		}
294 
295 	/* Creating a whole new backend */
296 	} else {
297 		backend = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), result, error);
298 		if (backend) {
299 			G_LOCK (backend_instance);
300 			if (backend_instance == NULL)
301 				backend_instance = backend;
302 			G_UNLOCK (backend_instance);
303 		}
304 	}
305 
306 	if (backend == NULL)
307 		return NULL;
308 
309 	return SECRET_BACKEND (backend);
310 }
311