[evolution-data-server] Bug 789169 - [CalDAV] Ask for credentials when PUT fails with Unauthorized



commit 252f5be5e2fb7ae235e99ec08d347e16beb19e7a
Author: Milan Crha <mcrha redhat com>
Date:   Tue Oct 24 15:25:39 2017 +0200

    Bug 789169 - [CalDAV] Ask for credentials when PUT fails with Unauthorized

 .../backends/webdav/e-book-backend-webdav.c        |  106 +++++---
 .../libedata-book/e-book-meta-backend.c            |  305 ++++++++++++++++++--
 .../libedata-book/e-book-meta-backend.h            |   11 +-
 .../backends/caldav/e-cal-backend-caldav.c         |  106 +++++---
 src/calendar/libedata-cal/e-cal-meta-backend.c     |  305 ++++++++++++++++++--
 src/calendar/libedata-cal/e-cal-meta-backend.h     |   10 +-
 6 files changed, 719 insertions(+), 124 deletions(-)
---
diff --git a/src/addressbook/backends/webdav/e-book-backend-webdav.c 
b/src/addressbook/backends/webdav/e-book-backend-webdav.c
index b3fe648..8a0c116 100644
--- a/src/addressbook/backends/webdav/e-book-backend-webdav.c
+++ b/src/addressbook/backends/webdav/e-book-backend-webdav.c
@@ -543,6 +543,32 @@ ebb_webdav_search_changes_cb (EBookCache *book_cache,
        return TRUE;
 }
 
+static void
+ebb_webdav_check_credentials_error (EBookBackendWebDAV *bbdav,
+                                   GError *op_error)
+{
+       g_return_if_fail (E_IS_BOOK_BACKEND_WEBDAV (bbdav));
+
+       if (g_error_matches (op_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED) && bbdav->priv->webdav) {
+               op_error->domain = E_DATA_BOOK_ERROR;
+               op_error->code = E_DATA_BOOK_STATUS_TLS_NOT_AVAILABLE;
+       } else if (g_error_matches (op_error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) ||
+                  g_error_matches (op_error, SOUP_HTTP_ERROR, SOUP_STATUS_FORBIDDEN)) {
+               op_error->domain = E_DATA_BOOK_ERROR;
+               op_error->code = E_DATA_BOOK_STATUS_AUTHENTICATION_REQUIRED;
+
+               if (bbdav->priv->webdav) {
+                       ENamedParameters *credentials;
+
+                       credentials = e_soup_session_dup_credentials (E_SOUP_SESSION (bbdav->priv->webdav));
+                       if (credentials && e_named_parameters_count (credentials) > 0)
+                               op_error->code = E_DATA_BOOK_STATUS_AUTHENTICATION_FAILED;
+
+                       e_named_parameters_free (credentials);
+               }
+       }
+}
+
 static gboolean
 ebb_webdav_get_changes_sync (EBookMetaBackend *meta_backend,
                             const gchar *last_sync_tag,
@@ -645,42 +671,10 @@ ebb_webdav_get_changes_sync (EBookMetaBackend *meta_backend,
                } while (success && link);
        }
 
-       if (local_error && !g_cancellable_is_cancelled (cancellable)) {
-               ESourceCredentialsReason reason = E_SOURCE_CREDENTIALS_REASON_UNKNOWN;
-
-               if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED)) {
-                       reason = E_SOURCE_CREDENTIALS_REASON_SSL_FAILED;
-               } else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) ||
-                          g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_FORBIDDEN)) {
-                       reason = E_SOURCE_CREDENTIALS_REASON_REQUIRED;
-               }
-
-               if (reason != E_SOURCE_CREDENTIALS_REASON_UNKNOWN) {
-                       GTlsCertificateFlags certificate_errors = 0;
-                       gchar *certificate_pem = NULL;
-                       GError *local_error2 = NULL;
-
-                       if (!e_soup_session_get_ssl_error_details (E_SOUP_SESSION (bbdav->priv->webdav), 
&certificate_pem, &certificate_errors)) {
-                               certificate_pem = NULL;
-                               certificate_errors = 0;
-                       }
-
-                       if (!e_backend_credentials_required_sync (E_BACKEND (bbdav), reason, certificate_pem, 
certificate_errors,
-                               local_error, cancellable, &local_error2)) {
-                               g_warning ("%s: Failed to call credentials required: %s", G_STRFUNC, 
local_error2 ? local_error2->message : "Unknown error");
-                       } else {
-                               /* Ignore the error when the caller had been notified through the 
credentials-required */
-                               g_clear_error (&local_error);
-                               success = TRUE;
-                       }
-
-                       g_clear_error (&local_error2);
-                       g_free (certificate_pem);
-               }
-       }
-
-       if (local_error)
+       if (local_error) {
+               ebb_webdav_check_credentials_error (bbdav, local_error);
                g_propagate_error (error, local_error);
+       }
 
        return success;
 }
@@ -744,6 +738,7 @@ ebb_webdav_list_existing_sync (EBookMetaBackend *meta_backend,
 {
        EBookBackendWebDAV *bbdav;
        EXmlDocument *xml;
+       GError *local_error = NULL;
        gboolean success;
 
        g_return_val_if_fail (E_IS_BOOK_BACKEND_WEBDAV (meta_backend), FALSE);
@@ -771,13 +766,18 @@ ebb_webdav_list_existing_sync (EBookMetaBackend *meta_backend,
        e_xml_document_end_element (xml); /* prop */
 
        success = e_webdav_session_report_sync (bbdav->priv->webdav, NULL, E_WEBDAV_DEPTH_THIS, xml,
-               ebb_webdav_extract_existing_cb, out_existing_objects, NULL, NULL, cancellable, error);
+               ebb_webdav_extract_existing_cb, out_existing_objects, NULL, NULL, cancellable, &local_error);
 
        g_object_unref (xml);
 
        if (success)
                *out_existing_objects = g_slist_reverse (*out_existing_objects);
 
+       if (local_error) {
+               ebb_webdav_check_credentials_error (bbdav, local_error);
+               g_propagate_error (error, local_error);
+       }
+
        return success;
 }
 
@@ -931,8 +931,10 @@ ebb_webdav_load_contact_sync (EBookMetaBackend *meta_backend,
        g_free (etag);
        g_free (bytes);
 
-       if (local_error)
+       if (local_error) {
+               ebb_webdav_check_credentials_error (bbdav, local_error);
                g_propagate_error (error, local_error);
+       }
 
        return success;
 }
@@ -951,6 +953,7 @@ ebb_webdav_save_contact_sync (EBookMetaBackend *meta_backend,
        EBookBackendWebDAV *bbdav;
        gchar *href = NULL, *etag = NULL, *uid = NULL;
        gchar *vcard_string = NULL;
+       GError *local_error = NULL;
        gboolean success;
 
        g_return_val_if_fail (E_IS_BOOK_BACKEND_WEBDAV (meta_backend), FALSE);
@@ -988,7 +991,7 @@ ebb_webdav_save_contact_sync (EBookMetaBackend *meta_backend,
 
                success = e_webdav_session_put_data_sync (bbdav->priv->webdav, (extra && *extra) ? extra : 
href,
                        force_write ? "" : overwrite_existing ? etag : NULL, E_WEBDAV_CONTENT_TYPE_VCARD,
-                       vcard_string, -1, out_new_extra, NULL, cancellable, error);
+                       vcard_string, -1, out_new_extra, NULL, cancellable, &local_error);
 
                /* To read the component back, because server can change it */
                if (success)
@@ -1003,6 +1006,11 @@ ebb_webdav_save_contact_sync (EBookMetaBackend *meta_backend,
        g_free (etag);
        g_free (uid);
 
+       if (local_error) {
+               ebb_webdav_check_credentials_error (bbdav, local_error);
+               g_propagate_error (error, local_error);
+       }
+
        return success;
 }
 
@@ -1071,12 +1079,31 @@ ebb_webdav_remove_contact_sync (EBookMetaBackend *meta_backend,
        g_object_unref (contact);
        g_free (etag);
 
-       if (local_error)
+       if (local_error) {
+               ebb_webdav_check_credentials_error (bbdav, local_error);
                g_propagate_error (error, local_error);
+       }
 
        return success;
 }
 
+static gboolean
+ebb_webdav_get_ssl_error_details (EBookMetaBackend *meta_backend,
+                                 gchar **out_certificate_pem,
+                                 GTlsCertificateFlags *out_certificate_errors)
+{
+       EBookBackendWebDAV *bbdav;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_WEBDAV (meta_backend), FALSE);
+
+       bbdav = E_BOOK_BACKEND_WEBDAV (meta_backend);
+
+       if (!bbdav->priv->webdav)
+               return FALSE;
+
+       return e_soup_session_get_ssl_error_details (E_SOUP_SESSION (bbdav->priv->webdav), 
out_certificate_pem, out_certificate_errors);
+}
+
 static gchar *
 ebb_webdav_get_backend_property (EBookBackend *book_backend,
                                 const gchar *prop_name)
@@ -1159,6 +1186,7 @@ e_book_backend_webdav_class_init (EBookBackendWebDAVClass *klass)
        book_meta_backend_class->load_contact_sync = ebb_webdav_load_contact_sync;
        book_meta_backend_class->save_contact_sync = ebb_webdav_save_contact_sync;
        book_meta_backend_class->remove_contact_sync = ebb_webdav_remove_contact_sync;
+       book_meta_backend_class->get_ssl_error_details = ebb_webdav_get_ssl_error_details;
 
        book_backend_class = E_BOOK_BACKEND_CLASS (klass);
        book_backend_class->get_backend_property = ebb_webdav_get_backend_property;
diff --git a/src/addressbook/libedata-book/e-book-meta-backend.c 
b/src/addressbook/libedata-book/e-book-meta-backend.c
index 860a42a..0b296bf 100644
--- a/src/addressbook/libedata-book/e-book-meta-backend.c
+++ b/src/addressbook/libedata-book/e-book-meta-backend.c
@@ -52,9 +52,18 @@
 
 #define LOCAL_PREFIX "file://"
 
+/* How many times can repeat an operation when credentials fail. */
+#define MAX_REPEAT_COUNT 3
+
+/* How long to wait for credentials, in seconds, during the operation repeat cycle */
+#define MAX_WAIT_FOR_CREDENTIALS_SECS 60
+
 struct _EBookMetaBackendPrivate {
        GMutex connect_lock;
        GMutex property_lock;
+       GMutex wait_credentials_lock;
+       GCond wait_credentials_cond;
+       guint wait_credentials_stamp;
        GError *create_cache_error;
        EBookCache *cache;
        ENamedParameters *last_credentials;
@@ -460,6 +469,14 @@ ebmb_requires_reconnect (EBookMetaBackend *meta_backend)
        return requires;
 }
 
+static gboolean
+ebmb_get_ssl_error_details (EBookMetaBackend *meta_backend,
+                           gchar **out_certificate_pem,
+                           GTlsCertificateFlags *out_certificate_errors)
+{
+       return FALSE;
+}
+
 static GSList * /* gchar * */
 ebmb_gather_photos_local_filenames (EBookMetaBackend *meta_backend,
                                    EContact *contact)
@@ -2023,6 +2040,11 @@ ebmb_authenticate_sync (EBackend *backend,
                g_set_error_literal (error, E_CLIENT_ERROR, E_CLIENT_ERROR_REPOSITORY_OFFLINE,
                        e_client_error_to_string (E_CLIENT_ERROR_REPOSITORY_OFFLINE));
 
+               g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+               meta_backend->priv->wait_credentials_stamp++;
+               g_cond_broadcast (&meta_backend->priv->wait_credentials_cond);
+               g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+
                return E_SOURCE_AUTHENTICATION_ERROR;
        }
 
@@ -2030,6 +2052,9 @@ ebmb_authenticate_sync (EBackend *backend,
 
        e_source_set_connection_status (e_backend_get_source (backend), 
E_SOURCE_CONNECTION_STATUS_CONNECTING);
 
+       /* Always disconnect first, then provide new credentials. */
+       e_book_meta_backend_disconnect_sync (meta_backend, cancellable, NULL);
+
        success = e_book_meta_backend_connect_sync (meta_backend, credentials, &auth_result,
                out_certificate_pem, out_certificate_errors, cancellable, error);
 
@@ -2060,6 +2085,11 @@ ebmb_authenticate_sync (EBackend *backend,
 
        g_mutex_unlock (&meta_backend->priv->property_lock);
 
+       g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+       meta_backend->priv->wait_credentials_stamp++;
+       g_cond_broadcast (&meta_backend->priv->wait_credentials_cond);
+       g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+
        if (refresh_after_authenticate)
                e_book_meta_backend_schedule_refresh (meta_backend);
 
@@ -2164,6 +2194,87 @@ ebmb_cancel_view_cb (gpointer key,
 }
 
 static void
+ebmb_wait_for_credentials_cancelled_cb (GCancellable *cancellable,
+                                       gpointer user_data)
+{
+       EBookMetaBackend *meta_backend = user_data;
+
+       g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend));
+
+       g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+       g_cond_broadcast (&meta_backend->priv->wait_credentials_cond);
+       g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+}
+
+static gboolean
+ebmb_maybe_wait_for_credentials (EBookMetaBackend *meta_backend,
+                                guint wait_credentials_stamp,
+                                const GError *op_error,
+                                GCancellable *cancellable)
+{
+       EBackend *backend;
+       ESourceCredentialsReason reason = E_SOURCE_CREDENTIALS_REASON_UNKNOWN;
+       gchar *certificate_pem = NULL;
+       GTlsCertificateFlags certificate_errors = 0;
+       gboolean got_credentials = FALSE;
+       GError *local_error = NULL;
+
+       g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
+
+       if (!op_error || g_cancellable_is_cancelled (cancellable))
+               return FALSE;
+
+       if (g_error_matches (op_error, E_DATA_BOOK_ERROR, E_DATA_BOOK_STATUS_TLS_NOT_AVAILABLE) &&
+           e_book_meta_backend_get_ssl_error_details (meta_backend, &certificate_pem, &certificate_errors)) {
+               reason = E_SOURCE_CREDENTIALS_REASON_SSL_FAILED;
+       } else if (g_error_matches (op_error, E_DATA_BOOK_ERROR, E_DATA_BOOK_STATUS_AUTHENTICATION_REQUIRED)) 
{
+               reason = E_SOURCE_CREDENTIALS_REASON_REQUIRED;
+       } else if (g_error_matches (op_error, E_DATA_BOOK_ERROR, E_DATA_BOOK_STATUS_AUTHENTICATION_FAILED)) {
+               reason = E_SOURCE_CREDENTIALS_REASON_REJECTED;
+       }
+
+       if (reason == E_SOURCE_CREDENTIALS_REASON_UNKNOWN)
+               return FALSE;
+
+       backend = E_BACKEND (meta_backend);
+
+       g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+
+       if (wait_credentials_stamp != meta_backend->priv->wait_credentials_stamp ||
+           e_backend_credentials_required_sync (backend, reason, certificate_pem, certificate_errors,
+               op_error, cancellable, &local_error)) {
+               gint64 wait_end_time;
+               gulong handler_id;
+
+               wait_end_time = g_get_monotonic_time () + MAX_WAIT_FOR_CREDENTIALS_SECS * G_TIME_SPAN_SECOND;
+
+               handler_id = cancellable ? g_signal_connect (cancellable, "cancelled",
+                       G_CALLBACK (ebmb_wait_for_credentials_cancelled_cb), meta_backend) : 0;
+
+               while (wait_credentials_stamp == meta_backend->priv->wait_credentials_stamp &&
+                      !g_cancellable_is_cancelled (cancellable)) {
+                       if (!g_cond_wait_until (&meta_backend->priv->wait_credentials_cond, 
&meta_backend->priv->wait_credentials_lock, wait_end_time))
+                               break;
+               }
+
+               if (handler_id)
+                       g_signal_handler_disconnect (cancellable, handler_id);
+
+               if (wait_credentials_stamp != meta_backend->priv->wait_credentials_stamp)
+                       got_credentials = e_source_get_connection_status (e_backend_get_source (backend)) == 
E_SOURCE_CONNECTION_STATUS_CONNECTED;
+       } else {
+               g_warning ("%s: Failed to call credentials required: %s", G_STRFUNC, local_error ? 
local_error->message : "Unknown error");
+       }
+
+       g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+
+       g_clear_error (&local_error);
+       g_free (certificate_pem);
+
+       return got_credentials;
+}
+
+static void
 e_book_meta_backend_set_property (GObject *object,
                                  guint property_id,
                                  const GValue *value,
@@ -2311,6 +2422,8 @@ e_book_meta_backend_finalize (GObject *object)
 
        g_mutex_clear (&meta_backend->priv->connect_lock);
        g_mutex_clear (&meta_backend->priv->property_lock);
+       g_mutex_clear (&meta_backend->priv->wait_credentials_lock);
+       g_cond_clear (&meta_backend->priv->wait_credentials_cond);
        g_hash_table_destroy (meta_backend->priv->view_cancellables);
 
        /* Chain up to parent's method. */
@@ -2332,6 +2445,7 @@ e_book_meta_backend_class_init (EBookMetaBackendClass *klass)
        klass->search_sync = ebmb_search_sync;
        klass->search_uids_sync = ebmb_search_uids_sync;
        klass->requires_reconnect = ebmb_requires_reconnect;
+       klass->get_ssl_error_details = ebmb_get_ssl_error_details;
 
        book_backend_class = E_BOOK_BACKEND_CLASS (klass);
        book_backend_class->get_backend_property = ebmb_get_backend_property;
@@ -2413,6 +2527,8 @@ e_book_meta_backend_init (EBookMetaBackend *meta_backend)
 
        g_mutex_init (&meta_backend->priv->connect_lock);
        g_mutex_init (&meta_backend->priv->property_lock);
+       g_mutex_init (&meta_backend->priv->wait_credentials_lock);
+       g_cond_init (&meta_backend->priv->wait_credentials_cond);
 
        meta_backend->priv->view_cancellables = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, 
g_object_unref);
        meta_backend->priv->current_online_state = FALSE;
@@ -3634,6 +3750,9 @@ e_book_meta_backend_get_changes_sync (EBookMetaBackend *meta_backend,
                                      GError **error)
 {
        EBookMetaBackendClass *klass;
+       gint repeat_count = 0;
+       gboolean success = FALSE;
+       GError *local_error = NULL;
 
        g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
        g_return_val_if_fail (out_new_sync_tag != NULL, FALSE);
@@ -3647,16 +3766,35 @@ e_book_meta_backend_get_changes_sync (EBookMetaBackend *meta_backend,
        g_return_val_if_fail (klass != NULL, FALSE);
        g_return_val_if_fail (klass->get_changes_sync != NULL, FALSE);
 
-       return klass->get_changes_sync (meta_backend,
-               last_sync_tag,
-               is_repeat,
-               out_new_sync_tag,
-               out_repeat,
-               out_created_objects,
-               out_modified_objects,
-               out_removed_objects,
-               cancellable,
-               error);
+       while (!success && repeat_count <= MAX_REPEAT_COUNT) {
+               guint wait_credentials_stamp;
+
+               g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+               wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp;
+               g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+
+               g_clear_error (&local_error);
+               repeat_count++;
+
+               success = klass->get_changes_sync (meta_backend,
+                       last_sync_tag,
+                       is_repeat,
+                       out_new_sync_tag,
+                       out_repeat,
+                       out_created_objects,
+                       out_modified_objects,
+                       out_removed_objects,
+                       cancellable,
+                       &local_error);
+
+               if (!success && repeat_count <= MAX_REPEAT_COUNT && !ebmb_maybe_wait_for_credentials 
(meta_backend, wait_credentials_stamp, local_error, cancellable))
+                       break;
+       }
+
+       if (local_error)
+               g_propagate_error (error, local_error);
+
+       return success;
 }
 
 /**
@@ -3693,6 +3831,9 @@ e_book_meta_backend_list_existing_sync (EBookMetaBackend *meta_backend,
                                        GError **error)
 {
        EBookMetaBackendClass *klass;
+       gint repeat_count = 0;
+       gboolean success = FALSE;
+       GError *local_error = NULL;
 
        g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
        g_return_val_if_fail (out_existing_objects != NULL, FALSE);
@@ -3701,7 +3842,27 @@ e_book_meta_backend_list_existing_sync (EBookMetaBackend *meta_backend,
        g_return_val_if_fail (klass != NULL, FALSE);
        g_return_val_if_fail (klass->list_existing_sync != NULL, FALSE);
 
-       return klass->list_existing_sync (meta_backend, out_new_sync_tag, out_existing_objects, cancellable, 
error);
+
+       while (!success && repeat_count <= MAX_REPEAT_COUNT) {
+               guint wait_credentials_stamp;
+
+               g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+               wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp;
+               g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+
+               g_clear_error (&local_error);
+               repeat_count++;
+
+               success = klass->list_existing_sync (meta_backend, out_new_sync_tag, out_existing_objects, 
cancellable, &local_error);
+
+               if (!success && repeat_count <= MAX_REPEAT_COUNT && !ebmb_maybe_wait_for_credentials 
(meta_backend, wait_credentials_stamp, local_error, cancellable))
+                       break;
+       }
+
+       if (local_error)
+               g_propagate_error (error, local_error);
+
+       return success;
 }
 
 /**
@@ -3738,6 +3899,9 @@ e_book_meta_backend_load_contact_sync (EBookMetaBackend *meta_backend,
                                       GError **error)
 {
        EBookMetaBackendClass *klass;
+       gint repeat_count = 0;
+       gboolean success = FALSE;
+       GError *local_error = NULL;
 
        g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
        g_return_val_if_fail (uid != NULL, FALSE);
@@ -3748,7 +3912,27 @@ e_book_meta_backend_load_contact_sync (EBookMetaBackend *meta_backend,
        g_return_val_if_fail (klass != NULL, FALSE);
        g_return_val_if_fail (klass->load_contact_sync != NULL, FALSE);
 
-       return klass->load_contact_sync (meta_backend, uid, extra, out_contact, out_extra, cancellable, 
error);
+
+       while (!success && repeat_count <= MAX_REPEAT_COUNT) {
+               guint wait_credentials_stamp;
+
+               g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+               wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp;
+               g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+
+               g_clear_error (&local_error);
+               repeat_count++;
+
+               success = klass->load_contact_sync (meta_backend, uid, extra, out_contact, out_extra, 
cancellable, &local_error);
+
+               if (!success && repeat_count <= MAX_REPEAT_COUNT && !ebmb_maybe_wait_for_credentials 
(meta_backend, wait_credentials_stamp, local_error, cancellable))
+                       break;
+       }
+
+       if (local_error)
+               g_propagate_error (error, local_error);
+
+       return success;
 }
 
 /**
@@ -3803,6 +3987,9 @@ e_book_meta_backend_save_contact_sync (EBookMetaBackend *meta_backend,
                                       GError **error)
 {
        EBookMetaBackendClass *klass;
+       gint repeat_count = 0;
+       gboolean success = FALSE;
+       GError *local_error = NULL;
 
        g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
        g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
@@ -3817,15 +4004,35 @@ e_book_meta_backend_save_contact_sync (EBookMetaBackend *meta_backend,
                return FALSE;
        }
 
-       return klass->save_contact_sync (meta_backend,
-               overwrite_existing,
-               conflict_resolution,
-               contact,
-               extra,
-               out_new_uid,
-               out_new_extra,
-               cancellable,
-               error);
+
+       while (!success && repeat_count <= MAX_REPEAT_COUNT) {
+               guint wait_credentials_stamp;
+
+               g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+               wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp;
+               g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+
+               g_clear_error (&local_error);
+               repeat_count++;
+
+               success = klass->save_contact_sync (meta_backend,
+                       overwrite_existing,
+                       conflict_resolution,
+                       contact,
+                       extra,
+                       out_new_uid,
+                       out_new_extra,
+                       cancellable,
+                       &local_error);
+
+               if (!success && repeat_count <= MAX_REPEAT_COUNT && !ebmb_maybe_wait_for_credentials 
(meta_backend, wait_credentials_stamp, local_error, cancellable))
+                       break;
+       }
+
+       if (local_error)
+               g_propagate_error (error, local_error);
+
+       return success;
 }
 
 /**
@@ -3858,6 +4065,9 @@ e_book_meta_backend_remove_contact_sync (EBookMetaBackend *meta_backend,
                                         GError **error)
 {
        EBookMetaBackendClass *klass;
+       gint repeat_count = 0;
+       gboolean success = FALSE;
+       GError *local_error = NULL;
 
        g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
        g_return_val_if_fail (uid != NULL, FALSE);
@@ -3870,7 +4080,27 @@ e_book_meta_backend_remove_contact_sync (EBookMetaBackend *meta_backend,
                return FALSE;
        }
 
-       return klass->remove_contact_sync (meta_backend, conflict_resolution, uid, extra, object, 
cancellable, error);
+
+       while (!success && repeat_count <= MAX_REPEAT_COUNT) {
+               guint wait_credentials_stamp;
+
+               g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+               wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp;
+               g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+
+               g_clear_error (&local_error);
+               repeat_count++;
+
+               success = klass->remove_contact_sync (meta_backend, conflict_resolution, uid, extra, object, 
cancellable, &local_error);
+
+               if (!success && repeat_count <= MAX_REPEAT_COUNT && !ebmb_maybe_wait_for_credentials 
(meta_backend, wait_credentials_stamp, local_error, cancellable))
+                       break;
+       }
+
+       if (local_error)
+               g_propagate_error (error, local_error);
+
+       return success;
 }
 
 /**
@@ -3991,3 +4221,34 @@ e_book_meta_backend_requires_reconnect (EBookMetaBackend *meta_backend)
 
        return klass->requires_reconnect (meta_backend);
 }
+
+/**
+ * e_book_meta_backend_get_ssl_error_details:
+ * @meta_backend: an #EBookMetaBackend
+ * @out_certificate_pem: (out): SSL certificate encoded in PEM format
+ * @out_certificate_errors: (out): bit-or of #GTlsCertificateFlags claiming the certificate errors
+ *
+ * It is optional to implement this virtual method by the descendants.
+ * It is used to receive SSL error details when any online operation
+ * returns E_DATA_BOOK_ERROR, E_DATA_BOOK_STATUS_TLS_NOT_AVAILABLE error.
+ *
+ * Returns: %TRUE, when the SSL error details had been available and
+ *    the out parameters populated, %FALSE otherwise.
+ *
+ * Since: 3.28
+ **/
+gboolean
+e_book_meta_backend_get_ssl_error_details (EBookMetaBackend *meta_backend,
+                                          gchar **out_certificate_pem,
+                                          GTlsCertificateFlags *out_certificate_errors)
+{
+       EBookMetaBackendClass *klass;
+
+       g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
+
+       klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->get_ssl_error_details != NULL, FALSE);
+
+       return klass->get_ssl_error_details (meta_backend, out_certificate_pem, out_certificate_errors);
+}
diff --git a/src/addressbook/libedata-book/e-book-meta-backend.h 
b/src/addressbook/libedata-book/e-book-meta-backend.h
index 67adcb7..6389f9f 100644
--- a/src/addressbook/libedata-book/e-book-meta-backend.h
+++ b/src/addressbook/libedata-book/e-book-meta-backend.h
@@ -167,8 +167,13 @@ struct _EBookMetaBackendClass {
        /* Signals */
        void            (* source_changed)      (EBookMetaBackend *meta_backend);
 
+       gboolean        (* get_ssl_error_details)
+                                               (EBookMetaBackend *meta_backend,
+                                                gchar **out_certificate_pem,
+                                                GTlsCertificateFlags *out_certificate_errors);
+
        /* Padding for future expansion */
-       gpointer reserved[10];
+       gpointer reserved[9];
 };
 
 GType          e_book_meta_backend_get_type    (void) G_GNUC_CONST;
@@ -296,6 +301,10 @@ gboolean   e_book_meta_backend_search_uids_sync
                                                 GError **error);
 gboolean       e_book_meta_backend_requires_reconnect
                                                (EBookMetaBackend *meta_backend);
+gboolean       e_book_meta_backend_get_ssl_error_details
+                                               (EBookMetaBackend *meta_backend,
+                                                gchar **out_certificate_pem,
+                                                GTlsCertificateFlags *out_certificate_errors);
 
 G_END_DECLS
 
diff --git a/src/calendar/backends/caldav/e-cal-backend-caldav.c 
b/src/calendar/backends/caldav/e-cal-backend-caldav.c
index 577cf8b..75981d5 100644
--- a/src/calendar/backends/caldav/e-cal-backend-caldav.c
+++ b/src/calendar/backends/caldav/e-cal-backend-caldav.c
@@ -571,6 +571,32 @@ ecb_caldav_search_changes_cb (ECalCache *cal_cache,
        return TRUE;
 }
 
+static void
+ecb_caldav_check_credentials_error (ECalBackendCalDAV *cbdav,
+                                   GError *op_error)
+{
+       g_return_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav));
+
+       if (g_error_matches (op_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED) && cbdav->priv->webdav) {
+               op_error->domain = E_DATA_CAL_ERROR;
+               op_error->code = TLSNotAvailable;
+       } else if (g_error_matches (op_error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) ||
+                  g_error_matches (op_error, SOUP_HTTP_ERROR, SOUP_STATUS_FORBIDDEN)) {
+               op_error->domain = E_DATA_CAL_ERROR;
+               op_error->code = AuthenticationRequired;
+
+               if (cbdav->priv->webdav) {
+                       ENamedParameters *credentials;
+
+                       credentials = e_soup_session_dup_credentials (E_SOUP_SESSION (cbdav->priv->webdav));
+                       if (credentials && e_named_parameters_count (credentials) > 0)
+                               op_error->code = AuthenticationFailed;
+
+                       e_named_parameters_free (credentials);
+               }
+       }
+}
+
 static gboolean
 ecb_caldav_get_changes_sync (ECalMetaBackend *meta_backend,
                             const gchar *last_sync_tag,
@@ -728,42 +754,10 @@ ecb_caldav_get_changes_sync (ECalMetaBackend *meta_backend,
                } while (success && link);
        }
 
-       if (local_error && !g_cancellable_is_cancelled (cancellable)) {
-               ESourceCredentialsReason reason = E_SOURCE_CREDENTIALS_REASON_UNKNOWN;
-
-               if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED)) {
-                       reason = E_SOURCE_CREDENTIALS_REASON_SSL_FAILED;
-               } else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) ||
-                          g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_FORBIDDEN)) {
-                       reason = E_SOURCE_CREDENTIALS_REASON_REQUIRED;
-               }
-
-               if (reason != E_SOURCE_CREDENTIALS_REASON_UNKNOWN) {
-                       GTlsCertificateFlags certificate_errors = 0;
-                       gchar *certificate_pem = NULL;
-                       GError *local_error2 = NULL;
-
-                       if (!e_soup_session_get_ssl_error_details (E_SOUP_SESSION (cbdav->priv->webdav), 
&certificate_pem, &certificate_errors)) {
-                               certificate_pem = NULL;
-                               certificate_errors = 0;
-                       }
-
-                       if (!e_backend_credentials_required_sync (E_BACKEND (cbdav), reason, certificate_pem, 
certificate_errors,
-                               local_error, cancellable, &local_error2)) {
-                               g_warning ("%s: Failed to call credentials required: %s", G_STRFUNC, 
local_error2 ? local_error2->message : "Unknown error");
-                       } else {
-                               /* Ignore the error when the caller had been notified through the 
credentials-required */
-                               g_clear_error (&local_error);
-                               success = TRUE;
-                       }
-
-                       g_clear_error (&local_error2);
-                       g_free (certificate_pem);
-               }
-       }
-
-       if (local_error)
+       if (local_error) {
+               ecb_caldav_check_credentials_error (cbdav, local_error);
                g_propagate_error (error, local_error);
+       }
 
        return success;
 }
@@ -828,6 +822,7 @@ ecb_caldav_list_existing_sync (ECalMetaBackend *meta_backend,
        ECalBackendCalDAV *cbdav;
        icalcomponent_kind kind;
        EXmlDocument *xml;
+       GError *local_error = NULL;
        gboolean success;
 
        g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (meta_backend), FALSE);
@@ -880,13 +875,18 @@ ecb_caldav_list_existing_sync (ECalMetaBackend *meta_backend,
        e_xml_document_end_element (xml); /* filter */
 
        success = e_webdav_session_report_sync (cbdav->priv->webdav, NULL, E_WEBDAV_DEPTH_THIS, xml,
-               ecb_caldav_extract_existing_cb, out_existing_objects, NULL, NULL, cancellable, error);
+               ecb_caldav_extract_existing_cb, out_existing_objects, NULL, NULL, cancellable, &local_error);
 
        g_object_unref (xml);
 
        if (success)
                *out_existing_objects = g_slist_reverse (*out_existing_objects);
 
+       if (local_error) {
+               ecb_caldav_check_credentials_error (cbdav, local_error);
+               g_propagate_error (error, local_error);
+       }
+
        return success;
 }
 
@@ -1053,8 +1053,10 @@ ecb_caldav_load_component_sync (ECalMetaBackend *meta_backend,
        g_free (etag);
        g_free (bytes);
 
-       if (local_error)
+       if (local_error) {
+               ecb_caldav_check_credentials_error (cbdav, local_error);
                g_propagate_error (error, local_error);
+       }
 
        return success;
 }
@@ -1075,6 +1077,7 @@ ecb_caldav_save_component_sync (ECalMetaBackend *meta_backend,
        gchar *href = NULL, *etag = NULL, *uid = NULL;
        gchar *ical_string = NULL;
        gboolean success;
+       GError *local_error = NULL;
 
        g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (meta_backend), FALSE);
        g_return_val_if_fail (instances != NULL, FALSE);
@@ -1127,7 +1130,7 @@ ecb_caldav_save_component_sync (ECalMetaBackend *meta_backend,
 
                success = e_webdav_session_put_data_sync (cbdav->priv->webdav, (extra && *extra) ? extra : 
href,
                        force_write ? "" : overwrite_existing ? etag : NULL, E_WEBDAV_CONTENT_TYPE_CALENDAR,
-                       ical_string, -1, out_new_extra, NULL, cancellable, error);
+                       ical_string, -1, out_new_extra, NULL, cancellable, &local_error);
 
                /* To read the component back, because server can change it */
                if (success)
@@ -1142,6 +1145,11 @@ ecb_caldav_save_component_sync (ECalMetaBackend *meta_backend,
        g_free (etag);
        g_free (uid);
 
+       if (local_error) {
+               ecb_caldav_check_credentials_error (cbdav, local_error);
+               g_propagate_error (error, local_error);
+       }
+
        return success;
 }
 
@@ -1210,13 +1218,32 @@ ecb_caldav_remove_component_sync (ECalMetaBackend *meta_backend,
        icalcomponent_free (icalcomp);
        g_free (etag);
 
-       if (local_error)
+       if (local_error) {
+               ecb_caldav_check_credentials_error (cbdav, local_error);
                g_propagate_error (error, local_error);
+       }
 
        return success;
 }
 
 static gboolean
+ecb_caldav_get_ssl_error_details (ECalMetaBackend *meta_backend,
+                                 gchar **out_certificate_pem,
+                                 GTlsCertificateFlags *out_certificate_errors)
+{
+       ECalBackendCalDAV *cbdav;
+
+       g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (meta_backend), FALSE);
+
+       cbdav = E_CAL_BACKEND_CALDAV (meta_backend);
+
+       if (!cbdav->priv->webdav)
+               return FALSE;
+
+       return e_soup_session_get_ssl_error_details (E_SOUP_SESSION (cbdav->priv->webdav), 
out_certificate_pem, out_certificate_errors);
+}
+
+static gboolean
 ecb_caldav_propfind_get_owner_cb (EWebDAVSession *webdav,
                                  xmlXPathContextPtr xpath_ctx,
                                  const gchar *xpath_prop_prefix,
@@ -1881,6 +1908,7 @@ e_cal_backend_caldav_class_init (ECalBackendCalDAVClass *klass)
        cal_meta_backend_class->load_component_sync = ecb_caldav_load_component_sync;
        cal_meta_backend_class->save_component_sync = ecb_caldav_save_component_sync;
        cal_meta_backend_class->remove_component_sync = ecb_caldav_remove_component_sync;
+       cal_meta_backend_class->get_ssl_error_details = ecb_caldav_get_ssl_error_details;
 
        cal_backend_sync_class = E_CAL_BACKEND_SYNC_CLASS (klass);
        cal_backend_sync_class->get_free_busy_sync = ecb_caldav_get_free_busy_sync;
diff --git a/src/calendar/libedata-cal/e-cal-meta-backend.c b/src/calendar/libedata-cal/e-cal-meta-backend.c
index db6e5c2..8c9fc6c 100644
--- a/src/calendar/libedata-cal/e-cal-meta-backend.c
+++ b/src/calendar/libedata-cal/e-cal-meta-backend.c
@@ -49,9 +49,18 @@
 
 #define LOCAL_PREFIX "file://"
 
+/* How many times can repeat an operation when credentials fail. */
+#define MAX_REPEAT_COUNT 3
+
+/* How long to wait for credentials, in seconds, during the operation repeat cycle */
+#define MAX_WAIT_FOR_CREDENTIALS_SECS 60
+
 struct _ECalMetaBackendPrivate {
        GMutex connect_lock;
        GMutex property_lock;
+       GMutex wait_credentials_lock;
+       GCond wait_credentials_cond;
+       guint wait_credentials_stamp;
        GError *create_cache_error;
        ECalCache *cache;
        ENamedParameters *last_credentials;
@@ -463,6 +472,14 @@ ecmb_requires_reconnect (ECalMetaBackend *meta_backend)
        return requires;
 }
 
+static gboolean
+ecmb_get_ssl_error_details (ECalMetaBackend *meta_backend,
+                           gchar **out_certificate_pem,
+                           GTlsCertificateFlags *out_certificate_errors)
+{
+       return FALSE;
+}
+
 static void
 ecmb_start_view_thread_func (ECalBackend *cal_backend,
                             gpointer user_data,
@@ -2792,6 +2809,11 @@ ecmb_authenticate_sync (EBackend *backend,
                g_set_error_literal (error, E_CLIENT_ERROR, E_CLIENT_ERROR_REPOSITORY_OFFLINE,
                        e_client_error_to_string (E_CLIENT_ERROR_REPOSITORY_OFFLINE));
 
+               g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+               meta_backend->priv->wait_credentials_stamp++;
+               g_cond_broadcast (&meta_backend->priv->wait_credentials_cond);
+               g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+
                return E_SOURCE_AUTHENTICATION_ERROR;
        }
 
@@ -2799,6 +2821,9 @@ ecmb_authenticate_sync (EBackend *backend,
 
        e_source_set_connection_status (e_backend_get_source (backend), 
E_SOURCE_CONNECTION_STATUS_CONNECTING);
 
+       /* Always disconnect first, then provide new credentials. */
+       e_cal_meta_backend_disconnect_sync (meta_backend, cancellable, NULL);
+
        success = e_cal_meta_backend_connect_sync (meta_backend, credentials, &auth_result,
                out_certificate_pem, out_certificate_errors, cancellable, error);
 
@@ -2829,6 +2854,11 @@ ecmb_authenticate_sync (EBackend *backend,
 
        g_mutex_unlock (&meta_backend->priv->property_lock);
 
+       g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+       meta_backend->priv->wait_credentials_stamp++;
+       g_cond_broadcast (&meta_backend->priv->wait_credentials_cond);
+       g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+
        if (refresh_after_authenticate)
                e_cal_meta_backend_schedule_refresh (meta_backend);
 
@@ -2933,6 +2963,87 @@ ecmb_cancel_view_cb (gpointer key,
 }
 
 static void
+ecmb_wait_for_credentials_cancelled_cb (GCancellable *cancellable,
+                                       gpointer user_data)
+{
+       ECalMetaBackend *meta_backend = user_data;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (meta_backend));
+
+       g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+       g_cond_broadcast (&meta_backend->priv->wait_credentials_cond);
+       g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+}
+
+static gboolean
+ecmb_maybe_wait_for_credentials (ECalMetaBackend *meta_backend,
+                                guint wait_credentials_stamp,
+                                const GError *op_error,
+                                GCancellable *cancellable)
+{
+       EBackend *backend;
+       ESourceCredentialsReason reason = E_SOURCE_CREDENTIALS_REASON_UNKNOWN;
+       gchar *certificate_pem = NULL;
+       GTlsCertificateFlags certificate_errors = 0;
+       gboolean got_credentials = FALSE;
+       GError *local_error = NULL;
+
+       g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), FALSE);
+
+       if (!op_error || g_cancellable_is_cancelled (cancellable))
+               return FALSE;
+
+       if (g_error_matches (op_error, E_DATA_CAL_ERROR, TLSNotAvailable) &&
+           e_cal_meta_backend_get_ssl_error_details (meta_backend, &certificate_pem, &certificate_errors)) {
+               reason = E_SOURCE_CREDENTIALS_REASON_SSL_FAILED;
+       } else if (g_error_matches (op_error, E_DATA_CAL_ERROR, AuthenticationRequired)) {
+               reason = E_SOURCE_CREDENTIALS_REASON_REQUIRED;
+       } else if (g_error_matches (op_error, E_DATA_CAL_ERROR, AuthenticationFailed)) {
+               reason = E_SOURCE_CREDENTIALS_REASON_REJECTED;
+       }
+
+       if (reason == E_SOURCE_CREDENTIALS_REASON_UNKNOWN)
+               return FALSE;
+
+       backend = E_BACKEND (meta_backend);
+
+       g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+
+       if (wait_credentials_stamp != meta_backend->priv->wait_credentials_stamp ||
+           e_backend_credentials_required_sync (backend, reason, certificate_pem, certificate_errors,
+               op_error, cancellable, &local_error)) {
+               gint64 wait_end_time;
+               gulong handler_id;
+
+               wait_end_time = g_get_monotonic_time () + MAX_WAIT_FOR_CREDENTIALS_SECS * G_TIME_SPAN_SECOND;
+
+               handler_id = cancellable ? g_signal_connect (cancellable, "cancelled",
+                       G_CALLBACK (ecmb_wait_for_credentials_cancelled_cb), meta_backend) : 0;
+
+               while (wait_credentials_stamp == meta_backend->priv->wait_credentials_stamp &&
+                      !g_cancellable_is_cancelled (cancellable)) {
+                       if (!g_cond_wait_until (&meta_backend->priv->wait_credentials_cond, 
&meta_backend->priv->wait_credentials_lock, wait_end_time))
+                               break;
+               }
+
+               if (handler_id)
+                       g_signal_handler_disconnect (cancellable, handler_id);
+
+               if (wait_credentials_stamp != meta_backend->priv->wait_credentials_stamp)
+                       got_credentials = e_source_get_connection_status (e_backend_get_source (backend)) == 
E_SOURCE_CONNECTION_STATUS_CONNECTED;
+       } else {
+               g_warning ("%s: Failed to call credentials required: %s", G_STRFUNC, local_error ? 
local_error->message : "Unknown error");
+       }
+
+       g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+
+       g_clear_error (&local_error);
+       g_free (certificate_pem);
+
+       return got_credentials;
+}
+
+static void
 e_cal_meta_backend_set_property (GObject *object,
                                 guint property_id,
                                 const GValue *value,
@@ -3073,6 +3184,8 @@ e_cal_meta_backend_finalize (GObject *object)
 
        g_mutex_clear (&meta_backend->priv->connect_lock);
        g_mutex_clear (&meta_backend->priv->property_lock);
+       g_mutex_clear (&meta_backend->priv->wait_credentials_lock);
+       g_cond_clear (&meta_backend->priv->wait_credentials_cond);
        g_hash_table_destroy (meta_backend->priv->view_cancellables);
 
        /* Chain up to parent's method. */
@@ -3093,6 +3206,7 @@ e_cal_meta_backend_class_init (ECalMetaBackendClass *klass)
        klass->search_sync = ecmb_search_sync;
        klass->search_components_sync = ecmb_search_components_sync;
        klass->requires_reconnect = ecmb_requires_reconnect;
+       klass->get_ssl_error_details = ecmb_get_ssl_error_details;
 
        cal_backend_sync_class = E_CAL_BACKEND_SYNC_CLASS (klass);
        cal_backend_sync_class->open_sync = ecmb_open_sync;
@@ -3176,6 +3290,8 @@ e_cal_meta_backend_init (ECalMetaBackend *meta_backend)
 
        g_mutex_init (&meta_backend->priv->connect_lock);
        g_mutex_init (&meta_backend->priv->property_lock);
+       g_mutex_init (&meta_backend->priv->wait_credentials_lock);
+       g_cond_init (&meta_backend->priv->wait_credentials_cond);
 
        meta_backend->priv->view_cancellables = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, 
g_object_unref);
        meta_backend->priv->current_online_state = FALSE;
@@ -4515,6 +4631,9 @@ e_cal_meta_backend_get_changes_sync (ECalMetaBackend *meta_backend,
                                     GError **error)
 {
        ECalMetaBackendClass *klass;
+       gint repeat_count = 0;
+       gboolean success = FALSE;
+       GError *local_error = NULL;
 
        g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), FALSE);
        g_return_val_if_fail (out_new_sync_tag != NULL, FALSE);
@@ -4528,16 +4647,36 @@ e_cal_meta_backend_get_changes_sync (ECalMetaBackend *meta_backend,
        g_return_val_if_fail (klass != NULL, FALSE);
        g_return_val_if_fail (klass->get_changes_sync != NULL, FALSE);
 
-       return klass->get_changes_sync (meta_backend,
-               last_sync_tag,
-               is_repeat,
-               out_new_sync_tag,
-               out_repeat,
-               out_created_objects,
-               out_modified_objects,
-               out_removed_objects,
-               cancellable,
-               error);
+
+       while (!success && repeat_count <= MAX_REPEAT_COUNT) {
+               guint wait_credentials_stamp;
+
+               g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+               wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp;
+               g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+
+               g_clear_error (&local_error);
+               repeat_count++;
+
+               success = klass->get_changes_sync (meta_backend,
+                       last_sync_tag,
+                       is_repeat,
+                       out_new_sync_tag,
+                       out_repeat,
+                       out_created_objects,
+                       out_modified_objects,
+                       out_removed_objects,
+                       cancellable,
+                       &local_error);
+
+               if (!success && repeat_count <= MAX_REPEAT_COUNT && !ecmb_maybe_wait_for_credentials 
(meta_backend, wait_credentials_stamp, local_error, cancellable))
+                       break;
+       }
+
+       if (local_error)
+               g_propagate_error (error, local_error);
+
+       return success;
 }
 
 /**
@@ -4574,6 +4713,9 @@ e_cal_meta_backend_list_existing_sync (ECalMetaBackend *meta_backend,
                                       GError **error)
 {
        ECalMetaBackendClass *klass;
+       gint repeat_count = 0;
+       gboolean success = FALSE;
+       GError *local_error = NULL;
 
        g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), FALSE);
        g_return_val_if_fail (out_existing_objects != NULL, FALSE);
@@ -4582,7 +4724,27 @@ e_cal_meta_backend_list_existing_sync (ECalMetaBackend *meta_backend,
        g_return_val_if_fail (klass != NULL, FALSE);
        g_return_val_if_fail (klass->list_existing_sync != NULL, FALSE);
 
-       return klass->list_existing_sync (meta_backend, out_new_sync_tag, out_existing_objects, cancellable, 
error);
+
+       while (!success && repeat_count <= MAX_REPEAT_COUNT) {
+               guint wait_credentials_stamp;
+
+               g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+               wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp;
+               g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+
+               g_clear_error (&local_error);
+               repeat_count++;
+
+               success = klass->list_existing_sync (meta_backend, out_new_sync_tag, out_existing_objects, 
cancellable, &local_error);
+
+               if (!success && repeat_count <= MAX_REPEAT_COUNT && !ecmb_maybe_wait_for_credentials 
(meta_backend, wait_credentials_stamp, local_error, cancellable))
+                       break;
+       }
+
+       if (local_error)
+               g_propagate_error (error, local_error);
+
+       return success;
 }
 
 /**
@@ -4623,6 +4785,9 @@ e_cal_meta_backend_load_component_sync (ECalMetaBackend *meta_backend,
                                        GError **error)
 {
        ECalMetaBackendClass *klass;
+       gint repeat_count = 0;
+       gboolean success = FALSE;
+       GError *local_error = NULL;
 
        g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), FALSE);
        g_return_val_if_fail (uid != NULL, FALSE);
@@ -4633,7 +4798,27 @@ e_cal_meta_backend_load_component_sync (ECalMetaBackend *meta_backend,
        g_return_val_if_fail (klass != NULL, FALSE);
        g_return_val_if_fail (klass->load_component_sync != NULL, FALSE);
 
-       return klass->load_component_sync (meta_backend, uid, extra, out_component, out_extra, cancellable, 
error);
+
+       while (!success && repeat_count <= MAX_REPEAT_COUNT) {
+               guint wait_credentials_stamp;
+
+               g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+               wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp;
+               g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+
+               g_clear_error (&local_error);
+               repeat_count++;
+
+               success = klass->load_component_sync (meta_backend, uid, extra, out_component, out_extra, 
cancellable, &local_error);
+
+               if (!success && repeat_count <= MAX_REPEAT_COUNT && !ecmb_maybe_wait_for_credentials 
(meta_backend, wait_credentials_stamp, local_error, cancellable))
+                       break;
+       }
+
+       if (local_error)
+               g_propagate_error (error, local_error);
+
+       return success;
 }
 
 /**
@@ -4694,6 +4879,9 @@ e_cal_meta_backend_save_component_sync (ECalMetaBackend *meta_backend,
                                        GError **error)
 {
        ECalMetaBackendClass *klass;
+       gint repeat_count = 0;
+       gboolean success = FALSE;
+       GError *local_error = NULL;
 
        g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), FALSE);
        g_return_val_if_fail (instances != NULL, FALSE);
@@ -4708,15 +4896,35 @@ e_cal_meta_backend_save_component_sync (ECalMetaBackend *meta_backend,
                return FALSE;
        }
 
-       return klass->save_component_sync (meta_backend,
-               overwrite_existing,
-               conflict_resolution,
-               instances,
-               extra,
-               out_new_uid,
-               out_new_extra,
-               cancellable,
-               error);
+
+       while (!success && repeat_count <= MAX_REPEAT_COUNT) {
+               guint wait_credentials_stamp;
+
+               g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+               wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp;
+               g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+
+               g_clear_error (&local_error);
+               repeat_count++;
+
+               success = klass->save_component_sync (meta_backend,
+                       overwrite_existing,
+                       conflict_resolution,
+                       instances,
+                       extra,
+                       out_new_uid,
+                       out_new_extra,
+                       cancellable,
+                       &local_error);
+
+               if (!success && repeat_count <= MAX_REPEAT_COUNT && !ecmb_maybe_wait_for_credentials 
(meta_backend, wait_credentials_stamp, local_error, cancellable))
+                       break;
+       }
+
+       if (local_error)
+               g_propagate_error (error, local_error);
+
+       return success;
 }
 
 /**
@@ -4750,6 +4958,9 @@ e_cal_meta_backend_remove_component_sync (ECalMetaBackend *meta_backend,
                                          GError **error)
 {
        ECalMetaBackendClass *klass;
+       gint repeat_count = 0;
+       gboolean success = FALSE;
+       GError *local_error = NULL;
 
        g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), FALSE);
        g_return_val_if_fail (uid != NULL, FALSE);
@@ -4762,7 +4973,26 @@ e_cal_meta_backend_remove_component_sync (ECalMetaBackend *meta_backend,
                return FALSE;
        }
 
-       return klass->remove_component_sync (meta_backend, conflict_resolution, uid, extra, object, 
cancellable, error);
+       while (!success && repeat_count <= MAX_REPEAT_COUNT) {
+               guint wait_credentials_stamp;
+
+               g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
+               wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp;
+               g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
+
+               g_clear_error (&local_error);
+               repeat_count++;
+
+               success = klass->remove_component_sync (meta_backend, conflict_resolution, uid, extra, 
object, cancellable, &local_error);
+
+               if (!success && repeat_count <= MAX_REPEAT_COUNT && !ecmb_maybe_wait_for_credentials 
(meta_backend, wait_credentials_stamp, local_error, cancellable))
+                       break;
+       }
+
+       if (local_error)
+               g_propagate_error (error, local_error);
+
+       return success;
 }
 
 /**
@@ -4881,3 +5111,34 @@ e_cal_meta_backend_requires_reconnect (ECalMetaBackend *meta_backend)
 
        return klass->requires_reconnect (meta_backend);
 }
+
+/**
+ * e_cal_meta_backend_get_ssl_error_details:
+ * @meta_backend: an #ECalMetaBackend
+ * @out_certificate_pem: (out): SSL certificate encoded in PEM format
+ * @out_certificate_errors: (out): bit-or of #GTlsCertificateFlags claiming the certificate errors
+ *
+ * It is optional to implement this virtual method by the descendants.
+ * It is used to receive SSL error details when any online operation
+ * returns E_DATA_CAL_ERROR, TLSNotAvailable error.
+ *
+ * Returns: %TRUE, when the SSL error details had been available and
+ *    the out parameters populated, %FALSE otherwise.
+ *
+ * Since: 3.28
+ **/
+gboolean
+e_cal_meta_backend_get_ssl_error_details (ECalMetaBackend *meta_backend,
+                                         gchar **out_certificate_pem,
+                                         GTlsCertificateFlags *out_certificate_errors)
+{
+       ECalMetaBackendClass *klass;
+
+       g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), FALSE);
+
+       klass = E_CAL_META_BACKEND_GET_CLASS (meta_backend);
+       g_return_val_if_fail (klass != NULL, FALSE);
+       g_return_val_if_fail (klass->get_ssl_error_details != NULL, FALSE);
+
+       return klass->get_ssl_error_details (meta_backend, out_certificate_pem, out_certificate_errors);
+}
diff --git a/src/calendar/libedata-cal/e-cal-meta-backend.h b/src/calendar/libedata-cal/e-cal-meta-backend.h
index 29e51be..4318e15 100644
--- a/src/calendar/libedata-cal/e-cal-meta-backend.h
+++ b/src/calendar/libedata-cal/e-cal-meta-backend.h
@@ -164,8 +164,12 @@ struct _ECalMetaBackendClass {
        /* Signals */
        void            (* source_changed)      (ECalMetaBackend *meta_backend);
 
+       gboolean        (* get_ssl_error_details)
+                                               (ECalMetaBackend *meta_backend,
+                                                gchar **out_certificate_pem,
+                                                GTlsCertificateFlags *out_certificate_errors);
        /* Padding for future expansion */
-       gpointer reserved[10];
+       gpointer reserved[9];
 };
 
 GType          e_cal_meta_backend_get_type     (void) G_GNUC_CONST;
@@ -301,6 +305,10 @@ gboolean   e_cal_meta_backend_search_components_sync
                                                 GError **error);
 gboolean       e_cal_meta_backend_requires_reconnect
                                                (ECalMetaBackend *meta_backend);
+gboolean       e_cal_meta_backend_get_ssl_error_details
+                                               (ECalMetaBackend *meta_backend,
+                                                gchar **out_certificate_pem,
+                                                GTlsCertificateFlags *out_certificate_errors);
 
 G_END_DECLS
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]