Bump version number
[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: Unstable
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: 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-zero then it is used as an XWindow id. The Secret
136  * Service can make its prompt transient for the window with this id. In some
137  * Secret Service implementations this is not possible, so the behavior
138  * depending on this should degrade gracefully.
139  *
140  * This runs the dialog in a recursive mainloop. When run from a user interface
141  * thread, this means the user interface will remain responsive. Care should be
142  * taken that appropriate user interface actions are disabled while running the
143  * prompt.
144  *
145  * Returns: (transfer full): %NULL if the prompt was dismissed or an error occurred
146  */
147 GVariant *
148 secret_prompt_run (SecretPrompt *self,
149                    gulong window_id,
150                    GCancellable *cancellable,
151                    const GVariantType *return_type,
152                    GError **error)
153 {
154         GMainContext *context;
155         RunClosure *closure;
156         GVariant *retval;
157
158         g_return_val_if_fail (SECRET_IS_PROMPT (self), NULL);
159         g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
160         g_return_val_if_fail (error == NULL || *error == NULL, NULL);
161
162         context = g_main_context_get_thread_default ();
163
164         closure = g_new0 (RunClosure, 1);
165         closure->loop = g_main_loop_new (context, FALSE);
166
167         secret_prompt_perform (self, window_id, cancellable,
168                                on_prompt_run_complete, closure);
169
170         g_main_loop_run (closure->loop);
171
172         retval = secret_prompt_perform_finish (self, closure->result, return_type, error);
173
174         g_main_loop_unref (closure->loop);
175         g_object_unref (closure->result);
176         g_free (closure);
177
178         return retval;
179 }
180
181 /**
182  * secret_prompt_perform_sync:
183  * @self: a prompt
184  * @window_id: XWindow id for parent window to be transient for
185  * @cancellable: optional cancellation object
186  * @return_type: the variant type of the prompt result
187  * @error: location to place an error on failure
188  *
189  * Runs a prompt and performs the prompting. Returns a variant result if the
190  * prompt was completed and not dismissed. The type of result depends on the
191  * action the prompt is completing, and is defined in the Secret Service DBus
192  * API specification.
193  *
194  * If @window_id is non-zero then it is used as an XWindow id. The Secret
195  * Service can make its prompt transient for the window with this id. In some
196  * Secret Service implementations this is not possible, so the behavior
197  * 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                             gulong 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 } PerformClosure;
245
246 static void
247 perform_closure_free (gpointer data)
248 {
249         PerformClosure *closure = data;
250         g_object_unref (closure->call_cancellable);
251         g_clear_object (&closure->async_cancellable);
252         g_object_unref (closure->connection);
253         if (closure->result)
254                 g_variant_unref (closure->result);
255         g_assert (closure->signal == 0);
256         g_assert (closure->watch == 0);
257         g_slice_free (PerformClosure, closure);
258 }
259
260 static void
261 perform_prompt_complete (GSimpleAsyncResult *res,
262                          gboolean dismissed)
263 {
264         PerformClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
265
266         closure->dismissed = dismissed;
267         if (closure->completed)
268                 return;
269         closure->completed = TRUE;
270
271         if (closure->signal)
272                 g_dbus_connection_signal_unsubscribe (closure->connection, closure->signal);
273         closure->signal = 0;
274
275         if (closure->watch)
276                 g_bus_unwatch_name (closure->watch);
277         closure->watch = 0;
278
279         if (closure->cancelled_sig)
280                 g_signal_handler_disconnect (closure->async_cancellable, closure->cancelled_sig);
281         closure->cancelled_sig = 0;
282
283         g_simple_async_result_complete (res);
284 }
285
286 static void
287 on_prompt_completed (GDBusConnection *connection,
288                      const gchar *sender_name,
289                      const gchar *object_path,
290                      const gchar *interface_name,
291                      const gchar *signal_name,
292                      GVariant *parameters,
293                      gpointer user_data)
294 {
295         GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
296         SecretPrompt *self = SECRET_PROMPT (g_async_result_get_source_object (user_data));
297         PerformClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
298         gboolean dismissed;
299
300         closure->prompting = FALSE;
301
302         if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(bv)"))) {
303                 g_warning ("SecretPrompt received invalid %s signal of type %s",
304                            signal_name, g_variant_get_type_string (parameters));
305                 perform_prompt_complete (res, TRUE);
306
307         } else {
308                 g_variant_get (parameters, "(bv)", &dismissed, &closure->result);
309                 perform_prompt_complete (res, dismissed);
310         }
311
312         g_object_unref (self);
313 }
314
315 static void
316 on_prompt_prompted (GObject *source,
317                     GAsyncResult *result,
318                     gpointer user_data)
319 {
320         GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
321         PerformClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
322         SecretPrompt *self = SECRET_PROMPT (source);
323         GError *error = NULL;
324         GVariant *retval;
325
326         retval = g_dbus_proxy_call_finish (G_DBUS_PROXY (self), result, &error);
327
328         if (retval)
329                 g_variant_unref (retval);
330         if (closure->vanished)
331                 g_clear_error (&error);
332
333         if (error != NULL) {
334                 _secret_util_strip_remote_error (&error);
335                 g_simple_async_result_take_error (res, error);
336                 perform_prompt_complete (res, TRUE);
337
338         } else {
339                 closure->prompting = TRUE;
340                 g_atomic_int_set (&self->pv->prompted, 1);
341
342                 /* And now we wait for the signal */
343         }
344
345         g_object_unref (res);
346 }
347
348 static void
349 on_prompt_vanished (GDBusConnection *connection,
350                     const gchar *name,
351                     gpointer user_data)
352 {
353         GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
354         PerformClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
355         closure->vanished = TRUE;
356         g_cancellable_cancel (closure->call_cancellable);
357         perform_prompt_complete (res, TRUE);
358 }
359
360 static void
361 on_prompt_dismissed (GObject *source,
362                      GAsyncResult *result,
363                      gpointer user_data)
364 {
365         GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
366         PerformClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
367         SecretPrompt *self = SECRET_PROMPT (source);
368         GError *error = NULL;
369         GVariant *retval;
370
371         retval = g_dbus_proxy_call_finish (G_DBUS_PROXY (self), result, &error);
372
373         if (retval)
374                 g_variant_unref (retval);
375         if (closure->vanished)
376                 g_clear_error (&error);
377         if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
378                 g_clear_error (&error);
379
380         if (error != NULL) {
381                 _secret_util_strip_remote_error (&error);
382                 g_simple_async_result_take_error (res, error);
383                 perform_prompt_complete (res, TRUE);
384         }
385
386         g_object_unref (res);
387 }
388
389 static void
390 on_prompt_cancelled (GCancellable *cancellable,
391                      gpointer user_data)
392 {
393         GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
394         PerformClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
395         SecretPrompt *self = SECRET_PROMPT (g_async_result_get_source_object (user_data));
396
397         /* Instead of cancelling our dbus calls, we cancel the prompt itself via this dbus call */
398
399         g_dbus_proxy_call (G_DBUS_PROXY (self), "Dismiss", g_variant_new ("()"),
400                            G_DBUS_CALL_FLAGS_NO_AUTO_START, -1,
401                            closure->call_cancellable,
402                            on_prompt_dismissed, g_object_ref (res));
403
404         g_object_unref (self);
405 }
406
407 /**
408  * secret_prompt_perform:
409  * @self: a prompt
410  * @window_id: XWindow id for parent window to be transient for
411  * @cancellable: optional cancellation object
412  * @callback: called when the operation completes
413  * @user_data: data to be passed to the callback
414  *
415  * Runs a prompt and performs the prompting. Returns %TRUE if the prompt
416  * was completed and not dismissed.
417  *
418  * If @window_id is non-zero then it is used as an XWindow id. The Secret
419  * Service can make its prompt transient for the window with this id. In some
420  * Secret Service implementations this is not possible, so the behavior
421  * depending on this should degrade gracefully.
422  *
423  * This method will return immediately and complete asynchronously.
424  */
425 void
426 secret_prompt_perform (SecretPrompt *self,
427                        gulong window_id,
428                        GCancellable *cancellable,
429                        GAsyncReadyCallback callback,
430                        gpointer user_data)
431 {
432         GSimpleAsyncResult *res;
433         PerformClosure *closure;
434         const gchar *owner_name;
435         const gchar *object_path;
436         gboolean prompted;
437         GDBusProxy *proxy;
438         gchar *window;
439
440         g_return_if_fail (SECRET_IS_PROMPT (self));
441         g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
442
443         prompted = g_atomic_int_get (&self->pv->prompted);
444         if (prompted) {
445                 g_warning ("The prompt object has already had its prompt called.");
446                 return;
447         }
448
449         proxy = G_DBUS_PROXY (self);
450
451         res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
452                                          secret_prompt_perform);
453         closure = g_slice_new0 (PerformClosure);
454         closure->connection = g_object_ref (g_dbus_proxy_get_connection (proxy));
455         closure->call_cancellable = g_cancellable_new ();
456         closure->async_cancellable = cancellable ? g_object_ref (cancellable) : NULL;
457         g_simple_async_result_set_op_res_gpointer (res, closure, perform_closure_free);
458
459         if (window_id == 0)
460                 window = g_strdup ("");
461         else
462                 window = g_strdup_printf ("%lu", window_id);
463
464         owner_name = g_dbus_proxy_get_name_owner (proxy);
465         object_path = g_dbus_proxy_get_object_path (proxy);
466
467         closure->signal = g_dbus_connection_signal_subscribe (closure->connection, owner_name,
468                                                               SECRET_PROMPT_INTERFACE,
469                                                               SECRET_PROMPT_SIGNAL_COMPLETED,
470                                                               object_path, NULL,
471                                                               G_DBUS_SIGNAL_FLAGS_NONE,
472                                                               on_prompt_completed,
473                                                               g_object_ref (res),
474                                                               g_object_unref);
475
476         closure->watch = g_bus_watch_name_on_connection (closure->connection, owner_name,
477                                                          G_BUS_NAME_WATCHER_FLAGS_NONE, NULL,
478                                                          on_prompt_vanished,
479                                                          g_object_ref (res),
480                                                          g_object_unref);
481
482         if (closure->async_cancellable) {
483                 closure->cancelled_sig = g_cancellable_connect (closure->async_cancellable,
484                                                                 G_CALLBACK (on_prompt_cancelled),
485                                                                 res, NULL);
486         }
487
488         g_dbus_proxy_call (proxy, "Prompt", g_variant_new ("(s)", window),
489                            G_DBUS_CALL_FLAGS_NO_AUTO_START, -1,
490                            closure->call_cancellable, on_prompt_prompted, g_object_ref (res));
491
492         g_free (window);
493         g_object_unref (res);
494 }
495
496 /**
497  * secret_prompt_perform_finish:
498  * @self: a prompt
499  * @result: the asynchronous result passed to the callback
500  * @return_type: the variant type of the prompt result
501  * @error: location to place an error on failure
502  *
503  * Complete asynchronous operation to run a prompt and perform the prompting.
504  *
505  * Returns a variant result if the prompt was completed and not dismissed. The
506  * type of result depends on the action the prompt is completing, and is
507  * defined in the Secret Service DBus API specification.
508  *
509  * Returns: (transfer full): %NULL if the prompt was dismissed or an error occurred,
510  *          a variant result if the prompt was successful
511  */
512 GVariant *
513 secret_prompt_perform_finish (SecretPrompt *self,
514                               GAsyncResult *result,
515                               const GVariantType *return_type,
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 (g_simple_async_result_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 (return_type != NULL && !g_variant_is_of_type (closure->result, return_type)) {
536                 string = g_variant_type_dup_string (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 }