/* * Copyright (C) 2015 Red Hat, Inc. (www.redhat.com) * * This library is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "evolution-data-server-config.h" #include #include #include #include #include "e-credentials-prompter.h" /* built-in credentials prompter implementations */ #include "e-credentials-prompter-impl-password.h" #include "e-credentials-prompter-impl-oauth2.h" typedef struct _ProcessPromptData { GWeakRef *prompter; ECredentialsPrompterImpl *prompter_impl; ESource *auth_source; ESource *cred_source; ESourceConnectionStatus connection_status; /* of the auth_source */ gboolean remember_password; /* of the cred_source, to check for changes */ gulong notify_handler_id; gchar *error_text; ENamedParameters *credentials; gboolean allow_source_save; GSimpleAsyncResult *async_result; } ProcessPromptData; struct _ECredentialsPrompterPrivate { ESourceRegistry *registry; ESourceCredentialsProvider *provider; gboolean auto_prompt; GCancellable *cancellable; GMutex disabled_auto_prompt_lock; GHashTable *disabled_auto_prompt; /* gchar *source_uid ~> 1; Source UIDs for which the auto-prompt is disabled */ GMutex prompters_lock; GHashTable *prompters; /* gchar *method ~> ECredentialsPrompterImpl *impl */ GHashTable *known_prompters; /* gpointer [ECredentialsPrompterImpl] ~> UINT known instances; the prompter_impl is not referenced */ GRecMutex queue_lock; /* guards all queue and schedule related properties */ GSList *queue; /* ProcessPromptData * */ ProcessPromptData *processing_prompt; gulong schedule_idle_id; }; enum { PROP_0, PROP_AUTO_PROMPT, PROP_REGISTRY, PROP_PROVIDER }; enum { GET_DIALOG_PARENT, GET_DIALOG_PARENT_FULL, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; G_DEFINE_TYPE_WITH_CODE (ECredentialsPrompter, e_credentials_prompter, G_TYPE_OBJECT, G_ADD_PRIVATE (ECredentialsPrompter) G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)) static void process_prompt_data_free (gpointer ptr) { ProcessPromptData *ppd = ptr; if (ppd) { if (ppd->notify_handler_id > 0) g_signal_handler_disconnect (ppd->auth_source, ppd->notify_handler_id); if (ppd->async_result) { ECredentialsPrompter *prompter; prompter = g_weak_ref_get (ppd->prompter); if (prompter) { e_credentials_prompter_complete_prompt_call (prompter, ppd->async_result, ppd->auth_source, NULL, NULL); g_clear_object (&prompter); } } e_weak_ref_free (ppd->prompter); g_clear_object (&ppd->prompter_impl); g_clear_object (&ppd->auth_source); g_clear_object (&ppd->cred_source); g_free (ppd->error_text); e_named_parameters_free (ppd->credentials); g_slice_free (ProcessPromptData, ppd); } } typedef struct _LookupSourceDetailsData { ESource *auth_source; /* an ESource which asked for credentials */ ESource *cred_source; /* this might be auth_source or a parent collection source, if applicable, from where the credentials come */ ENamedParameters *credentials; /* actual stored credentials */ } LookupSourceDetailsData; static void lookup_source_details_data_free (gpointer ptr) { LookupSourceDetailsData *data = ptr; if (data) { g_clear_object (&data->auth_source); g_clear_object (&data->cred_source); e_named_parameters_free (data->credentials); g_slice_free (LookupSourceDetailsData, data); } } static void credentials_prompter_lookup_source_details_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { ESource *source, *cred_source = NULL; ECredentialsPrompter *prompter; ESourceCredentialsProvider *provider; ENamedParameters *credentials = NULL; GError *local_error = NULL; g_return_if_fail (E_IS_SOURCE (source_object)); source = E_SOURCE (source_object); prompter = g_weak_ref_get (task_data); if (!prompter) return; provider = e_credentials_prompter_get_provider (prompter); cred_source = e_source_credentials_provider_ref_credentials_source (provider, source); e_source_credentials_provider_lookup_sync (provider, cred_source ? cred_source : source, cancellable, &credentials, &local_error); /* Interested only in the cancelled error, which means the prompter is freed. */ if (local_error != NULL && g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_task_return_error (task, local_error); local_error = NULL; } else { LookupSourceDetailsData *data; data = g_slice_new0 (LookupSourceDetailsData); data->auth_source = g_object_ref (source); data->cred_source = g_object_ref (cred_source ? cred_source : source); /* always set both, for simplicity */ data->credentials = credentials; /* NULL for no credentials available */ /* To not be freed below. */ credentials = NULL; g_task_return_pointer (task, data, lookup_source_details_data_free); } e_named_parameters_free (credentials); g_clear_object (&cred_source); g_clear_object (&prompter); g_clear_error (&local_error); } static void credentials_prompter_lookup_source_details (ESource *source, ECredentialsPrompter *prompter, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; g_return_if_fail (E_IS_SOURCE (source)); g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); task = g_task_new (source, prompter->priv->cancellable, callback, user_data); g_task_set_source_tag (task, credentials_prompter_lookup_source_details_thread); g_task_set_task_data (task, e_weak_ref_new (prompter), (GDestroyNotify) e_weak_ref_free); g_task_run_in_thread (task, credentials_prompter_lookup_source_details_thread); g_object_unref (task); } static gboolean credentials_prompter_lookup_source_details_finish (ESource *source, GAsyncResult *result, ECredentialsPrompter **out_prompter, /* will be referenced, if not NULL */ LookupSourceDetailsData **out_data, GError **error) { LookupSourceDetailsData *data; g_return_val_if_fail (E_IS_SOURCE (source), FALSE); g_return_val_if_fail (out_prompter != NULL, FALSE); g_return_val_if_fail (out_data != NULL, FALSE); g_return_val_if_fail (g_task_is_valid (result, source), FALSE); g_return_val_if_fail ( g_async_result_is_tagged ( result, credentials_prompter_lookup_source_details_thread), FALSE); data = g_task_propagate_pointer (G_TASK (result), error); if (!data) return FALSE; *out_data = data; *out_prompter = g_weak_ref_get (g_task_get_task_data (G_TASK (result))); return TRUE; } static void credentials_prompter_invoke_authenticate_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GError *error = NULL; if (!e_source_invoke_authenticate_finish (E_SOURCE (source_object), result, &error) && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_debug ("%s: Failed to invoke authenticate: %s", G_STRFUNC, error ? error->message : "Unknown error"); } g_clear_error (&error); } typedef struct _CredentialsPromptData { ESource *source; gchar *error_text; ECredentialsPrompterPromptFlags flags; GTask *complete_task; GSimpleAsyncResult *async_result; } CredentialsPromptData; static void credentials_prompt_data_free (gpointer ptr) { CredentialsPromptData *data = ptr; if (data) { if (data->async_result) { g_simple_async_result_set_error (data->async_result, G_IO_ERROR, G_IO_ERROR_CANCELLED, "%s", _("Credentials prompt was cancelled")); g_simple_async_result_complete_in_idle (data->async_result); g_clear_object (&data->async_result); } g_clear_object (&data->source); g_free (data->error_text); g_slice_free (CredentialsPromptData, data); } } typedef struct _CredentialsResultData { ESource *source; ENamedParameters *credentials; } CredentialsResultData; static void credentials_result_data_free (gpointer ptr) { CredentialsResultData *data = ptr; if (data) { g_clear_object (&data->source); e_named_parameters_free (data->credentials); g_slice_free (CredentialsResultData, data); } } static void credentials_prompter_maybe_process_next_prompt (ECredentialsPrompter *prompter) { g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); g_rec_mutex_lock (&prompter->priv->queue_lock); /* Already processing one */ if (prompter->priv->processing_prompt) { g_rec_mutex_unlock (&prompter->priv->queue_lock); return; } if (prompter->priv->queue) { ProcessPromptData *ppd = prompter->priv->queue->data; g_warn_if_fail (ppd != NULL); prompter->priv->queue = g_slist_remove (prompter->priv->queue, ppd); prompter->priv->processing_prompt = ppd; e_credentials_prompter_impl_prompt (ppd->prompter_impl, ppd, ppd->auth_source, ppd->cred_source, ppd->error_text, ppd->credentials); } g_rec_mutex_unlock (&prompter->priv->queue_lock); } static gboolean credentials_prompter_process_next_prompt_idle_cb (gpointer user_data) { ECredentialsPrompter *prompter = user_data; if (g_source_is_destroyed (g_main_current_source ())) return FALSE; g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter), FALSE); g_rec_mutex_lock (&prompter->priv->queue_lock); if (g_source_get_id (g_main_current_source ()) == prompter->priv->schedule_idle_id) { prompter->priv->schedule_idle_id = 0; credentials_prompter_maybe_process_next_prompt (prompter); } g_rec_mutex_unlock (&prompter->priv->queue_lock); return FALSE; } static void credentials_prompter_schedule_process_next_prompt (ECredentialsPrompter *prompter) { g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); g_rec_mutex_lock (&prompter->priv->queue_lock); /* Already processing one */ if (prompter->priv->processing_prompt || prompter->priv->schedule_idle_id) { g_rec_mutex_unlock (&prompter->priv->queue_lock); return; } prompter->priv->schedule_idle_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE, credentials_prompter_process_next_prompt_idle_cb, prompter, NULL); g_rec_mutex_unlock (&prompter->priv->queue_lock); } static void credentials_prompter_connection_status_changed_cb (ESource *source, GParamSpec *param, ECredentialsPrompter *prompter) { g_return_if_fail (E_IS_SOURCE (source)); g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); /* Do not cancel the prompt when the source is still waiting for the credentials. */ if (e_source_get_connection_status (source) == E_SOURCE_CONNECTION_STATUS_AWAITING_CREDENTIALS) return; g_rec_mutex_lock (&prompter->priv->queue_lock); if (prompter->priv->processing_prompt && e_source_equal (prompter->priv->processing_prompt->auth_source, source)) { e_credentials_prompter_impl_cancel_prompt (prompter->priv->processing_prompt->prompter_impl, prompter->priv->processing_prompt); } else { GSList *link; for (link = prompter->priv->queue; link; link = g_slist_next (link)) { ProcessPromptData *ppd = link->data; g_warn_if_fail (ppd != NULL); if (ppd && e_source_equal (ppd->auth_source, source)) { if (ppd->connection_status != e_source_get_connection_status (source)) { prompter->priv->queue = g_slist_remove (prompter->priv->queue, ppd); process_prompt_data_free (ppd); } break; } } } g_rec_mutex_unlock (&prompter->priv->queue_lock); } static gboolean e_credentials_prompter_eval_remember_password (ESource *source) { gboolean remember_password = FALSE; if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) remember_password = e_source_authentication_get_remember_password ( e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)); return remember_password; } static void e_credentials_prompter_manage_impl_prompt (ECredentialsPrompter *prompter, ECredentialsPrompterImpl *prompter_impl, ESource *auth_source, ESource *cred_source, const gchar *error_text, const ENamedParameters *credentials, gboolean allow_source_save, GSimpleAsyncResult *async_result) { GSList *link; gboolean success = TRUE; g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); g_return_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL (prompter_impl)); g_return_if_fail (E_IS_SOURCE (auth_source)); g_return_if_fail (E_IS_SOURCE (cred_source)); g_return_if_fail (credentials != NULL); g_rec_mutex_lock (&prompter->priv->queue_lock); for (link = prompter->priv->queue; link; link = g_slist_next (link)) { ProcessPromptData *ppd = link->data; g_warn_if_fail (ppd != NULL); if (ppd && e_source_equal (ppd->auth_source, auth_source)) { break; } } if (link != NULL || (prompter->priv->processing_prompt && e_source_equal (prompter->priv->processing_prompt->auth_source, auth_source))) { /* have queued or already asking for credentials for this source */ success = FALSE; } else { ProcessPromptData *ppd; ppd = g_slice_new0 (ProcessPromptData); ppd->prompter = e_weak_ref_new (prompter); ppd->prompter_impl = g_object_ref (prompter_impl); ppd->auth_source = g_object_ref (auth_source); ppd->cred_source = g_object_ref (cred_source); ppd->connection_status = e_source_get_connection_status (ppd->auth_source); ppd->remember_password = e_credentials_prompter_eval_remember_password (ppd->cred_source); ppd->error_text = g_strdup (error_text); ppd->credentials = e_named_parameters_new_clone (credentials); ppd->allow_source_save = allow_source_save; ppd->async_result = async_result ? g_object_ref (async_result) : NULL; /* If the prompter doesn't auto-prompt, then it should not auto-close the prompt as well. */ if (e_credentials_prompter_get_auto_prompt (prompter)) { ppd->notify_handler_id = g_signal_connect (ppd->auth_source, "notify::connection-status", G_CALLBACK (credentials_prompter_connection_status_changed_cb), prompter); } else { ppd->notify_handler_id = 0; } prompter->priv->queue = g_slist_append (prompter->priv->queue, ppd); credentials_prompter_schedule_process_next_prompt (prompter); } g_rec_mutex_unlock (&prompter->priv->queue_lock); if (!success && async_result) { e_credentials_prompter_complete_prompt_call (prompter, async_result, auth_source, NULL, NULL); } } static void credentials_prompter_store_credentials_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GError *error = NULL; if (!e_source_credentials_provider_store_finish (E_SOURCE_CREDENTIALS_PROVIDER (source_object), result, &error) && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_warning ("%s: Failed to store source credentials: %s", G_STRFUNC, error ? error->message : "Unknown error"); } g_clear_error (&error); } static void credentials_prompter_source_write_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { ESource *source = E_SOURCE (source_object); GError *error = NULL; if (!e_source_write_finish (source, result, &error) && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_warning ("%s: Failed to write source '%s' (%s) changes: %s", G_STRFUNC, e_source_get_uid (source), e_source_get_display_name (source), error ? error->message : "Unknown error"); } g_clear_error (&error); } static void credentials_prompter_update_username_for_children (ESourceRegistry *registry, ESource *collection_source, gboolean allow_source_save, const gchar *old_username, const gchar *new_username, GCancellable *cancellable) { GList *sources, *link; const gchar *parent_uid; gchar *collection_host; gboolean username_changed; g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); g_return_if_fail (E_IS_SOURCE (collection_source)); parent_uid = e_source_get_uid (collection_source); if (!parent_uid || !*parent_uid) return; collection_host = e_source_authentication_dup_host (e_source_get_extension (collection_source, E_SOURCE_EXTENSION_AUTHENTICATION)); username_changed = g_strcmp0 (old_username, new_username) != 0; sources = e_source_registry_list_sources (registry, NULL); for (link = sources; link; link = g_list_next (link)) { ESource *child = link->data; if (g_strcmp0 (e_source_get_parent (child), parent_uid) == 0 && e_source_get_writable (child) && e_source_has_extension (child, E_SOURCE_EXTENSION_AUTHENTICATION)) { ESourceAuthentication *auth_extension; gchar *child_username, *child_host; auth_extension = e_source_get_extension (child, E_SOURCE_EXTENSION_AUTHENTICATION); child_username = e_source_authentication_dup_user (auth_extension); child_host = e_source_authentication_dup_host (auth_extension); if ((!child_host || !*child_host || !collection_host || !*collection_host || g_ascii_strcasecmp (child_host, collection_host) == 0) && (!child_username || !*child_username || !old_username || !*old_username || (username_changed && g_strcmp0 (child_username, old_username) == 0))) { e_source_authentication_set_user (auth_extension, new_username); if (allow_source_save) { e_source_write (child, cancellable, credentials_prompter_source_write_cb, NULL); } } g_free (child_username); g_free (child_host); } } g_list_free_full (sources, g_object_unref); g_free (collection_host); } static void e_credentials_prompter_prompt_finish_for_source (ECredentialsPrompter *prompter, ProcessPromptData *ppd, const ENamedParameters *credentials) { ESource *cred_source; gboolean changed = FALSE; g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); g_return_if_fail (ppd != NULL); if (!credentials) return; cred_source = ppd->cred_source; if (e_source_has_extension (cred_source, E_SOURCE_EXTENSION_AUTHENTICATION)) { ESourceAuthentication *auth_extension = e_source_get_extension (cred_source, E_SOURCE_EXTENSION_AUTHENTICATION); gboolean could_use_collection; could_use_collection = e_source_has_extension (cred_source, E_SOURCE_EXTENSION_COLLECTION); if (e_source_get_writable (cred_source)) { const gchar *username; username = e_named_parameters_get (credentials, E_SOURCE_CREDENTIAL_USERNAME); if (username && *username) { gchar *old_username; old_username = e_source_authentication_dup_user (auth_extension); /* Sync the changed user name to the child sources of the collection as well */ if (ppd->auth_source == cred_source && e_source_has_extension (cred_source, E_SOURCE_EXTENSION_COLLECTION)) { credentials_prompter_update_username_for_children ( e_credentials_prompter_get_registry (prompter), cred_source, ppd->allow_source_save, old_username, username, prompter->priv->cancellable); /* Update the collection source as the last, due to tests for the old username in the credentials_prompter_update_username_for_children(). */ if (g_strcmp0 (username, old_username) != 0) { e_source_authentication_set_user (auth_extension, username); changed = TRUE; } } else if (g_strcmp0 (username, old_username) != 0) { if (ppd->auth_source != cred_source && e_source_has_extension (cred_source, E_SOURCE_EXTENSION_COLLECTION)) { auth_extension = e_source_get_extension (ppd->auth_source, E_SOURCE_EXTENSION_AUTHENTICATION); e_source_authentication_set_user (auth_extension, username); if (ppd->allow_source_save && e_source_get_writable (ppd->auth_source)) { e_source_write (ppd->auth_source, prompter->priv->cancellable, credentials_prompter_source_write_cb, NULL); } } else { e_source_authentication_set_user (auth_extension, username); changed = TRUE; } } g_free (old_username); } } if (could_use_collection && !e_util_can_use_collection_as_credential_source (cred_source, ppd->auth_source)) { /* Copy also the remember-password flag */ e_source_authentication_set_remember_password (e_source_get_extension (ppd->auth_source, E_SOURCE_EXTENSION_AUTHENTICATION), e_credentials_prompter_eval_remember_password (cred_source)); cred_source = ppd->auth_source; } } if (e_source_has_extension (cred_source, E_SOURCE_EXTENSION_AUTHENTICATION)) { ESourceAuthentication *auth_extension = e_source_get_extension (cred_source, E_SOURCE_EXTENSION_AUTHENTICATION); if (e_source_credentials_provider_can_store (e_credentials_prompter_get_provider (prompter), cred_source)) { e_source_credentials_provider_store (e_credentials_prompter_get_provider (prompter), cred_source, credentials, e_source_authentication_get_remember_password (auth_extension), prompter->priv->cancellable, credentials_prompter_store_credentials_cb, NULL); } } if (ppd->allow_source_save && e_source_get_writable (cred_source) && (changed || (ppd->remember_password ? 1 : 0) != (e_credentials_prompter_eval_remember_password (cred_source) ? 1 : 0))) { e_source_write (cred_source, prompter->priv->cancellable, credentials_prompter_source_write_cb, NULL); } if (ppd->async_result) { ECredentialsPrompter *ppd_prompter; ppd_prompter = g_weak_ref_get (ppd->prompter); if (ppd_prompter) { e_credentials_prompter_complete_prompt_call (ppd_prompter, ppd->async_result, ppd->auth_source, credentials, NULL); g_clear_object (&ppd_prompter); /* To not be completed multiple times */ g_clear_object (&ppd->async_result); } } else { e_source_invoke_authenticate (ppd->auth_source, credentials, prompter->priv->cancellable, credentials_prompter_invoke_authenticate_cb, NULL); } } static void credentials_prompter_prompt_finished_cb (ECredentialsPrompterImpl *prompter_impl, gpointer prompt_id, const ENamedParameters *credentials, ECredentialsPrompter *prompter) { g_return_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL (prompter_impl)); g_return_if_fail (prompt_id != NULL); g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); g_rec_mutex_lock (&prompter->priv->queue_lock); if (prompt_id == prompter->priv->processing_prompt) { ProcessPromptData *ppd = prompter->priv->processing_prompt; GSList *link, *to_remove = NULL; prompter->priv->processing_prompt = NULL; e_credentials_prompter_prompt_finish_for_source (prompter, ppd, credentials); /* Finish also any other pending prompts for the same credentials source as was finished this one. This can be relevant to collection sources. */ for (link = prompter->priv->queue; link; link = g_slist_next (link)) { ProcessPromptData *sub_ppd = link->data; if (sub_ppd && sub_ppd->cred_source && e_source_equal (sub_ppd->cred_source, ppd->cred_source)) { to_remove = g_slist_prepend (to_remove, sub_ppd); } } for (link = to_remove; link; link = g_slist_next (link)) { ProcessPromptData *sub_ppd = link->data; if (sub_ppd) { prompter->priv->queue = g_slist_remove (prompter->priv->queue, sub_ppd); e_credentials_prompter_prompt_finish_for_source (prompter, sub_ppd, credentials); } } g_slist_free_full (to_remove, process_prompt_data_free); process_prompt_data_free (ppd); credentials_prompter_schedule_process_next_prompt (prompter); } else { g_warning ("%s: Unknown prompt_id %p", G_STRFUNC, prompt_id); } g_rec_mutex_unlock (&prompter->priv->queue_lock); } static gboolean credentials_prompter_prompt_with_source_details (ECredentialsPrompter *prompter, LookupSourceDetailsData *data, const gchar *error_text, ECredentialsPrompterPromptFlags flags, GSimpleAsyncResult *async_result) { ECredentialsPrompterImpl *prompter_impl = NULL; gchar *method = NULL; gboolean success = TRUE; g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter), FALSE); g_return_val_if_fail (data != NULL, FALSE); if (e_source_has_extension (data->cred_source, E_SOURCE_EXTENSION_AUTHENTICATION)) { ESourceAuthentication *authentication = e_source_get_extension (data->cred_source, E_SOURCE_EXTENSION_AUTHENTICATION); method = e_source_authentication_dup_method (authentication); } g_mutex_lock (&prompter->priv->prompters_lock); prompter_impl = g_hash_table_lookup (prompter->priv->prompters, method ? method : ""); if (!prompter_impl && method && *method) prompter_impl = g_hash_table_lookup (prompter->priv->prompters, ""); if (prompter_impl) g_object_ref (prompter_impl); g_mutex_unlock (&prompter->priv->prompters_lock); if (prompter_impl) { ENamedParameters *credentials; credentials = e_named_parameters_new (); if (data->credentials) e_named_parameters_assign (credentials, data->credentials); if (async_result && data->credentials && (flags & E_CREDENTIALS_PROMPTER_PROMPT_FLAG_ALLOW_STORED_CREDENTIALS) != 0) { e_credentials_prompter_complete_prompt_call (prompter, async_result, data->auth_source, credentials, NULL); } else if (!e_source_credentials_provider_can_prompt (prompter->priv->provider, data->auth_source)) { /* This source cannot be asked for credentials, thus end with a 'not supported' error. */ GError *error; error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Source “%s” doesn’t support prompt for credentials"), e_source_get_display_name (data->cred_source)); if (async_result) e_credentials_prompter_complete_prompt_call (prompter, async_result, data->auth_source, NULL, error); g_clear_error (&error); } else { e_credentials_prompter_manage_impl_prompt (prompter, prompter_impl, data->auth_source, data->cred_source, error_text, credentials, !async_result || (flags & E_CREDENTIALS_PROMPTER_PROMPT_FLAG_ALLOW_SOURCE_SAVE) != 0, async_result); } e_named_parameters_free (credentials); } else { /* Shoud not happen, because the password prompter is added as the default prompter. */ g_warning ("%s: No prompter impl found for an authentication method '%s'", G_STRFUNC, method ? method : ""); success = FALSE; } g_clear_object (&prompter_impl); g_free (method); return success; } static void credentials_prompter_lookup_source_details_before_prompt_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { CredentialsPromptData *prompt_data = user_data; ECredentialsPrompter *prompter = NULL; LookupSourceDetailsData *data = NULL; GError *error = NULL; g_return_if_fail (prompt_data != NULL); g_return_if_fail (E_IS_SOURCE (source_object)); if (!credentials_prompter_lookup_source_details_finish (E_SOURCE (source_object), result, &prompter, &data, &error)) { g_clear_error (&error); credentials_prompt_data_free (prompt_data); return; } if (credentials_prompter_prompt_with_source_details (prompter, data, prompt_data->error_text, prompt_data->flags, prompt_data->async_result)) { /* To not finish the async_result multiple times */ g_clear_object (&prompt_data->async_result); } g_clear_object (&prompter); credentials_prompt_data_free (prompt_data); lookup_source_details_data_free (data); } static void credentials_prompter_lookup_source_details_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { LookupSourceDetailsData *data = NULL; ECredentialsPrompter *prompter = NULL; ESource *source; GError *error = NULL; g_return_if_fail (E_IS_SOURCE (source_object)); source = E_SOURCE (source_object); if (!credentials_prompter_lookup_source_details_finish (source, result, &prompter, &data, &error)) { g_clear_error (&error); return; } g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); g_return_if_fail (data != NULL); if (data->credentials) { e_source_invoke_authenticate (E_SOURCE (data->auth_source), data->credentials, prompter->priv->cancellable, credentials_prompter_invoke_authenticate_cb, NULL); } else { credentials_prompter_prompt_with_source_details (prompter, data, NULL, 0, NULL); } lookup_source_details_data_free (data); g_clear_object (&prompter); } static void credentials_prompter_credentials_required_cb (ESourceRegistry *registry, ESource *source, ESourceCredentialsReason reason, const gchar *certificate_pem, GTlsCertificateFlags certificate_errors, const GError *op_error, ECredentialsPrompter *prompter) { ESource *cred_source; g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); g_return_if_fail (E_IS_SOURCE (source)); g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); /* Only these two reasons are meant to be used to prompt the user for credentials. */ if (reason != E_SOURCE_CREDENTIALS_REASON_REQUIRED && reason != E_SOURCE_CREDENTIALS_REASON_REJECTED) { return; } if (!e_source_registry_check_enabled (prompter->priv->registry, source)) return; cred_source = e_source_credentials_provider_ref_credentials_source (e_credentials_prompter_get_provider (prompter), source); /* Global auto-prompt or the source's auto-prompt is disabled. */ if (!e_credentials_prompter_get_auto_prompt (prompter) || (e_credentials_prompter_get_auto_prompt_disabled_for (prompter, source) && (!cred_source || e_credentials_prompter_get_auto_prompt_disabled_for (prompter, cred_source)))) { g_clear_object (&cred_source); return; } g_clear_object (&cred_source); /* This is a re-prompt, but the source cannot be prompted for credentials. */ if (reason == E_SOURCE_CREDENTIALS_REASON_REJECTED && !e_source_credentials_provider_can_prompt (prompter->priv->provider, source)) { return; } if (reason == E_SOURCE_CREDENTIALS_REASON_REQUIRED) { credentials_prompter_lookup_source_details (source, prompter, credentials_prompter_lookup_source_details_cb, NULL); return; } e_credentials_prompter_prompt (prompter, source, op_error ? op_error->message : NULL, 0, NULL, NULL); } static gboolean credentials_prompter_get_dialog_parent_accumulator (GSignalInvocationHint *ihint, GValue *return_accu, const GValue *handler_return, gpointer data) { if (handler_return && g_value_get_object (handler_return) != NULL) { g_value_set_object (return_accu, g_value_get_object (handler_return)); return FALSE; } return TRUE; } static void credentials_prompter_set_registry (ECredentialsPrompter *prompter, ESourceRegistry *registry) { g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); g_return_if_fail (prompter->priv->registry == NULL); prompter->priv->registry = g_object_ref (registry); prompter->priv->provider = e_source_credentials_provider_new (prompter->priv->registry); g_signal_connect (prompter->priv->registry, "credentials-required", G_CALLBACK (credentials_prompter_credentials_required_cb), prompter); } static void credentials_prompter_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_REGISTRY: credentials_prompter_set_registry ( E_CREDENTIALS_PROMPTER (object), g_value_get_object (value)); return; case PROP_AUTO_PROMPT: e_credentials_prompter_set_auto_prompt ( E_CREDENTIALS_PROMPTER (object), g_value_get_boolean (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void credentials_prompter_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_REGISTRY: g_value_set_object (value, e_credentials_prompter_get_registry ( E_CREDENTIALS_PROMPTER (object))); return; case PROP_PROVIDER: g_value_set_object (value, e_credentials_prompter_get_provider ( E_CREDENTIALS_PROMPTER (object))); return; case PROP_AUTO_PROMPT: g_value_set_boolean (value, e_credentials_prompter_get_auto_prompt ( E_CREDENTIALS_PROMPTER (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void credentials_prompter_constructed (GObject *object) { /* Chain up to parent's method. */ G_OBJECT_CLASS (e_credentials_prompter_parent_class)->constructed (object); e_extensible_load_extensions (E_EXTENSIBLE (object)); } static void credentials_prompter_dispose (GObject *object) { ECredentialsPrompter *prompter = E_CREDENTIALS_PROMPTER (object); GHashTableIter iter; gpointer key, value; if (prompter->priv->cancellable) { g_cancellable_cancel (prompter->priv->cancellable); g_clear_object (&prompter->priv->cancellable); } if (prompter->priv->registry) { g_signal_handlers_disconnect_by_data (prompter->priv->registry, prompter); g_clear_object (&prompter->priv->registry); } g_rec_mutex_lock (&prompter->priv->queue_lock); if (prompter->priv->schedule_idle_id) { g_source_remove (prompter->priv->schedule_idle_id); prompter->priv->schedule_idle_id = 0; } g_rec_mutex_unlock (&prompter->priv->queue_lock); g_clear_object (&prompter->priv->provider); g_mutex_lock (&prompter->priv->prompters_lock); g_hash_table_iter_init (&iter, prompter->priv->prompters); while (g_hash_table_iter_next (&iter, &key, &value)) { ECredentialsPrompterImpl *prompter_impl = value; g_signal_handlers_disconnect_by_func (prompter_impl, credentials_prompter_prompt_finished_cb, prompter); } g_hash_table_remove_all (prompter->priv->prompters); g_hash_table_remove_all (prompter->priv->known_prompters); g_mutex_unlock (&prompter->priv->prompters_lock); /* Chain up to parent's method. */ G_OBJECT_CLASS (e_credentials_prompter_parent_class)->dispose (object); } static void credentials_prompter_finalize (GObject *object) { ECredentialsPrompter *prompter = E_CREDENTIALS_PROMPTER (object); g_hash_table_destroy (prompter->priv->prompters); g_hash_table_destroy (prompter->priv->known_prompters); g_mutex_clear (&prompter->priv->prompters_lock); g_hash_table_destroy (prompter->priv->disabled_auto_prompt); g_mutex_clear (&prompter->priv->disabled_auto_prompt_lock); g_rec_mutex_clear (&prompter->priv->queue_lock); /* Chain up to parent's method. */ G_OBJECT_CLASS (e_credentials_prompter_parent_class)->finalize (object); } static void e_credentials_prompter_class_init (ECredentialsPrompterClass *class) { GObjectClass *object_class; object_class = G_OBJECT_CLASS (class); object_class->set_property = credentials_prompter_set_property; object_class->get_property = credentials_prompter_get_property; object_class->constructed = credentials_prompter_constructed; object_class->dispose = credentials_prompter_dispose; object_class->finalize = credentials_prompter_finalize; /** * ECredentialsPrompter:auto-prompt: * * Whether the #ECredentialsPrompter can response to credential * requests automatically. * * Since: 3.16 **/ g_object_class_install_property ( object_class, PROP_AUTO_PROMPT, g_param_spec_boolean ( "auto-prompt", "Auto Prompt", "Whether can response to credential requests automatically", TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); /** * ECredentialsPrompter:registry: * * The #ESourceRegistry object, to whose credential requests the prompter listens. * * Since: 3.16 **/ g_object_class_install_property ( object_class, PROP_REGISTRY, g_param_spec_object ( "registry", "Registry", "An ESourceRegistry", E_TYPE_SOURCE_REGISTRY, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * ECredentialsPrompter:provider: * * The #ESourceCredentialsProvider object, which the prompter uses. * * Since: 3.16 **/ g_object_class_install_property ( object_class, PROP_PROVIDER, g_param_spec_object ( "provider", "Provider", "An ESourceCredentialsProvider", E_TYPE_SOURCE_CREDENTIALS_PROVIDER, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * ECredentialsPrompter::get-dialog-parent: * @prompter: the #ECredentialsPrompter which emitted the signal * * Emitted when a new dialog will be shown, to get the right parent * window for it. If the result of the call is %NULL, then it tries * to get the window from the default GtkApplication. * * Returns: (transfer none) (nullable): a #GtkWindow, to be used as a * dialog parent, or %NULL. * * Since: 3.16 **/ signals[GET_DIALOG_PARENT] = g_signal_new ( "get-dialog-parent", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ECredentialsPrompterClass, get_dialog_parent), credentials_prompter_get_dialog_parent_accumulator, NULL, NULL, GTK_TYPE_WINDOW, 0, G_TYPE_NONE); /** * ECredentialsPrompter::get-dialog-parent-full: * @prompter: the #ECredentialsPrompter which emitted the signal * @auth_source: (nullable): an #ESource, for which to show the credentials prompt * * Emitted when a new dialog will be shown, to get the right parent * window for it. If the result of the call is %NULL, then it tries * to get the window from the default GtkApplication. * * Returns: (transfer none) (nullable): a #GtkWindow, to be used as a * dialog parent, or %NULL. * * Since: 3.42 **/ signals[GET_DIALOG_PARENT_FULL] = g_signal_new ( "get-dialog-parent-full", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, /*G_STRUCT_OFFSET (ECredentialsPrompterClass, get_dialog_parent_full)*/ 0, credentials_prompter_get_dialog_parent_accumulator, NULL, NULL, GTK_TYPE_WINDOW, 1, E_TYPE_SOURCE); /* Ensure built-in credential providers implementation types */ g_type_ensure (E_TYPE_CREDENTIALS_PROMPTER_IMPL_PASSWORD); g_type_ensure (E_TYPE_CREDENTIALS_PROMPTER_IMPL_OAUTH2); } static void e_credentials_prompter_init (ECredentialsPrompter *prompter) { prompter->priv = e_credentials_prompter_get_instance_private (prompter); prompter->priv->auto_prompt = TRUE; prompter->priv->provider = NULL; prompter->priv->cancellable = g_cancellable_new (); g_mutex_init (&prompter->priv->prompters_lock); prompter->priv->prompters = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, g_free, g_object_unref); prompter->priv->known_prompters = g_hash_table_new (g_direct_hash, g_direct_equal); g_mutex_init (&prompter->priv->disabled_auto_prompt_lock); prompter->priv->disabled_auto_prompt = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); g_rec_mutex_init (&prompter->priv->queue_lock); } /** * e_credentials_prompter_new: * @registry: an #ESourceRegistry to have the prompter listen to * * Creates a new #ECredentialsPrompter, which listens for credential requests * from @registry. * * Returns: (transfer full): a new #ECredentialsPrompter * * Since: 3.16 **/ ECredentialsPrompter * e_credentials_prompter_new (ESourceRegistry *registry) { return g_object_new (E_TYPE_CREDENTIALS_PROMPTER, "registry", registry, NULL); } /** * e_credentials_prompter_get_registry: * @prompter: an #ECredentialsPrompter * * Returns an #ESourceRegistry, to which the @prompter listens. * * Returns: (transfer none): an #ESourceRegistry, to which the @prompter listens. * * Since: 3.16 **/ ESourceRegistry * e_credentials_prompter_get_registry (ECredentialsPrompter *prompter) { g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter), NULL); return prompter->priv->registry; } /** * e_credentials_prompter_get_provider: * @prompter: an #ECredentialsPrompter * * Returns an #ESourceCredentialsProvider, which the @prompter uses. * * Returns: (transfer none): an #ESourceCredentialsProvider, which the @prompter uses. * * Since: 3.16 **/ ESourceCredentialsProvider * e_credentials_prompter_get_provider (ECredentialsPrompter *prompter) { g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter), NULL); g_return_val_if_fail (prompter->priv->provider != NULL, NULL); return prompter->priv->provider; } /** * e_credentials_prompter_get_auto_prompt: * @prompter: an #ECredentialsPrompter * * Returns, whether can respond to credential prompts automatically. * Default value is %TRUE. * * This property does not influence direct calls of e_credentials_prompter_prompt(). * * Returns: Whether can respond to credential prompts automatically. * * Since: 3.16 **/ gboolean e_credentials_prompter_get_auto_prompt (ECredentialsPrompter *prompter) { g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter), FALSE); return prompter->priv->auto_prompt; } /** * e_credentials_prompter_set_auto_prompt: * @prompter: an #ECredentialsPrompter * @auto_prompt: new value of the auto-prompt property * * Sets whether can respond to credential prompts automatically. That means that * whenever any ESource will ask for credentials, it'll try to provide them. * * Use e_credentials_prompter_set_auto_prompt_disabled_for() to influence * auto-prompt per an #ESource. * * This property does not influence direct calls of e_credentials_prompter_prompt(). * * Since: 3.16 **/ void e_credentials_prompter_set_auto_prompt (ECredentialsPrompter *prompter, gboolean auto_prompt) { g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); if ((prompter->priv->auto_prompt ? 1 : 0) == (auto_prompt ? 1 : 0)) return; prompter->priv->auto_prompt = auto_prompt; g_object_notify (G_OBJECT (prompter), "auto-prompt"); } /** * e_credentials_prompter_set_auto_prompt_disabled_for: * @prompter: an #ECredentialsPrompter * @source: an #ESource * @is_disabled: whether the auto-prompt should be disabled for this @source * * Sets whether the auto-prompt should be disabled for the given @source. * All sources can be auto-prompted by default. This is a complementary * value for the ECredentialsPrompter::auto-prompt property. * * This value does not influence direct calls of e_credentials_prompter_prompt(). * * Since: 3.16 **/ void e_credentials_prompter_set_auto_prompt_disabled_for (ECredentialsPrompter *prompter, ESource *source, gboolean is_disabled) { g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); g_return_if_fail (E_IS_SOURCE (source)); g_return_if_fail (e_source_get_uid (source) != NULL); g_mutex_lock (&prompter->priv->disabled_auto_prompt_lock); if (is_disabled) g_hash_table_insert (prompter->priv->disabled_auto_prompt, g_strdup (e_source_get_uid (source)), GINT_TO_POINTER (1)); else g_hash_table_remove (prompter->priv->disabled_auto_prompt, e_source_get_uid (source)); g_mutex_unlock (&prompter->priv->disabled_auto_prompt_lock); } /** * e_credentials_prompter_get_auto_prompt_disabled_for: * @prompter: an #ECredentialsPrompter * @source: an #ESource * * Returns whether the auto-prompt is disabled for the given @source. * All sources can be auto-prompted by default. This is a complementary * value for the ECredentialsPrompter::auto-prompt property. * * This value does not influence direct calls of e_credentials_prompter_prompt(). * * Returns: Whether the auto-prompt is disabled for the given @source * * Since: 3.16 **/ gboolean e_credentials_prompter_get_auto_prompt_disabled_for (ECredentialsPrompter *prompter, ESource *source) { gboolean is_disabled; g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter), TRUE); g_return_val_if_fail (E_IS_SOURCE (source), TRUE); g_return_val_if_fail (e_source_get_uid (source) != NULL, TRUE); g_mutex_lock (&prompter->priv->disabled_auto_prompt_lock); is_disabled = g_hash_table_contains (prompter->priv->disabled_auto_prompt, e_source_get_uid (source)); g_mutex_unlock (&prompter->priv->disabled_auto_prompt_lock); return is_disabled; } static GtkWindow * credentials_prompter_guess_dialog_parent (ECredentialsPrompter *prompter) { GApplication *app; app = g_application_get_default (); if (!app) return NULL; if (GTK_IS_APPLICATION (app)) return gtk_application_get_active_window (GTK_APPLICATION (app)); return NULL; } /** * e_credentials_prompter_get_dialog_parent: * @prompter: an #ECredentialsPrompter * * Returns a #GtkWindow, which should be used as a dialog parent. This is determined * by an ECredentialsPrompter::get-dialog-parent signal emission. If there is no callback * registered or the current callbacks don't have any suitable window, then there's * chosen the last active window from the default GApplication, if any available. * * Returns: (transfer none) (nullable): a #GtkWindow, to be used as a dialog parent, * or %NULL. * * Since: 3.16 **/ GtkWindow * e_credentials_prompter_get_dialog_parent (ECredentialsPrompter *prompter) { GtkWindow *parent = NULL; g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter), NULL); g_signal_emit (prompter, signals[GET_DIALOG_PARENT], 0, &parent); if (!parent) parent = credentials_prompter_guess_dialog_parent (prompter); return parent; } /** * e_credentials_prompter_get_dialog_parent_full: * @prompter: an #ECredentialsPrompter * @auth_source: (nullable): an #ESource * * Returns a #GtkWindow, which should be used as a dialog parent for the @auth_source. * * This is determined by an ECredentialsPrompter::get-dialog-parent-full signal emission * and an ECredentialsPrompter::get-dialog-parent when the first doesn't return anything. * If there is no callback registered or the current callbacks don't have any suitable * window, then there's chosen the last active window from the default GApplication, * if any available. * * Returns: (transfer none) (nullable): a #GtkWindow, to be used as a dialog parent, * or %NULL. * * Since: 3.42 **/ GtkWindow * e_credentials_prompter_get_dialog_parent_full (ECredentialsPrompter *prompter, ESource *auth_source) { GtkWindow *parent = NULL; g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter), NULL); if (auth_source) g_return_val_if_fail (E_IS_SOURCE (auth_source), NULL); g_signal_emit (prompter, signals[GET_DIALOG_PARENT_FULL], 0, auth_source, &parent); if (!parent) g_signal_emit (prompter, signals[GET_DIALOG_PARENT], 0, &parent); if (!parent) parent = credentials_prompter_guess_dialog_parent (prompter); return parent; } /** * e_credentials_prompter_register_impl: * @prompter: an #ECredentialsPrompter * @authentication_method: (nullable): an authentication method to registr @prompter_impl for; or %NULL * @prompter_impl: an #ECredentialsPrompterImpl * * Registers a prompter implementation for a given authentication method. If there is * registered a prompter for the same @authentication_method, then the function does * nothing, otherwise it adds its own reference on the @prompter_impl, and uses it * for that authentication method. One @prompter_impl can be registered for multiple * authentication methods. * * A special value %NULL can be used for the @authentication_method, which means * a default credentials prompter, that is to be used when there is no prompter * registered for the exact authentication method. * * Returns: %TRUE on success, %FALSE on failure or when there was another prompter * implementation registered for the given authentication method. * * Since: 3.16 **/ gboolean e_credentials_prompter_register_impl (ECredentialsPrompter *prompter, const gchar *authentication_method, ECredentialsPrompterImpl *prompter_impl) { guint known_prompters; g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter), FALSE); g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL (prompter_impl), FALSE); if (!authentication_method) authentication_method = ""; g_mutex_lock (&prompter->priv->prompters_lock); if (g_hash_table_lookup (prompter->priv->prompters, authentication_method) != NULL) { g_mutex_unlock (&prompter->priv->prompters_lock); return FALSE; } g_hash_table_insert (prompter->priv->prompters, g_strdup (authentication_method), g_object_ref (prompter_impl)); known_prompters = GPOINTER_TO_UINT (g_hash_table_lookup (prompter->priv->known_prompters, prompter_impl)); if (!known_prompters) { g_signal_connect (prompter_impl, "prompt-finished", G_CALLBACK (credentials_prompter_prompt_finished_cb), prompter); } g_hash_table_insert (prompter->priv->known_prompters, prompter_impl, GUINT_TO_POINTER (known_prompters + 1)); g_mutex_unlock (&prompter->priv->prompters_lock); return TRUE; } /** * e_credentials_prompter_unregister_impl: * @prompter: an #ECredentialsPrompter * @authentication_method: (nullable): an authentication method to registr @prompter_impl for; or %NULL * @prompter_impl: an #ECredentialsPrompterImpl * * Unregisters previously registered @prompter_impl for the given @autnetication_method with * e_credentials_prompter_register_impl(). Function does nothing, if no such authentication * method is registered or if it has set a different prompter implementation. * * Since: 3.16 **/ void e_credentials_prompter_unregister_impl (ECredentialsPrompter *prompter, const gchar *authentication_method, ECredentialsPrompterImpl *prompter_impl) { ECredentialsPrompterImpl *current_prompter_impl; g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); if (!authentication_method) authentication_method = ""; g_mutex_lock (&prompter->priv->prompters_lock); current_prompter_impl = g_hash_table_lookup (prompter->priv->prompters, authentication_method); if (current_prompter_impl == prompter_impl) { guint known_prompters; known_prompters = GPOINTER_TO_UINT (g_hash_table_lookup (prompter->priv->known_prompters, prompter_impl)); if (known_prompters == 1) { g_signal_handlers_disconnect_by_func (prompter_impl, credentials_prompter_prompt_finished_cb, prompter); g_hash_table_remove (prompter->priv->known_prompters, prompter_impl); } else { known_prompters--; g_hash_table_insert (prompter->priv->known_prompters, prompter_impl, GUINT_TO_POINTER (known_prompters + 1)); } g_hash_table_remove (prompter->priv->prompters, authentication_method); } g_mutex_unlock (&prompter->priv->prompters_lock); } static void credentials_prompter_get_last_credentials_required_arguments_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { ECredentialsPrompter *prompter = user_data; ESource *source; ESourceCredentialsReason reason = E_SOURCE_CREDENTIALS_REASON_UNKNOWN; gchar *certificate_pem = NULL; GTlsCertificateFlags certificate_errors = 0; GError *op_error = NULL; GError *error = NULL; g_return_if_fail (E_IS_SOURCE (source_object)); source = E_SOURCE (source_object); if (!e_source_get_last_credentials_required_arguments_finish (source, result, &reason, &certificate_pem, &certificate_errors, &op_error, &error)) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_warning ("%s: Failed to get last credential values: %s", G_STRFUNC, error ? error->message : "Unknown error"); } g_clear_error (&error); return; } /* Can check only now, when know the operation was not cancelled and the prompter freed. */ g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); /* Check once again, as this was called asynchronously and anything could change meanwhile. */ if (e_source_get_connection_status (source) == E_SOURCE_CONNECTION_STATUS_AWAITING_CREDENTIALS) { credentials_prompter_credentials_required_cb (prompter->priv->registry, source, reason, certificate_pem, certificate_errors, op_error, prompter); } g_free (certificate_pem); g_clear_error (&op_error); } /** * e_credentials_prompter_process_awaiting_credentials: * @prompter: an #ECredentialsPrompter * * Process all enabled sources with connection state #E_SOURCE_CONNECTION_STATUS_AWAITING_CREDENTIALS, * like if they just asked for its credentials for the first time. * * Since: 3.16 **/ void e_credentials_prompter_process_awaiting_credentials (ECredentialsPrompter *prompter) { GList *sources, *link; g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); sources = e_source_registry_list_enabled (prompter->priv->registry, NULL); for (link = sources; link; link = g_list_next (link)) { ESource *source = link->data; if (!source) continue; if (e_source_get_connection_status (source) == E_SOURCE_CONNECTION_STATUS_AWAITING_CREDENTIALS) { /* Check what failed the last time */ e_credentials_prompter_process_source (prompter, source); } } g_list_free_full (sources, g_object_unref); } /** * e_credentials_prompter_process_source: * @prompter: an #ECredentialsPrompter * @source: an #ESource * * Continues a credential prompt for @source. Returns, whether anything will be done. * The %FALSE either means that the @source's connection status is not * the %E_SOURCE_CONNECTION_STATUS_AWAITING_CREDENTIALS or it is disabled. * * Returns: Whether continues with the credentials prompt. * * Since: 3.16 **/ gboolean e_credentials_prompter_process_source (ECredentialsPrompter *prompter, ESource *source) { g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter), FALSE); g_return_val_if_fail (E_IS_SOURCE (source), FALSE); if (e_source_get_connection_status (source) != E_SOURCE_CONNECTION_STATUS_AWAITING_CREDENTIALS || !e_source_registry_check_enabled (prompter->priv->registry, source)) return FALSE; e_source_get_last_credentials_required_arguments (source, prompter->priv->cancellable, credentials_prompter_get_last_credentials_required_arguments_cb, prompter); return TRUE; } /** * e_credentials_prompter_prompt: * @prompter: an #ECredentialsPrompter * @source: an #ESource, which prompt the credentials for * @error_text: (nullable): Additional error text to show to a user, or %NULL * @flags: a bit-or of #ECredentialsPrompterPromptFlags * @callback: a callback to call when the credentials are ready, or %NULL * @user_data: user data passed into @callback * * Asks the @prompter to prompt for credentials, which are returned * to the caller through @callback, when available.The @flags are ignored, * when the @callback is %NULL; the credentials are passed to the @source * with e_source_invoke_authenticate() directly, in this case. * Call e_credentials_prompter_prompt_finish() in @callback to get to * the provided credentials. * * Since: 3.16 **/ void e_credentials_prompter_prompt (ECredentialsPrompter *prompter, ESource *source, const gchar *error_text, ECredentialsPrompterPromptFlags flags, GAsyncReadyCallback callback, gpointer user_data) { CredentialsPromptData *prompt_data; g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); g_return_if_fail (E_IS_SOURCE (source)); prompt_data = g_slice_new0 (CredentialsPromptData); prompt_data->source = g_object_ref (source); prompt_data->error_text = g_strdup (error_text); prompt_data->flags = flags; prompt_data->async_result = callback ? g_simple_async_result_new (G_OBJECT (prompter), callback, user_data, e_credentials_prompter_prompt) : NULL; /* Just it can be shown in the UI as a prefilled value and the right source (collection) is used. */ credentials_prompter_lookup_source_details (source, prompter, credentials_prompter_lookup_source_details_before_prompt_cb, prompt_data); } /** * e_credentials_prompter_prompt_finish: * @prompter: an #ECredentialsPrompter * @result: a #GAsyncResult * @out_source: (transfer full) (out) (optional) (nullable): optionally set to an #ESource, on which the prompt was started; can be %NULL * @out_credentials: (transfer full) (out) (nullable): set to an #ENamedParameters with provied credentials * @error: return location for a #GError, or %NULL * * Finishes a credentials prompt previously started with e_credentials_prompter_prompt(). * The @out_source will have set a referenced #ESource, for which the prompt * was started. Unref it, when no longer needed. Similarly the @out_credentials * will have set a newly allocated #ENamedParameters structure with provided credentials, * which should be freed with e_named_credentials_free() when no longer needed. * Both output arguments will be set to %NULL on error and %FALSE will be returned. * * Returns: %TRUE on success, %FALSE otherwise. * * Since: 3.16 **/ gboolean e_credentials_prompter_prompt_finish (ECredentialsPrompter *prompter, GAsyncResult *result, ESource **out_source, ENamedParameters **out_credentials, GError **error) { CredentialsResultData *data; g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter), FALSE); g_return_val_if_fail (g_simple_async_result_get_source_tag (G_SIMPLE_ASYNC_RESULT (result)) == e_credentials_prompter_prompt, FALSE); g_return_val_if_fail (out_credentials, FALSE); if (out_source) *out_source = NULL; *out_credentials = NULL; if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error)) return FALSE; data = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)); g_return_val_if_fail (data != NULL, FALSE); if (data->credentials) { if (out_source) *out_source = g_object_ref (data->source); *out_credentials = e_named_parameters_new_clone (data->credentials); } else { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Credentials prompt was cancelled")); return FALSE; } return TRUE; } /** * e_credentials_prompter_complete_prompt_call: * @prompter: an #ECredentialsPrompter * @async_result: a #GSimpleAsyncResult * @source: an #ESource, on which the prompt was started * @credentials: (nullable): credentials, as provided by a user, on %NULL, when the prompt was cancelled * @error: a resulting #GError, or %NULL * * Completes an ongoing credentials prompt on idle, by finishing the @async_result. * This function is meant to be used by an #ECredentialsPrompterImpl implementation. * To actually finish the credentials prompt previously started with * e_credentials_prompter_prompt(), the e_credentials_prompter_prompt_finish() should * be called from the provided callback. * * Using %NULL @credentials will result in a G_IO_ERROR_CANCELLED error, if * no other @error is provided. * * Since: 3.16 **/ void e_credentials_prompter_complete_prompt_call (ECredentialsPrompter *prompter, GSimpleAsyncResult *async_result, ESource *source, const ENamedParameters *credentials, const GError *error) { g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter)); g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (async_result)); g_return_if_fail (g_simple_async_result_get_source_tag (async_result) == e_credentials_prompter_prompt); g_return_if_fail (source == NULL || E_IS_SOURCE (source)); if (credentials) g_return_if_fail (E_IS_SOURCE (source)); if (error) { g_simple_async_result_set_from_error (async_result, error); } else if (!credentials) { g_simple_async_result_set_error (async_result, G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Credentials prompt was cancelled")); } else { CredentialsResultData *result; result = g_slice_new0 (CredentialsResultData); result->source = g_object_ref (source); result->credentials = e_named_parameters_new_clone (credentials); g_simple_async_result_set_op_res_gpointer (async_result, result, credentials_result_data_free); } g_simple_async_result_complete_in_idle (async_result); } static gboolean credentials_prompter_prompt_sync (ECredentialsPrompter *prompter, ESource *source, gboolean is_retry, ECredentialsPrompterPromptFlags *flags, const gchar *error_text, ENamedParameters **out_credentials, GCancellable *cancellable, GError **error) { gboolean res = FALSE; ESourceCredentialsProvider *credentials_provider; ENamedParameters *credentials = NULL; g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter), FALSE); g_return_val_if_fail (E_IS_SOURCE (source), FALSE); g_return_val_if_fail (flags != NULL, FALSE); g_return_val_if_fail (out_credentials != NULL, FALSE); if (g_cancellable_set_error_if_cancelled (cancellable, error)) return FALSE; credentials_provider = e_credentials_prompter_get_provider (prompter); if (!is_retry) { ESource *cred_source; GError *local_error = NULL; cred_source = e_source_credentials_provider_ref_credentials_source (credentials_provider, source); if (e_source_credentials_provider_lookup_sync (credentials_provider, cred_source ? cred_source : source, cancellable, &credentials, &local_error)) { res = TRUE; } else if (!g_cancellable_is_cancelled (cancellable)) { /* To prompt for the password directly */ is_retry = TRUE; g_clear_error (&local_error); } else { g_propagate_error (error, local_error); } g_clear_object (&cred_source); } if (is_retry) { EAsyncClosure *closure; GAsyncResult *result; *flags = (*flags) & (~E_CREDENTIALS_PROMPTER_PROMPT_FLAG_ALLOW_STORED_CREDENTIALS); closure = e_async_closure_new (); e_credentials_prompter_prompt (prompter, source, error_text, *flags, e_async_closure_callback, closure); result = e_async_closure_wait (closure); if (e_credentials_prompter_prompt_finish (prompter, result, NULL, &credentials, error)) { res = TRUE; } e_async_closure_free (closure); } if (res && credentials) *out_credentials = e_named_parameters_new_clone (credentials); e_named_parameters_free (credentials); return res; } /** * e_credentials_prompter_loop_prompt_sync: * @prompter: an #ECredentialsPrompter * @source: an #ESource to be prompted credentials for * @flags: a bit-or of #ECredentialsPrompterPromptFlags initial flags * @func: (scope call): an #ECredentialsPrompterLoopPromptFunc user function to call to check provided credentials * @user_data: user data to pass to @func * @cancellable: an optional #GCancellable, or %NULL * @error: a #GError, to store any errors to, or %NULL * * Runs a credentials prompt loop for @source, as long as the @func doesn't * indicate that the provided credentials can be used to successfully * authenticate against @source's server, or that the @func * returns %FALSE. The loop is also teminated when a used cancels * the credentials prompt or the @cancellable is cancelled, though * not sooner than the credentials prompt dialog is closed. * * Note: The function doesn't return until the loop is terminated, either * successfully or unsuccessfully. The function can be called from any * thread, though a dedicated thread is preferred. * * Returns: %TRUE, when the credentials were provided successfully and they * can be used to authenticate the @source; %FALSE otherwise. * * Since: 3.16 **/ gboolean e_credentials_prompter_loop_prompt_sync (ECredentialsPrompter *prompter, ESource *source, ECredentialsPrompterPromptFlags flags, ECredentialsPrompterLoopPromptFunc func, gpointer user_data, GCancellable *cancellable, GError **error) { gboolean is_retry, authenticated; ENamedParameters *credentials = NULL; g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER (prompter), FALSE); g_return_val_if_fail (E_IS_SOURCE (source), FALSE); g_return_val_if_fail (func != NULL, FALSE); is_retry = FALSE; authenticated = FALSE; while (!authenticated && !g_cancellable_is_cancelled (cancellable)) { GError *local_error = NULL; e_named_parameters_free (credentials); credentials = NULL; if (!credentials_prompter_prompt_sync (prompter, source, is_retry, &flags, NULL, &credentials, cancellable, error)) break; if (g_cancellable_set_error_if_cancelled (cancellable, error)) break; g_clear_error (&local_error); if (!func (prompter, source, credentials, &authenticated, user_data, cancellable, &local_error)) { if (local_error) g_propagate_error (error, local_error); break; } is_retry = TRUE; } e_named_parameters_free (credentials); return authenticated; }