[evolution-data-server/gnome-3-20] Improve handling of the Google OAuth2 tokens in Calendar



commit 9328567878de19a36f88920ae9bee36181f2250e
Author: Milan Crha <mcrha redhat com>
Date:   Wed Nov 30 15:00:01 2016 +0100

    Improve handling of the Google OAuth2 tokens in Calendar
    
    This is a merge of several changes which accumulated during the time,
    related to the Google OAuth2 tokens handling in the calendar code,
    affecting both accounts provided by GNOME Online Accounts and defined
    directly in the Evolution.

 calendar/backends/caldav/e-cal-backend-caldav.c    |  122 +++++--
 libedataserver/e-data-server-util.c                |    6 +-
 libedataserver/e-soup-auth-bearer.c                |   34 ++-
 libedataserver/e-soup-auth-bearer.h                |    1 +
 .../e-source-credentials-provider-impl-google.c    |  361 ++++++++++++++++++--
 .../e-source-credentials-provider-impl-google.h    |    6 +
 .../e-credentials-prompter-impl-google.c           |   38 ++-
 libedataserverui/e-credentials-prompter.c          |   16 +-
 8 files changed, 500 insertions(+), 84 deletions(-)
---
diff --git a/calendar/backends/caldav/e-cal-backend-caldav.c b/calendar/backends/caldav/e-cal-backend-caldav.c
index 94e2cce..8f0ccc6 100644
--- a/calendar/backends/caldav/e-cal-backend-caldav.c
+++ b/calendar/backends/caldav/e-cal-backend-caldav.c
@@ -131,7 +131,7 @@ struct _ECalBackendCalDAVPrivate {
         * message than a generic SOUP_STATUS_UNAUTHORIZED description. */
        GError *bearer_auth_error;
        GMutex bearer_auth_error_lock;
-       gboolean using_bearer_auth;
+       ESoupAuthBearer *using_bearer_auth;
 };
 
 /* Forward Declarations */
@@ -326,6 +326,40 @@ static void put_server_comp_to_cache (ECalBackendCalDAV *cbdav, icalcomponent *i
 /* ************************************************************************* */
 /* Misc. utility functions */
 
+static void
+caldav_ensure_bearer_auth_usage (ECalBackendCalDAV *cbdav,
+                                ESoupAuthBearer *bearer)
+{
+       SoupSessionFeature *feature;
+       SoupURI *soup_uri;
+       ESourceWebdav *extension;
+       ESource *source;
+
+       g_return_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav));
+
+       source = e_backend_get_source (E_BACKEND (cbdav));
+
+       /* Preload the SoupAuthManager with a valid "Bearer" token
+        * when using OAuth 2.0. This avoids an extra unauthorized
+        * HTTP round-trip, which apparently Google doesn't like. */
+
+       feature = soup_session_get_feature (cbdav->priv->session, SOUP_TYPE_AUTH_MANAGER);
+
+       if (!soup_session_feature_has_feature (feature, E_TYPE_SOUP_AUTH_BEARER)) {
+               /* Add the "Bearer" auth type to support OAuth 2.0. */
+               soup_session_feature_add_feature (feature, E_TYPE_SOUP_AUTH_BEARER);
+       }
+
+       extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+       soup_uri = e_source_webdav_dup_soup_uri (extension);
+
+       soup_auth_manager_use_auth (
+               SOUP_AUTH_MANAGER (feature),
+               soup_uri, SOUP_AUTH (bearer));
+
+       soup_uri_free (soup_uri);
+}
+
 static gboolean
 caldav_setup_bearer_auth (ECalBackendCalDAV *cbdav,
                          ESoupAuthBearer *bearer,
@@ -345,8 +379,10 @@ caldav_setup_bearer_auth (ECalBackendCalDAV *cbdav,
        success = e_util_get_source_oauth2_access_token_sync (source, cbdav->priv->credentials,
                &access_token, &expires_in_seconds, cancellable, error);
 
-       if (success)
+       if (success) {
                e_soup_auth_bearer_set_access_token (bearer, access_token, expires_in_seconds);
+               caldav_ensure_bearer_auth_usage (cbdav, bearer);
+       }
 
        g_free (access_token);
 
@@ -358,11 +394,7 @@ caldav_maybe_prepare_bearer_auth (ECalBackendCalDAV *cbdav,
                                  GCancellable *cancellable,
                                  GError **error)
 {
-       ESourceWebdav *extension;
        ESource *source;
-       SoupSessionFeature *feature;
-       SoupAuth *soup_auth;
-       SoupURI *soup_uri;
        gchar *auth_method = NULL;
        gboolean success;
 
@@ -386,32 +418,28 @@ caldav_maybe_prepare_bearer_auth (ECalBackendCalDAV *cbdav,
 
        g_free (auth_method);
 
-       /* Preload the SoupAuthManager with a valid "Bearer" token
-        * when using OAuth 2.0. This avoids an extra unauthorized
-        * HTTP round-trip, which apparently Google doesn't like. */
-
-       feature = soup_session_get_feature (cbdav->priv->session, SOUP_TYPE_AUTH_MANAGER);
+       if (cbdav->priv->using_bearer_auth) {
+               success = caldav_setup_bearer_auth (cbdav, cbdav->priv->using_bearer_auth, cancellable, 
error);
+       } else {
+               ESourceWebdav *extension;
+               SoupAuth *soup_auth;
+               SoupURI *soup_uri;
 
-       /* Add the "Bearer" auth type to support OAuth 2.0. */
-       soup_session_feature_add_feature (feature, E_TYPE_SOUP_AUTH_BEARER);
+               extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+               soup_uri = e_source_webdav_dup_soup_uri (extension);
 
-       extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
-       soup_uri = e_source_webdav_dup_soup_uri (extension);
+               soup_auth = g_object_new (
+                       E_TYPE_SOUP_AUTH_BEARER,
+                       SOUP_AUTH_HOST, soup_uri->host, NULL);
 
-       soup_auth = g_object_new (
-               E_TYPE_SOUP_AUTH_BEARER,
-               SOUP_AUTH_HOST, soup_uri->host, NULL);
+               success = caldav_setup_bearer_auth (cbdav, E_SOUP_AUTH_BEARER (soup_auth), cancellable, 
error);
+               if (success)
+                       cbdav->priv->using_bearer_auth = g_object_ref (soup_auth);
 
-       success = caldav_setup_bearer_auth (cbdav, E_SOUP_AUTH_BEARER (soup_auth), cancellable, error);
-       if (success) {
-               soup_auth_manager_use_auth (
-                       SOUP_AUTH_MANAGER (feature),
-                       soup_uri, soup_auth);
+               g_object_unref (soup_auth);
+               soup_uri_free (soup_uri);
        }
 
-       g_object_unref (soup_auth);
-       soup_uri_free (soup_uri);
-
        return success;
 }
 
@@ -690,7 +718,24 @@ status_code_to_result (SoupMessage *message,
                break;
 
        case SOUP_STATUS_FORBIDDEN:
-               g_propagate_error (perror, EDC_ERROR (AuthenticationRequired));
+               if (cbdav->priv->using_bearer_auth && message->response_body &&
+                   message->response_body->data && message->response_body->length) {
+                       gchar *body = g_strndup (message->response_body->data, 
message->response_body->length);
+
+                       /* Do not localize this string, it is returned by the server. */
+                       if (body && (e_util_strstrcase (body, "Daily Limit") ||
+                           e_util_strstrcase (body, "https://console.developers.google.com/";))) {
+                               /* Special-case this condition and provide this error up to the UI. */
+                               g_propagate_error (perror,
+                                       e_data_cal_create_error_fmt (OtherError, _("Failed to login to the 
server: %s"), body));
+                       } else {
+                               g_propagate_error (perror, EDC_ERROR (AuthenticationRequired));
+                       }
+
+                       g_free (body);
+               } else {
+                       g_propagate_error (perror, EDC_ERROR (AuthenticationRequired));
+               }
                break;
 
        case SOUP_STATUS_UNAUTHORIZED:
@@ -1128,7 +1173,12 @@ soup_authenticate (SoupSession *session,
        extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
        auth_extension = e_source_get_extension (source, extension_name);
 
-       cbdav->priv->using_bearer_auth = E_IS_SOUP_AUTH_BEARER (auth);
+       if (E_IS_SOUP_AUTH_BEARER (auth)) {
+               g_object_ref (auth);
+               g_warn_if_fail ((gpointer) cbdav->priv->using_bearer_auth == (gpointer) auth);
+               g_clear_object (&cbdav->priv->using_bearer_auth);
+               cbdav->priv->using_bearer_auth = E_SOUP_AUTH_BEARER (auth);
+       }
 
        if (retrying)
                return;
@@ -1212,6 +1262,21 @@ send_and_handle_redirection (ECalBackendCalDAV *cbdav,
 
        e_soup_ssl_trust_connect (msg, e_backend_get_source (E_BACKEND (cbdav)));
 
+       if (cbdav->priv->using_bearer_auth &&
+           e_soup_auth_bearer_is_expired (cbdav->priv->using_bearer_auth)) {
+               GError *local_error = NULL;
+
+               if (!caldav_setup_bearer_auth (cbdav, cbdav->priv->using_bearer_auth, cancellable, 
&local_error)) {
+                       if (local_error) {
+                               soup_message_set_status_full (msg, SOUP_STATUS_BAD_REQUEST, 
local_error->message);
+                               g_propagate_error (error, local_error);
+                       } else {
+                               soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
+                       }
+                       return;
+               }
+       }
+
        soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
        soup_message_add_header_handler (msg, "got_body", "Location", G_CALLBACK (redirect_handler), 
cbdav->priv->session);
        soup_message_headers_append (msg->request_headers, "Connection", "close");
@@ -5526,6 +5591,7 @@ e_cal_backend_caldav_dispose (GObject *object)
 
        g_clear_object (&priv->store);
        g_clear_object (&priv->session);
+       g_clear_object (&priv->using_bearer_auth);
 
        /* Chain up to parent's dispose() method. */
        G_OBJECT_CLASS (parent_class)->dispose (object);
diff --git a/libedataserver/e-data-server-util.c b/libedataserver/e-data-server-util.c
index 60bb63d..39351ba 100644
--- a/libedataserver/e-data-server-util.c
+++ b/libedataserver/e-data-server-util.c
@@ -2915,10 +2915,8 @@ e_util_get_source_oauth2_access_token_sync (ESource *source,
                        source, cancellable, out_access_token,
                        out_expires_in_seconds, error);
        } else if (g_strcmp0 (auth_method, "Google") == 0) {
-               success = TRUE;
-
-               e_source_credentials_google_util_extract_from_credentials (
-                       credentials, out_access_token, out_expires_in_seconds);
+               success = e_source_credentials_google_get_access_token_sync (
+                       source, credentials, out_access_token, out_expires_in_seconds, cancellable, error);
        }
 
        g_free (auth_method);
diff --git a/libedataserver/e-soup-auth-bearer.c b/libedataserver/e-soup-auth-bearer.c
index 4cdd029..2e0583f 100644
--- a/libedataserver/e-soup-auth-bearer.c
+++ b/libedataserver/e-soup-auth-bearer.c
@@ -56,17 +56,6 @@ G_DEFINE_TYPE (
        e_soup_auth_bearer,
        SOUP_TYPE_AUTH)
 
-static gboolean
-e_soup_auth_bearer_is_expired (ESoupAuthBearer *bearer)
-{
-       gboolean expired = FALSE;
-
-       if (bearer->priv->expiry != EXPIRY_INVALID)
-               expired = (bearer->priv->expiry < time (NULL));
-
-       return expired;
-}
-
 static void
 e_soup_auth_bearer_finalize (GObject *object)
 {
@@ -185,7 +174,7 @@ e_soup_auth_bearer_set_access_token (ESoupAuthBearer *bearer,
        bearer->priv->access_token = g_strdup (access_token);
 
        if (expires_in_seconds > 0)
-               bearer->priv->expiry = time (NULL) + expires_in_seconds;
+               bearer->priv->expiry = time (NULL) + expires_in_seconds - 1;
        else
                bearer->priv->expiry = EXPIRY_INVALID;
 
@@ -197,3 +186,24 @@ e_soup_auth_bearer_set_access_token (ESoupAuthBearer *bearer,
                        SOUP_AUTH_IS_AUTHENTICATED);
 }
 
+/**
+ * e_soup_auth_bearer_is_expired:
+ * @bearer: an #ESoupAuthBearer
+ *
+ * Returns: Whether the set token is expired. It is considered expired even
+ *   if the e_soup_auth_bearer_set_access_token() was called set yet.
+ *
+ * Since: 3.24
+ **/
+gboolean
+e_soup_auth_bearer_is_expired (ESoupAuthBearer *bearer)
+{
+       gboolean expired = TRUE;
+
+       g_return_val_if_fail (E_IS_SOUP_AUTH_BEARER (bearer), TRUE);
+
+       if (bearer->priv->expiry != EXPIRY_INVALID)
+               expired = (bearer->priv->expiry <= time (NULL));
+
+       return expired;
+}
diff --git a/libedataserver/e-soup-auth-bearer.h b/libedataserver/e-soup-auth-bearer.h
index 8f08aeb..8aaffbf 100644
--- a/libedataserver/e-soup-auth-bearer.h
+++ b/libedataserver/e-soup-auth-bearer.h
@@ -71,6 +71,7 @@ void          e_soup_auth_bearer_set_access_token
                                                (ESoupAuthBearer *bearer,
                                                 const gchar *access_token,
                                                 gint expires_in_seconds);
+gboolean       e_soup_auth_bearer_is_expired   (ESoupAuthBearer *bearer);
 
 G_END_DECLS
 
diff --git a/libedataserver/e-source-credentials-provider-impl-google.c 
b/libedataserver/e-source-credentials-provider-impl-google.c
index 5761a17..26bce3e 100644
--- a/libedataserver/e-source-credentials-provider-impl-google.c
+++ b/libedataserver/e-source-credentials-provider-impl-google.c
@@ -63,6 +63,38 @@ e_source_credentials_provider_impl_google_can_prompt (ESourceCredentialsProvider
 }
 
 static gboolean
+e_source_credentials_google_util_store_secret_for_source_sync (ESource *source,
+                                                              const gchar *secret,
+                                                              gboolean permanently,
+                                                              GCancellable *cancellable,
+                                                              GError **error)
+{
+       gchar *uid = NULL, *label;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
+       g_return_val_if_fail (secret != NULL, FALSE);
+
+       if (!e_source_credentials_google_util_generate_secret_uid (source, &uid)) {
+               g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       /* Translators: The first %s is a display name of the source, the second is its UID. 
*/
+                       _("Source '%s' (%s) is not a valid Google source"),
+                       e_source_get_display_name (source),
+                       e_source_get_uid (source));
+               return FALSE;
+       }
+
+       label = g_strdup_printf ("Evolution Data Source - %s", strstr (uid, "::") + 2);
+
+       success = e_secret_store_store_sync (uid, secret, label, permanently, cancellable, error);
+
+       g_free (label);
+       g_free (uid);
+
+       return success;
+}
+
+static gboolean
 e_source_credentials_google_util_get_access_token_from_secret (const gchar *secret,
                                                               gchar **out_access_token)
 {
@@ -95,26 +127,18 @@ e_source_credentials_google_util_get_access_token_from_secret (const gchar *secr
 }
 
 static gboolean
-e_source_credentials_provider_impl_google_lookup_sync (ESourceCredentialsProviderImpl *provider_impl,
-                                                      ESource *source,
-                                                      GCancellable *cancellable,
-                                                      ENamedParameters **out_credentials,
-                                                      GError **error)
+e_source_credentials_google_util_lookup_secret_for_source_sync (ESource *source,
+                                                               GCancellable *cancellable,
+                                                               ENamedParameters **out_credentials,
+                                                               GError **error)
 {
        gchar *uid = NULL, *secret = NULL, *access_token = NULL;
 
-       g_return_val_if_fail (E_IS_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE (provider_impl), FALSE);
        g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
        g_return_val_if_fail (out_credentials != NULL, FALSE);
 
        *out_credentials = NULL;
 
-       if (!e_source_credentials_google_is_supported ()) {
-               g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
-                       _("Google authentication is not supported"));
-               return FALSE;
-       }
-
        if (!e_source_credentials_google_util_generate_secret_uid (source, &uid)) {
                g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                        /* Translators: The first %s is a display name of the source, the second is its UID. 
*/
@@ -149,6 +173,28 @@ e_source_credentials_provider_impl_google_lookup_sync (ESourceCredentialsProvide
 }
 
 static gboolean
+e_source_credentials_provider_impl_google_lookup_sync (ESourceCredentialsProviderImpl *provider_impl,
+                                                      ESource *source,
+                                                      GCancellable *cancellable,
+                                                      ENamedParameters **out_credentials,
+                                                      GError **error)
+{
+       g_return_val_if_fail (E_IS_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE (provider_impl), FALSE);
+       g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
+       g_return_val_if_fail (out_credentials != NULL, FALSE);
+
+       *out_credentials = NULL;
+
+       if (!e_source_credentials_google_is_supported ()) {
+               g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                       _("Google authentication is not supported"));
+               return FALSE;
+       }
+
+       return e_source_credentials_google_util_lookup_secret_for_source_sync (source, cancellable, 
out_credentials, error);
+}
+
+static gboolean
 e_source_credentials_provider_impl_google_store_sync (ESourceCredentialsProviderImpl *provider_impl,
                                                      ESource *source,
                                                      const ENamedParameters *credentials,
@@ -156,9 +202,6 @@ e_source_credentials_provider_impl_google_store_sync (ESourceCredentialsProvider
                                                      GCancellable *cancellable,
                                                      GError **error)
 {
-       gchar *uid = NULL, *label;
-       gboolean success;
-
        g_return_val_if_fail (E_IS_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE (provider_impl), FALSE);
        g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
        g_return_val_if_fail (credentials != NULL, FALSE);
@@ -170,25 +213,9 @@ e_source_credentials_provider_impl_google_store_sync (ESourceCredentialsProvider
                return FALSE;
        }
 
-       if (!e_source_credentials_google_util_generate_secret_uid (source, &uid)) {
-               g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                       /* Translators: The first %s is a display name of the source, the second is its UID. 
*/
-                       _("Source '%s' (%s) is not a valid Google source"),
-                       e_source_get_display_name (source),
-                       e_source_get_uid (source));
-               return FALSE;
-       }
-
-       label = g_strdup_printf ("Evolution Data Source - %s", strstr (uid, "::") + 2);
-
-       success = e_secret_store_store_sync (uid,
+       return e_source_credentials_google_util_store_secret_for_source_sync (source,
                e_named_parameters_get (credentials, E_SOURCE_CREDENTIAL_GOOGLE_SECRET),
-               label, permanently, cancellable, error);
-
-       g_free (label);
-       g_free (uid);
-
-       return success;
+               permanently, cancellable, error);
 }
 
 static gboolean
@@ -258,6 +285,268 @@ e_source_credentials_google_is_supported (void)
 #endif
 }
 
+/* The "refresh token" code is mostly copied from e-credentials-prompter-impl-google.c,
+   which cannot be linked here, because the build dependency is opposite. */
+
+#define GOOGLE_TOKEN_URI "https://www.googleapis.com/oauth2/v3/token";
+
+static gchar *
+cpi_google_create_refresh_token_post_data (const gchar *refresh_token)
+{
+       g_return_val_if_fail (refresh_token != NULL, NULL);
+
+#ifdef ENABLE_GOOGLE_AUTH
+       return soup_form_encode (
+               "refresh_token", refresh_token,
+               "client_id", GOOGLE_CLIENT_ID,
+               "client_secret", GOOGLE_CLIENT_SECRET,
+               "grant_type", "refresh_token",
+               NULL);
+#else
+       return NULL;
+#endif
+}
+
+static void
+cpi_google_abort_session_cb (GCancellable *cancellable,
+                            SoupSession *session)
+{
+       soup_session_abort (session);
+}
+
+static guint
+cpi_google_post_data_sync (const gchar *uri,
+                          const gchar *post_data,
+                          /* ESourceRegistry *registry, */
+                          ESource *cred_source,
+                          GCancellable *cancellable,
+                          gchar **out_response_data)
+{
+       SoupSession *session;
+       SoupMessage *message;
+       guint status_code = SOUP_STATUS_CANCELLED;
+
+       g_return_val_if_fail (uri != NULL, SOUP_STATUS_MALFORMED);
+       g_return_val_if_fail (post_data != NULL, SOUP_STATUS_MALFORMED);
+       /* g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), SOUP_STATUS_MALFORMED); */
+       g_return_val_if_fail (E_IS_SOURCE (cred_source), SOUP_STATUS_MALFORMED);
+       g_return_val_if_fail (out_response_data != NULL, SOUP_STATUS_MALFORMED);
+
+       *out_response_data = NULL;
+
+       message = soup_message_new (SOUP_METHOD_POST, uri);
+       g_return_val_if_fail (message != NULL, SOUP_STATUS_MALFORMED);
+
+       soup_message_set_request (message, "application/x-www-form-urlencoded",
+               SOUP_MEMORY_TEMPORARY, post_data, strlen (post_data));
+
+       e_soup_ssl_trust_connect (message, cred_source);
+
+       session = soup_session_new ();
+       g_object_set (
+               session,
+               SOUP_SESSION_TIMEOUT, 90,
+               SOUP_SESSION_SSL_STRICT, TRUE,
+               SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
+               SOUP_SESSION_ACCEPT_LANGUAGE_AUTO, TRUE,
+               NULL);
+
+       /* Oops, doesn't honor proxy settings (neither EWebDAVDiscover) */
+       /* cpi_google_setup_proxy_resolver (session, registry, cred_source); */
+
+       soup_message_headers_append (message->request_headers, "Connection", "close");
+
+       if (!g_cancellable_is_cancelled (cancellable)) {
+               gulong cancel_handler_id = 0;
+
+               if (cancellable)
+                       cancel_handler_id = g_cancellable_connect (cancellable, G_CALLBACK 
(cpi_google_abort_session_cb), session, NULL);
+
+               status_code = soup_session_send_message (session, message);
+
+               if (cancel_handler_id)
+                       g_cancellable_disconnect (cancellable, cancel_handler_id);
+       }
+
+       if (SOUP_STATUS_IS_SUCCESSFUL (status_code)) {
+               if (message->response_body) {
+                       *out_response_data = g_strndup (message->response_body->data, 
message->response_body->length);
+               } else {
+                       status_code = SOUP_STATUS_MALFORMED;
+               }
+       }
+
+       g_object_unref (message);
+       g_object_unref (session);
+
+       return status_code;
+}
+
+static gboolean
+e_source_credentials_google_refresh_token_sync (ESource *source,
+                                               const ENamedParameters *credentials,
+                                               gchar **out_access_token,
+                                               gint *out_expires_in_seconds,
+                                               GCancellable *cancellable,
+                                               GError **error)
+{
+       const gchar *secret;
+       gchar *refresh_token = NULL;
+       gchar *post_data, *response_json = NULL;
+       guint soup_status;
+       gboolean success = FALSE;
+
+       secret = e_named_parameters_get (credentials, E_SOURCE_CREDENTIAL_GOOGLE_SECRET);
+       if (!secret) {
+               g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Failed to get Google secret 
from credentials"));
+               return FALSE;
+       }
+
+       if (!e_source_credentials_google_util_decode_from_secret (secret, E_GOOGLE_SECRET_REFRESH_TOKEN, 
&refresh_token, NULL) ||
+           !refresh_token || !*refresh_token) {
+               g_free (refresh_token);
+               g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Refresh token not found in 
Google secret"));
+               return FALSE;
+       }
+
+       post_data = cpi_google_create_refresh_token_post_data (refresh_token);
+       g_warn_if_fail (post_data != NULL);
+       if (!post_data) {
+               g_free (refresh_token);
+               g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Failed to construct 
refresh_token request"));
+               return FALSE;
+       }
+
+       soup_status = cpi_google_post_data_sync (GOOGLE_TOKEN_URI, post_data, source, cancellable, 
&response_json);
+
+       if (SOUP_STATUS_IS_SUCCESSFUL (soup_status) && response_json) {
+               gchar *access_token = NULL, *expires_in = NULL;
+
+               if (e_source_credentials_google_util_decode_from_secret (response_json,
+                       "access_token", &access_token,
+                       "expires_in", &expires_in,
+                       NULL) && access_token && expires_in) {
+                       gint64 expires_after_tm, expires_in_tm;
+                       gchar *expires_after;
+                       gchar *new_secret = NULL;
+
+                       expires_in_tm = g_ascii_strtoll (expires_in, NULL, 10) - 1;
+                       expires_after_tm = g_get_real_time () / G_USEC_PER_SEC;
+                       expires_after_tm += expires_in_tm;
+                       expires_after = g_strdup_printf ("%" G_GINT64_FORMAT, expires_after_tm);
+
+                       if (e_source_credentials_google_util_encode_to_secret (&new_secret,
+                               E_GOOGLE_SECRET_REFRESH_TOKEN, refresh_token,
+                               E_GOOGLE_SECRET_ACCESS_TOKEN, access_token,
+                               E_GOOGLE_SECRET_EXPIRES_AFTER, expires_after, NULL)) {
+                               /* Oops, always storing permanently, though it makes more sense here.
+                                  Otherwise can read RememberPassword from [Authentication] and use
+                                  it here. */
+                               success = e_source_credentials_google_util_store_secret_for_source_sync 
(source,
+                                       new_secret, TRUE, cancellable, error);
+                               g_free (new_secret);
+                       } else {
+                               g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Failed to 
encode new access token to Google secret"));
+                       }
+
+                       if (success) {
+                               if (out_access_token)
+                                       *out_access_token = g_strdup (access_token);
+                               if (out_expires_in_seconds)
+                                       *out_expires_in_seconds = expires_in_tm;
+                       }
+               } else {
+                       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Failed to get access 
token from refresh_token server response"));
+               }
+
+               g_free (access_token);
+               g_free (expires_in);
+       } else {
+               g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Failed to refresh token"));
+       }
+
+       g_free (refresh_token);
+       g_free (response_json);
+       g_free (post_data);
+
+       return success;
+}
+
+static gboolean
+e_source_credentials_google_extract_from_credentials_valid_only (const ENamedParameters *credentials,
+                                                                gchar **out_access_token,
+                                                                gint *out_expires_in_seconds)
+{
+       gchar *access_token = NULL;
+       gint expires_in_seconds = -1;
+
+       if (!credentials)
+               return FALSE;
+
+       if (e_source_credentials_google_util_extract_from_credentials (credentials, &access_token, 
&expires_in_seconds)) {
+               if (expires_in_seconds <= 0) {
+                       g_free (access_token);
+                       access_token = NULL;
+               }
+       } else {
+               access_token = NULL;
+       }
+
+       /* The token exists and is valid, just use it. */
+       if (access_token) {
+               if (out_access_token)
+                       *out_access_token = access_token;
+               else
+                       g_free (access_token);
+
+               if (out_expires_in_seconds)
+                       *out_expires_in_seconds = expires_in_seconds;
+
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+gboolean
+e_source_credentials_google_get_access_token_sync (ESource *source,
+                                                  const ENamedParameters *credentials,
+                                                  gchar **out_access_token,
+                                                  gint *out_expires_in_seconds,
+                                                  GCancellable *cancellable,
+                                                  GError **error)
+{
+       ENamedParameters *tmp_credentials = NULL;
+
+       g_return_val_if_fail (credentials != NULL, FALSE);
+
+       if (!e_source_credentials_google_is_supported ()) {
+               g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                       _("Google authentication is not supported"));
+               return FALSE;
+       }
+
+       /* The token exists and is valid, just use it. */
+       if (e_source_credentials_google_extract_from_credentials_valid_only (credentials, out_access_token, 
out_expires_in_seconds)) {
+               return TRUE;
+       }
+
+       /* Maybe some other part refreshed the token already and stored it into the secret store,
+          thus try to read it and use it, if it contains proper data.
+       */
+       if (e_source_credentials_google_util_lookup_secret_for_source_sync (source, cancellable, 
&tmp_credentials, error) &&
+           e_source_credentials_google_extract_from_credentials_valid_only (tmp_credentials, 
out_access_token, out_expires_in_seconds)) {
+               e_named_parameters_free (tmp_credentials);
+
+               return TRUE;
+       }
+
+       e_named_parameters_free (tmp_credentials);
+
+       /* Try to refresh the token */
+       return e_source_credentials_google_refresh_token_sync (source, credentials, out_access_token, 
out_expires_in_seconds, cancellable, error);
+}
+
 gboolean
 e_source_credentials_google_util_generate_secret_uid (ESource *source,
                                                      gchar **out_uid)
@@ -465,10 +754,12 @@ e_source_credentials_google_util_extract_from_credentials (const ENamedParameter
 
        if (out_expires_in_seconds) {
                now = g_get_real_time () / G_USEC_PER_SEC;
-               if (now < expires_after_tm)
-                       now = expires_after_tm;
+               if (expires_after_tm <= now)
+                       expires_after_tm = now;
 
                *out_expires_in_seconds = (gint) (expires_after_tm - now);
+               if (*out_expires_in_seconds > 0)
+                       *out_expires_in_seconds = (*out_expires_in_seconds) - 1;
        }
 
        return TRUE;
diff --git a/libedataserver/e-source-credentials-provider-impl-google.h 
b/libedataserver/e-source-credentials-provider-impl-google.h
index 4181c69..cede621 100644
--- a/libedataserver/e-source-credentials-provider-impl-google.h
+++ b/libedataserver/e-source-credentials-provider-impl-google.h
@@ -80,6 +80,12 @@ struct _ESourceCredentialsProviderImplGoogleClass {
 GType          e_source_credentials_provider_impl_google_get_type      (void);
 
 gboolean       e_source_credentials_google_is_supported                (void);
+gboolean       e_source_credentials_google_get_access_token_sync       (ESource *source,
+                                                                        const ENamedParameters *credentials,
+                                                                        gchar **out_access_token,
+                                                                        gint *out_expires_in_seconds,
+                                                                        GCancellable *cancellable,
+                                                                        GError **error);
 gboolean       e_source_credentials_google_util_generate_secret_uid    (ESource *source,
                                                                         gchar **out_uid);
 gboolean       e_source_credentials_google_util_encode_to_secret       (gchar **out_secret,
diff --git a/libedataserverui/e-credentials-prompter-impl-google.c 
b/libedataserverui/e-credentials-prompter-impl-google.c
index bc08e84..c7a4a25 100644
--- a/libedataserverui/e-credentials-prompter-impl-google.c
+++ b/libedataserverui/e-credentials-prompter-impl-google.c
@@ -291,9 +291,9 @@ cpi_google_update_prompter_credentials (GWeakRef *prompter_google_wr,
                        success = TRUE;
                }
 
-               g_object_unref (prompter_google);
                g_free (secret);
        }
+       g_clear_object (&prompter_google);
 
        g_free (expires_after);
 
@@ -332,8 +332,10 @@ e_credentials_prompter_impl_google_finish_dialog_idle_cb (gpointer user_data)
 
        g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_GOOGLE (prompter_google), FALSE);
 
+       g_mutex_lock (&prompter_google->priv->property_lock);
        if (g_source_get_id (g_main_current_source ()) == prompter_google->priv->show_dialog_idle_id) {
                prompter_google->priv->show_dialog_idle_id = 0;
+               g_mutex_unlock (&prompter_google->priv->property_lock);
 
                g_warn_if_fail (prompter_google->priv->dialog != NULL);
 
@@ -343,6 +345,9 @@ e_credentials_prompter_impl_google_finish_dialog_idle_cb (gpointer user_data)
                        e_credentials_prompter_impl_google_show_html (prompter_google->priv->web_view,
                                "Finished with error", prompter_google->priv->error_text);
                }
+       } else {
+               g_warning ("%s: Source was cancelled? current:%d expected:%d", G_STRFUNC, (gint) 
g_source_get_id (g_main_current_source ()), (gint) prompter_google->priv->show_dialog_idle_id);
+               g_mutex_unlock (&prompter_google->priv->property_lock);
        }
 
        return FALSE;
@@ -442,9 +447,11 @@ cpi_google_get_access_token_thread (gpointer user_data)
                                GOOGLE_TOKEN_URI, soup_status, soup_status_get_phrase (soup_status));
                }
 
+               g_mutex_lock (&prompter_google->priv->property_lock);
                prompter_google->priv->show_dialog_idle_id = g_idle_add (
                        e_credentials_prompter_impl_google_finish_dialog_idle_cb,
                        prompter_google);
+               g_mutex_unlock (&prompter_google->priv->property_lock);
        }
 
        g_clear_object (&prompter_google);
@@ -843,10 +850,12 @@ e_credentials_prompter_impl_google_manage_dialog_idle_cb (gpointer user_data)
 
        g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_GOOGLE (prompter_google), FALSE);
 
+       g_mutex_lock (&prompter_google->priv->property_lock);
        if (g_source_get_id (g_main_current_source ()) == prompter_google->priv->show_dialog_idle_id) {
                gboolean success;
 
                prompter_google->priv->show_dialog_idle_id = 0;
+               g_mutex_unlock (&prompter_google->priv->property_lock);
 
                g_warn_if_fail (prompter_google->priv->dialog == NULL);
 
@@ -861,6 +870,18 @@ e_credentials_prompter_impl_google_manage_dialog_idle_cb (gpointer user_data)
                        success ? prompter_google->priv->credentials : NULL);
 
                e_credentials_prompter_impl_google_free_prompt_data (prompter_google);
+       } else {
+               gpointer prompt_id = prompter_google->priv->prompt_id;
+
+               g_warning ("%s: Prompt's %p source cancelled? current:%d expected:%d", G_STRFUNC, prompt_id, 
(gint) g_source_get_id (g_main_current_source ()), (gint) prompter_google->priv->show_dialog_idle_id);
+
+               if (!prompter_google->priv->show_dialog_idle_id)
+                       e_credentials_prompter_impl_google_free_prompt_data (prompter_google);
+
+               g_mutex_unlock (&prompter_google->priv->property_lock);
+
+               if (prompt_id)
+                       e_credentials_prompter_impl_prompt_finish (E_CREDENTIALS_PROMPTER_IMPL 
(prompter_google), prompt_id, NULL);
        }
 
        return FALSE;
@@ -942,9 +963,11 @@ cpi_google_check_existing_token_thread (gpointer user_data)
  exit:
        prompter_google = g_weak_ref_get (td->prompter_google);
        if (prompter_google && !g_cancellable_is_cancelled (cancellable)) {
+               g_mutex_lock (&prompter_google->priv->property_lock);
                prompter_google->priv->show_dialog_idle_id = g_idle_add (
                        e_credentials_prompter_impl_google_manage_dialog_idle_cb,
                        prompter_google);
+               g_mutex_unlock (&prompter_google->priv->property_lock);
        }
 
        g_clear_object (&prompter_google);
@@ -970,7 +993,14 @@ e_credentials_prompter_impl_google_process_prompt (ECredentialsPrompterImpl *pro
 
        prompter_google = E_CREDENTIALS_PROMPTER_IMPL_GOOGLE (prompter_impl);
        g_return_if_fail (prompter_google->priv->prompt_id == NULL);
-       g_return_if_fail (prompter_google->priv->show_dialog_idle_id == 0);
+
+       g_mutex_lock (&prompter_google->priv->property_lock);
+       if (prompter_google->priv->show_dialog_idle_id != 0) {
+               g_mutex_unlock (&prompter_google->priv->property_lock);
+               g_warning ("%s: Already processing other prompt", G_STRFUNC);
+               return;
+       }
+       g_mutex_unlock (&prompter_google->priv->property_lock);
 
        prompter_google->priv->prompt_id = prompt_id;
        prompter_google->priv->auth_source = g_object_ref (auth_source);
@@ -1021,9 +1051,11 @@ e_credentials_prompter_impl_google_process_prompt (ECredentialsPrompterImpl *pro
                g_thread_unref (thread);
        } else {
 #endif /* ENABLE_GOOGLE_AUTH */
+               g_mutex_lock (&prompter_google->priv->property_lock);
                prompter_google->priv->show_dialog_idle_id = g_idle_add (
                        e_credentials_prompter_impl_google_manage_dialog_idle_cb,
                        prompter_google);
+               g_mutex_unlock (&prompter_google->priv->property_lock);
 #ifdef ENABLE_GOOGLE_AUTH
        }
 #endif /* ENABLE_GOOGLE_AUTH */
@@ -1053,10 +1085,12 @@ e_credentials_prompter_impl_google_dispose (GObject *object)
 {
        ECredentialsPrompterImplGoogle *prompter_google = E_CREDENTIALS_PROMPTER_IMPL_GOOGLE (object);
 
+       g_mutex_lock (&prompter_google->priv->property_lock);
        if (prompter_google->priv->show_dialog_idle_id) {
                g_source_remove (prompter_google->priv->show_dialog_idle_id);
                prompter_google->priv->show_dialog_idle_id = 0;
        }
+       g_mutex_unlock (&prompter_google->priv->property_lock);
 
        if (prompter_google->priv->cancellable) {
                g_cancellable_cancel (prompter_google->priv->cancellable);
diff --git a/libedataserverui/e-credentials-prompter.c b/libedataserverui/e-credentials-prompter.c
index 40c39a7..1e0458e 100644
--- a/libedataserverui/e-credentials-prompter.c
+++ b/libedataserverui/e-credentials-prompter.c
@@ -152,7 +152,7 @@ credentials_prompter_lookup_source_details_thread (GTask *task,
        provider = e_credentials_prompter_get_provider (prompter);
        cred_source = e_source_credentials_provider_ref_credentials_source (provider, source);
 
-       e_source_credentials_provider_lookup_sync (prompter->priv->provider, cred_source ? cred_source : 
source, cancellable, &credentials, &local_error);
+       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)) {
@@ -760,19 +760,29 @@ credentials_prompter_credentials_required_cb (ESourceRegistry *registry,
                                              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)
+           reason != E_SOURCE_CREDENTIALS_REASON_REJECTED) {
                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))
+           (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 &&


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