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