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