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