1 /* libsecret - GLib wrapper for Secret Prompt
2  *
3  * Copyright 2011 Collabora Ltd.
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: Stef Walter <stefw@gnome.org>
13  */
14 
15 #include "config.h"
16 
17 #include "secret-dbus-generated.h"
18 #include "secret-private.h"
19 #include "secret-prompt.h"
20 
21 #include <glib.h>
22 #include <glib/gi18n-lib.h>
23 
24 /**
25  * SECTION:secret-prompt
26  * @title: SecretPrompt
27  * @short_description: a prompt in the Service
28  *
29  * A #SecretPrompt represents a prompt in the Secret Service.
30  *
31  * Certain actions on the Secret Service require user prompting to complete,
32  * such as creating a collection, or unlocking a collection. When such a prompt
33  * is necessary, then a #SecretPrompt object is created by this library, and
34  * passed to the secret_service_prompt() method. In this way it is handled
35  * automatically.
36  *
37  * In order to customize prompt handling, override the
38  * SecretServiceClass::prompt_async and SecretServiceClass::prompt_finish
39  * virtual methods of the #SecretService class.
40  *
41  * Stability: Stable
42  */
43 
44 /**
45  * SecretPrompt:
46  *
47  * A proxy object representing a prompt that the Secret Service will display
48  * to the user.
49  */
50 
51 /**
52  * SecretPromptClass:
53  * @parent_class: the parent class
54  *
55  * The class for #SecretPrompt.
56  */
57 
58 struct _SecretPromptPrivate {
59 	gint prompted;
60 };
61 
62 G_DEFINE_TYPE_WITH_PRIVATE (SecretPrompt, secret_prompt, G_TYPE_DBUS_PROXY);
63 
64 static void
65 secret_prompt_init (SecretPrompt *self)
66 {
67 	self->pv = secret_prompt_get_instance_private (self);
68 }
69 
70 static void
71 secret_prompt_class_init (SecretPromptClass *klass)
72 {
73 }
74 
75 typedef struct {
76 	GMainLoop *loop;
77 	GAsyncResult *result;
78 } RunClosure;
79 
80 static void
81 on_prompt_run_complete (GObject *source,
82                         GAsyncResult *result,
83                         gpointer user_data)
84 {
85 	RunClosure *closure = user_data;
86 	closure->result = g_object_ref (result);
87 	g_main_loop_quit (closure->loop);
88 }
89 
90 SecretPrompt *
91 _secret_prompt_instance (SecretService *service,
92                          const gchar *prompt_path)
93 {
94 	GDBusProxy *proxy;
95 	SecretPrompt *prompt;
96 	GError *error = NULL;
97 
98 	g_return_val_if_fail (SECRET_IS_SERVICE (service), NULL);
99 	g_return_val_if_fail (prompt_path != NULL, NULL);
100 
101 	proxy = G_DBUS_PROXY (service);
102 	prompt = g_initable_new (SECRET_TYPE_PROMPT, NULL, &error,
103 	                         "g-flags", G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
104 	                         "g-interface-info", _secret_gen_prompt_interface_info (),
105 	                         "g-name", g_dbus_proxy_get_name (proxy),
106 	                         "g-connection", g_dbus_proxy_get_connection (proxy),
107 	                         "g-object-path", prompt_path,
108 	                         "g-interface-name", SECRET_PROMPT_INTERFACE,
109 	                         NULL);
110 
111 	if (error != NULL) {
112 		g_warning ("couldn't create SecretPrompt object: %s", error->message);
113 		g_clear_error (&error);
114 		return NULL;
115 	}
116 
117 	return prompt;
118 }
119 
120 /**
121  * secret_prompt_run:
122  * @self: a prompt
123  * @window_id: (allow-none): string form of XWindow id for parent window to be transient for
124  * @cancellable: optional cancellation object
125  * @return_type: the variant type of the prompt result
126  * @error: location to place an error on failure
127  *
128  * Runs a prompt and performs the prompting. Returns a variant result if the
129  * prompt was completed and not dismissed. The type of result depends on the
130  * action the prompt is completing, and is defined in the Secret Service DBus
131  * API specification.
132  *
133  * If @window_id is non-null then it is used as an XWindow id on Linux. The API
134  * expects this id to be converted to a string using the <literal>%d</literal>
135  * printf format. The Secret Service can make its prompt transient for the window
136  * with this id. In some Secret Service implementations this is not possible, so
137  * the behavior depending on this should degrade gracefully.
138  *
139  * This runs the dialog in a recursive mainloop. When run from a user interface
140  * thread, this means the user interface will remain responsive. Care should be
141  * taken that appropriate user interface actions are disabled while running the
142  * prompt.
143  *
144  * Returns: (transfer full): %NULL if the prompt was dismissed or an error occurred
145  */
146 GVariant *
147 secret_prompt_run (SecretPrompt *self,
148                    const gchar *window_id,
149                    GCancellable *cancellable,
150                    const GVariantType *return_type,
151                    GError **error)
152 {
153 	GMainContext *context;
154 	RunClosure *closure;
155 	GVariant *retval;
156 
157 	g_return_val_if_fail (SECRET_IS_PROMPT (self), NULL);
158 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
159 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
160 
161 	context = g_main_context_get_thread_default ();
162 
163 	closure = g_new0 (RunClosure, 1);
164 	closure->loop = g_main_loop_new (context, FALSE);
165 
166 	secret_prompt_perform (self, window_id, return_type, cancellable,
167 	                       on_prompt_run_complete, closure);
168 
169 	g_main_loop_run (closure->loop);
170 
171 	retval = secret_prompt_perform_finish (self, closure->result, error);
172 
173 	g_main_loop_unref (closure->loop);
174 	g_object_unref (closure->result);
175 	g_free (closure);
176 
177 	return retval;
178 }
179 
180 /**
181  * secret_prompt_perform_sync:
182  * @self: a prompt
183  * @window_id: (allow-none): string form of XWindow id for parent window to be transient for
184  * @cancellable: optional cancellation object
185  * @return_type: the variant type of the prompt result
186  * @error: location to place an error on failure
187  *
188  * Runs a prompt and performs the prompting. Returns a variant result if the
189  * prompt was completed and not dismissed. The type of result depends on the
190  * action the prompt is completing, and is defined in the Secret Service DBus
191  * API specification.
192  *
193  * If @window_id is non-null then it is used as an XWindow id on Linux. The API
194  * expects this id to be converted to a string using the <literal>%d</literal>
195  * printf format. The Secret Service can make its prompt transient for the window
196  * with this id. In some Secret Service implementations this is not possible,
197  * so the behavior depending on this should degrade gracefully.
198  *
199  * This method may block indefinitely and should not be used in user interface
200  * threads.
201  *
202  * Returns: (transfer full): %NULL if the prompt was dismissed or an error occurred
203  */
204 GVariant *
205 secret_prompt_perform_sync (SecretPrompt *self,
206                             const gchar *window_id,
207                             GCancellable *cancellable,
208                             const GVariantType *return_type,
209                             GError **error)
210 {
211 	GMainContext *context;
212 	GVariant *retval;
213 
214 	g_return_val_if_fail (SECRET_IS_PROMPT (self), NULL);
215 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
216 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
217 
218 	context = g_main_context_new ();
219 	g_main_context_push_thread_default (context);
220 
221 	retval = secret_prompt_run (self, window_id, cancellable, return_type, error);
222 
223 	/* Needed to prevent memory leaks */
224 	while (g_main_context_iteration (context, FALSE));
225 
226 	g_main_context_pop_thread_default (context);
227 	g_main_context_unref (context);
228 
229 	return retval;
230 }
231 
232 typedef struct {
233 	GDBusConnection *connection;
234 	GCancellable *call_cancellable;
235 	GCancellable *async_cancellable;
236 	gulong cancelled_sig;
237 	gboolean prompting;
238 	gboolean dismissed;
239 	gboolean vanished;
240 	gboolean completed;
241 	GVariant *result;
242 	guint signal;
243 	guint watch;
244 	GVariantType *return_type;
245 } PerformClosure;
246 
247 static void
248 perform_closure_free (gpointer data)
249 {
250 	PerformClosure *closure = data;
251 	g_object_unref (closure->call_cancellable);
252 	g_clear_object (&closure->async_cancellable);
253 	g_object_unref (closure->connection);
254 	if (closure->result)
255 		g_variant_unref (closure->result);
256 	if (closure->return_type)
257 		g_variant_type_free (closure->return_type);
258 	g_assert (closure->signal == 0);
259 	g_assert (closure->watch == 0);
260 	g_slice_free (PerformClosure, closure);
261 }
262 
263 static void
264 perform_prompt_complete (GSimpleAsyncResult *res,
265                          gboolean dismissed)
266 {
267 	PerformClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
268 
269 	closure->dismissed = dismissed;
270 	if (closure->completed)
271 		return;
272 	closure->completed = TRUE;
273 
274 	if (closure->signal)
275 		g_dbus_connection_signal_unsubscribe (closure->connection, closure->signal);
276 	closure->signal = 0;
277 
278 	if (closure->watch)
279 		g_bus_unwatch_name (closure->watch);
280 	closure->watch = 0;
281 
282 	if (closure->cancelled_sig)
283 		g_signal_handler_disconnect (closure->async_cancellable, closure->cancelled_sig);
284 	closure->cancelled_sig = 0;
285 
286 	g_simple_async_result_complete (res);
287 }
288 
289 static void
290 on_prompt_completed (GDBusConnection *connection,
291                      const gchar *sender_name,
292                      const gchar *object_path,
293                      const gchar *interface_name,
294                      const gchar *signal_name,
295                      GVariant *parameters,
296                      gpointer user_data)
297 {
298 	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
299 	SecretPrompt *self = SECRET_PROMPT (g_async_result_get_source_object (user_data));
300 	PerformClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
301 	gboolean dismissed;
302 
303 	closure->prompting = FALSE;
304 
305 	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(bv)"))) {
306 		g_warning ("SecretPrompt received invalid %s signal of type %s",
307 		           signal_name, g_variant_get_type_string (parameters));
308 		perform_prompt_complete (res, TRUE);
309 
310 	} else {
311 		g_variant_get (parameters, "(bv)", &dismissed, &closure->result);
312 		perform_prompt_complete (res, dismissed);
313 	}
314 
315 	g_object_unref (self);
316 }
317 
318 static void
319 on_prompt_prompted (GObject *source,
320                     GAsyncResult *result,
321                     gpointer user_data)
322 {
323 	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
324 	PerformClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
325 	SecretPrompt *self = SECRET_PROMPT (source);
326 	GError *error = NULL;
327 	GVariant *retval;
328 
329 	retval = g_dbus_proxy_call_finish (G_DBUS_PROXY (self), result, &error);
330 
331 	if (retval)
332 		g_variant_unref (retval);
333 	if (closure->vanished)
334 		g_clear_error (&error);
335 
336 	if (error != NULL) {
337 		g_simple_async_result_take_error (res, error);
338 		perform_prompt_complete (res, TRUE);
339 
340 	} else {
341 		closure->prompting = TRUE;
342 		g_atomic_int_set (&self->pv->prompted, 1);
343 
344 		/* And now we wait for the signal */
345 	}
346 
347 	g_object_unref (res);
348 }
349 
350 static void
351 on_prompt_vanished (GDBusConnection *connection,
352                     const gchar *name,
353                     gpointer user_data)
354 {
355 	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
356 	PerformClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
357 	closure->vanished = TRUE;
358 	g_cancellable_cancel (closure->call_cancellable);
359 	perform_prompt_complete (res, TRUE);
360 }
361 
362 static void
363 on_prompt_dismissed (GObject *source,
364                      GAsyncResult *result,
365                      gpointer user_data)
366 {
367 	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
368 	PerformClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
369 	SecretPrompt *self = SECRET_PROMPT (source);
370 	GError *error = NULL;
371 	GVariant *retval;
372 
373 	retval = g_dbus_proxy_call_finish (G_DBUS_PROXY (self), result, &error);
374 
375 	if (retval)
376 		g_variant_unref (retval);
377 	if (closure->vanished)
378 		g_clear_error (&error);
379 	if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
380 		g_clear_error (&error);
381 
382 	if (error != NULL) {
383 		g_simple_async_result_take_error (res, error);
384 		perform_prompt_complete (res, TRUE);
385 	}
386 
387 	g_object_unref (res);
388 }
389 
390 static void
391 on_prompt_cancelled (GCancellable *cancellable,
392                      gpointer user_data)
393 {
394 	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
395 	PerformClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
396 	SecretPrompt *self = SECRET_PROMPT (g_async_result_get_source_object (user_data));
397 
398 	/* Instead of cancelling our dbus calls, we cancel the prompt itself via this dbus call */
399 
400 	g_dbus_proxy_call (G_DBUS_PROXY (self), "Dismiss", g_variant_new ("()"),
401 	                   G_DBUS_CALL_FLAGS_NO_AUTO_START, -1,
402 	                   closure->call_cancellable,
403 	                   on_prompt_dismissed, g_object_ref (res));
404 
405 	g_object_unref (self);
406 }
407 
408 /**
409  * secret_prompt_perform:
410  * @self: a prompt
411  * @window_id: (allow-none): string form of XWindow id for parent window to be transient for
412  * @return_type: the variant type of the prompt result
413  * @cancellable: optional cancellation object
414  * @callback: called when the operation completes
415  * @user_data: data to be passed to the callback
416  *
417  * Runs a prompt and performs the prompting. Returns %TRUE if the prompt
418  * was completed and not dismissed.
419  *
420  * If @window_id is non-null then it is used as an XWindow id on Linux. The API
421  * expects this id to be converted to a string using the <literal>%d</literal>
422  * printf format. The Secret Service can make its prompt transient for the window
423  * with this id. In some Secret Service implementations this is not possible, so
424  * the behavior depending on this should degrade gracefully.
425  *
426  * This method will return immediately and complete asynchronously.
427  */
428 void
429 secret_prompt_perform (SecretPrompt *self,
430                        const gchar *window_id,
431                        const GVariantType *return_type,
432                        GCancellable *cancellable,
433                        GAsyncReadyCallback callback,
434                        gpointer user_data)
435 {
436 	GSimpleAsyncResult *res;
437 	PerformClosure *closure;
438 	gchar *owner_name;
439 	const gchar *object_path;
440 	gboolean prompted;
441 	GDBusProxy *proxy;
442 
443 	g_return_if_fail (SECRET_IS_PROMPT (self));
444 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
445 
446 	prompted = g_atomic_int_get (&self->pv->prompted);
447 	if (prompted) {
448 		g_warning ("The prompt object has already had its prompt called.");
449 		return;
450 	}
451 
452 	proxy = G_DBUS_PROXY (self);
453 
454 	res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
455 	                                 secret_prompt_perform);
456 	closure = g_slice_new0 (PerformClosure);
457 	closure->connection = g_object_ref (g_dbus_proxy_get_connection (proxy));
458 	closure->call_cancellable = g_cancellable_new ();
459 	closure->async_cancellable = cancellable ? g_object_ref (cancellable) : NULL;
460 	closure->return_type = return_type ? g_variant_type_copy (return_type) : NULL;
461 	g_simple_async_result_set_op_res_gpointer (res, closure, perform_closure_free);
462 
463 	if (window_id == NULL)
464 		window_id = "";
465 
466 	owner_name = g_dbus_proxy_get_name_owner (proxy);
467 	object_path = g_dbus_proxy_get_object_path (proxy);
468 
469 	closure->signal = g_dbus_connection_signal_subscribe (closure->connection, owner_name,
470 	                                                      SECRET_PROMPT_INTERFACE,
471 	                                                      SECRET_PROMPT_SIGNAL_COMPLETED,
472 	                                                      object_path, NULL,
473 	                                                      G_DBUS_SIGNAL_FLAGS_NONE,
474 	                                                      on_prompt_completed,
475 	                                                      g_object_ref (res),
476 	                                                      g_object_unref);
477 
478 	closure->watch = g_bus_watch_name_on_connection (closure->connection, owner_name,
479 	                                                 G_BUS_NAME_WATCHER_FLAGS_NONE, NULL,
480 	                                                 on_prompt_vanished,
481 	                                                 g_object_ref (res),
482 	                                                 g_object_unref);
483 
484 	if (closure->async_cancellable) {
485 		closure->cancelled_sig = g_cancellable_connect (closure->async_cancellable,
486 		                                                G_CALLBACK (on_prompt_cancelled),
487 		                                                res, NULL);
488 	}
489 
490 	g_dbus_proxy_call (proxy, "Prompt", g_variant_new ("(s)", window_id),
491 	                   G_DBUS_CALL_FLAGS_NO_AUTO_START, -1,
492 	                   closure->call_cancellable, on_prompt_prompted, g_object_ref (res));
493 
494 	g_object_unref (res);
495 	g_free (owner_name);
496 }
497 
498 /**
499  * secret_prompt_perform_finish:
500  * @self: a prompt
501  * @result: the asynchronous result passed to the callback
502  * @error: location to place an error on failure
503  *
504  * Complete asynchronous operation to run a prompt and perform the prompting.
505  *
506  * Returns a variant result if the prompt was completed and not dismissed. The
507  * type of result depends on the action the prompt is completing, and is
508  * defined in the Secret Service DBus API specification.
509  *
510  * Returns: (transfer full): %NULL if the prompt was dismissed or an error occurred,
511  *          a variant result if the prompt was successful
512  */
513 GVariant *
514 secret_prompt_perform_finish (SecretPrompt *self,
515                               GAsyncResult *result,
516                               GError **error)
517 {
518 	PerformClosure *closure;
519 	GSimpleAsyncResult *res;
520 	gchar *string;
521 
522 	g_return_val_if_fail (SECRET_IS_PROMPT (self), NULL);
523 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
524 	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
525 	                                                      secret_prompt_perform), NULL);
526 
527 	res = G_SIMPLE_ASYNC_RESULT (result);
528 
529 	if (_secret_util_propagate_error (res, error))
530 		return NULL;
531 
532 	closure = g_simple_async_result_get_op_res_gpointer (res);
533 	if (closure->result == NULL)
534 		return NULL;
535 	if (closure->return_type != NULL && !g_variant_is_of_type (closure->result, closure->return_type)) {
536 		string = g_variant_type_dup_string (closure->return_type);
537 		g_warning ("received unexpected result type %s from Completed signal instead of expected %s",
538 		           g_variant_get_type_string (closure->result), string);
539 		g_free (string);
540 		return NULL;
541 	}
542 	return g_variant_ref (closure->result);
543 }
544