[evolution-data-server] [OAuth2] Enhance how expired or invalid access tokens are handled



commit 256ad540accb5ebb538760192fe7c712b6cc65d3
Author: Milan Crha <mcrha redhat com>
Date:   Tue Nov 21 11:19:10 2017 +0100

    [OAuth2] Enhance how expired or invalid access tokens are handled
    
    With respect of ESoupAuthBearer (used mainly with CalDAV), the missing
    implementation of the e_soup_auth_bearer_update() could cause repeated
    requests with the invalid access token, which had been eventually
    aborted after many tries by libsoup with a runtime warning:
    "SoupMessage <pointer> stuck in infinite loop?"
    The change makes the access token expired in such case, which stops
    the cycle early and doesn't increase the error counter on the server.
    
    The GData authorizers didn't consider expiration time at all.

 .../backends/google/e-gdata-oauth2-authorizer.c    |   51 ++++++++++++++++----
 .../backends/gtasks/e-gdata-oauth2-authorizer.c    |   51 ++++++++++++++++----
 src/libedataserver/e-soup-auth-bearer.c            |   13 +++++-
 .../google-backend/e-gdata-oauth2-authorizer.c     |   51 ++++++++++++++++----
 4 files changed, 138 insertions(+), 28 deletions(-)
---
diff --git a/src/addressbook/backends/google/e-gdata-oauth2-authorizer.c 
b/src/addressbook/backends/google/e-gdata-oauth2-authorizer.c
index 3012ca6..3ee1d6e 100644
--- a/src/addressbook/backends/google/e-gdata-oauth2-authorizer.c
+++ b/src/addressbook/backends/google/e-gdata-oauth2-authorizer.c
@@ -15,17 +15,24 @@
  *
  */
 
+#include "evolution-data-server-config.h"
+
+#include <time.h>
+
 #include "e-gdata-oauth2-authorizer.h"
 
 #define E_GDATA_OAUTH2_AUTHORIZER_GET_PRIVATE(obj) \
        (G_TYPE_INSTANCE_GET_PRIVATE \
        ((obj), E_TYPE_GDATA_OAUTH2_AUTHORIZER, EGDataOAuth2AuthorizerPrivate))
 
+#define EXPIRY_INVALID ((time_t) -1)
+
 struct _EGDataOAuth2AuthorizerPrivate {
        GWeakRef source;
 
        /* These members are protected by the global mutex. */
        gchar *access_token;
+       time_t expiry;
        GHashTable *authorization_domains;
        ENamedParameters *credentials;
 };
@@ -51,6 +58,19 @@ G_DEFINE_TYPE_WITH_CODE (
                e_gdata_oauth2_authorizer_interface_init))
 
 static gboolean
+e_gdata_oauth2_authorizer_is_expired (GDataAuthorizer *authorizer)
+{
+       EGDataOAuth2Authorizer *oauth2_authorizer;
+
+       g_return_val_if_fail (E_IS_GDATA_OAUTH2_AUTHORIZER (authorizer), TRUE);
+
+       oauth2_authorizer = E_GDATA_OAUTH2_AUTHORIZER (authorizer);
+
+       return oauth2_authorizer->priv->expiry == EXPIRY_INVALID ||
+              oauth2_authorizer->priv->expiry <= time (NULL);
+}
+
+static gboolean
 gdata_oauth2_authorizer_is_authorized (GDataAuthorizer *authorizer,
                                        GDataAuthorizationDomain *domain)
 {
@@ -183,7 +203,8 @@ gdata_oauth2_authorizer_process_request (GDataAuthorizer *authorizer,
 
        g_mutex_lock (&mutex);
 
-       if (!gdata_oauth2_authorizer_is_authorized (authorizer, domain))
+       if (!gdata_oauth2_authorizer_is_authorized (authorizer, domain) ||
+           e_gdata_oauth2_authorizer_is_expired (authorizer))
                goto exit;
 
        /* We can't add an Authorization header without an access token.
@@ -229,28 +250,39 @@ gdata_oauth2_authorizer_refresh_authorization (GDataAuthorizer *authorizer,
 {
        EGDataOAuth2Authorizer *oauth2_authorizer;
        ESource *source;
-       gchar **ptr_access_token;
+       gchar *access_token = NULL;
+       gint expires_in_seconds = -1;
        gboolean success = FALSE;
 
        oauth2_authorizer = E_GDATA_OAUTH2_AUTHORIZER (authorizer);
        source = e_gdata_oauth2_authorizer_ref_source (oauth2_authorizer);
        g_return_val_if_fail (source != NULL, FALSE);
 
-       ptr_access_token = &oauth2_authorizer->priv->access_token;
-
        g_mutex_lock (&mutex);
 
-       g_free (*ptr_access_token);
-       *ptr_access_token = NULL;
-
        success = e_util_get_source_oauth2_access_token_sync (source, oauth2_authorizer->priv->credentials,
-               ptr_access_token, NULL, cancellable, error);
+               &access_token, &expires_in_seconds, cancellable, error);
+
+       /* Returned token is the same, thus no refresh happened, thus rather fail. */
+       if (access_token && g_strcmp0 (access_token, oauth2_authorizer->priv->access_token) == 0) {
+               g_free (access_token);
+               access_token = NULL;
+               success = FALSE;
+       }
+
+       g_free (oauth2_authorizer->priv->access_token);
+       oauth2_authorizer->priv->access_token = access_token;
+
+       if (success && expires_in_seconds > 0)
+               oauth2_authorizer->priv->expiry = time (NULL) + expires_in_seconds - 1;
+       else
+               oauth2_authorizer->priv->expiry = EXPIRY_INVALID;
 
        g_mutex_unlock (&mutex);
 
        g_object_unref (source);
 
-       return success;
+       return success && access_token;
 }
 
 static void
@@ -305,6 +337,7 @@ e_gdata_oauth2_authorizer_init (EGDataOAuth2Authorizer *authorizer)
 
        authorizer->priv = E_GDATA_OAUTH2_AUTHORIZER_GET_PRIVATE (authorizer);
        authorizer->priv->authorization_domains = authorization_domains;
+       authorizer->priv->expiry = EXPIRY_INVALID;
        g_weak_ref_init (&authorizer->priv->source, NULL);
 }
 
diff --git a/src/calendar/backends/gtasks/e-gdata-oauth2-authorizer.c 
b/src/calendar/backends/gtasks/e-gdata-oauth2-authorizer.c
index 094b30b..5370b0f 100644
--- a/src/calendar/backends/gtasks/e-gdata-oauth2-authorizer.c
+++ b/src/calendar/backends/gtasks/e-gdata-oauth2-authorizer.c
@@ -15,17 +15,24 @@
  *
  */
 
+#include "evolution-data-server-config.h"
+
+#include <time.h>
+
 #include "e-gdata-oauth2-authorizer.h"
 
 #define E_GDATA_OAUTH2_AUTHORIZER_GET_PRIVATE(obj) \
        (G_TYPE_INSTANCE_GET_PRIVATE \
        ((obj), E_TYPE_GDATA_OAUTH2_AUTHORIZER, EGDataOAuth2AuthorizerPrivate))
 
+#define EXPIRY_INVALID ((time_t) -1)
+
 struct _EGDataOAuth2AuthorizerPrivate {
        GWeakRef source;
 
        /* These members are protected by the global mutex. */
        gchar *access_token;
+       time_t expiry;
        GHashTable *authorization_domains;
        ENamedParameters *credentials;
 };
@@ -51,6 +58,19 @@ G_DEFINE_TYPE_WITH_CODE (
                e_gdata_oauth2_authorizer_interface_init))
 
 static gboolean
+e_gdata_oauth2_authorizer_is_expired (GDataAuthorizer *authorizer)
+{
+       EGDataOAuth2Authorizer *oauth2_authorizer;
+
+       g_return_val_if_fail (E_IS_GDATA_OAUTH2_AUTHORIZER (authorizer), TRUE);
+
+       oauth2_authorizer = E_GDATA_OAUTH2_AUTHORIZER (authorizer);
+
+       return oauth2_authorizer->priv->expiry == EXPIRY_INVALID ||
+              oauth2_authorizer->priv->expiry <= time (NULL);
+}
+
+static gboolean
 gdata_oauth2_authorizer_is_authorized (GDataAuthorizer *authorizer,
                                        GDataAuthorizationDomain *domain)
 {
@@ -183,7 +203,8 @@ gdata_oauth2_authorizer_process_request (GDataAuthorizer *authorizer,
 
        g_mutex_lock (&mutex);
 
-       if (!gdata_oauth2_authorizer_is_authorized (authorizer, domain))
+       if (!gdata_oauth2_authorizer_is_authorized (authorizer, domain) ||
+           e_gdata_oauth2_authorizer_is_expired (authorizer))
                goto exit;
 
        /* We can't add an Authorization header without an access token.
@@ -229,28 +250,39 @@ gdata_oauth2_authorizer_refresh_authorization (GDataAuthorizer *authorizer,
 {
        EGDataOAuth2Authorizer *oauth2_authorizer;
        ESource *source;
-       gchar **ptr_access_token;
+       gchar *access_token = NULL;
+       gint expires_in_seconds = -1;
        gboolean success = FALSE;
 
        oauth2_authorizer = E_GDATA_OAUTH2_AUTHORIZER (authorizer);
        source = e_gdata_oauth2_authorizer_ref_source (oauth2_authorizer);
        g_return_val_if_fail (source != NULL, FALSE);
 
-       ptr_access_token = &oauth2_authorizer->priv->access_token;
-
        g_mutex_lock (&mutex);
 
-       g_free (*ptr_access_token);
-       *ptr_access_token = NULL;
-
        success = e_util_get_source_oauth2_access_token_sync (source, oauth2_authorizer->priv->credentials,
-               ptr_access_token, NULL, cancellable, error);
+               &access_token, &expires_in_seconds, cancellable, error);
+
+       /* Returned token is the same, thus no refresh happened, thus rather fail. */
+       if (access_token && g_strcmp0 (access_token, oauth2_authorizer->priv->access_token) == 0) {
+               g_free (access_token);
+               access_token = NULL;
+               success = FALSE;
+       }
+
+       g_free (oauth2_authorizer->priv->access_token);
+       oauth2_authorizer->priv->access_token = access_token;
+
+       if (success && expires_in_seconds > 0)
+               oauth2_authorizer->priv->expiry = time (NULL) + expires_in_seconds - 1;
+       else
+               oauth2_authorizer->priv->expiry = EXPIRY_INVALID;
 
        g_mutex_unlock (&mutex);
 
        g_object_unref (source);
 
-       return success;
+       return success && access_token;
 }
 
 static void
@@ -305,6 +337,7 @@ e_gdata_oauth2_authorizer_init (EGDataOAuth2Authorizer *authorizer)
 
        authorizer->priv = E_GDATA_OAUTH2_AUTHORIZER_GET_PRIVATE (authorizer);
        authorizer->priv->authorization_domains = authorization_domains;
+       authorizer->priv->expiry = EXPIRY_INVALID;
        g_weak_ref_init (&authorizer->priv->source, NULL);
 }
 
diff --git a/src/libedataserver/e-soup-auth-bearer.c b/src/libedataserver/e-soup-auth-bearer.c
index 5070d2b..8d1efa8 100644
--- a/src/libedataserver/e-soup-auth-bearer.c
+++ b/src/libedataserver/e-soup-auth-bearer.c
@@ -72,7 +72,18 @@ e_soup_auth_bearer_update (SoupAuth *auth,
                            SoupMessage *message,
                            GHashTable *auth_header)
 {
-       /* XXX Not sure what to do here.  Discard the access token? */
+       if (message && message->status_code == SOUP_STATUS_UNAUTHORIZED) {
+               ESoupAuthBearer *bearer;
+
+               g_return_val_if_fail (E_IS_SOUP_AUTH_BEARER (auth), FALSE);
+
+               bearer = E_SOUP_AUTH_BEARER (auth);
+
+               /* Expire the token, it's likely to be invalid. */
+               bearer->priv->expiry = EXPIRY_INVALID;
+
+               return FALSE;
+       }
 
        return TRUE;
 }
diff --git a/src/modules/google-backend/e-gdata-oauth2-authorizer.c 
b/src/modules/google-backend/e-gdata-oauth2-authorizer.c
index 094b30b..5370b0f 100644
--- a/src/modules/google-backend/e-gdata-oauth2-authorizer.c
+++ b/src/modules/google-backend/e-gdata-oauth2-authorizer.c
@@ -15,17 +15,24 @@
  *
  */
 
+#include "evolution-data-server-config.h"
+
+#include <time.h>
+
 #include "e-gdata-oauth2-authorizer.h"
 
 #define E_GDATA_OAUTH2_AUTHORIZER_GET_PRIVATE(obj) \
        (G_TYPE_INSTANCE_GET_PRIVATE \
        ((obj), E_TYPE_GDATA_OAUTH2_AUTHORIZER, EGDataOAuth2AuthorizerPrivate))
 
+#define EXPIRY_INVALID ((time_t) -1)
+
 struct _EGDataOAuth2AuthorizerPrivate {
        GWeakRef source;
 
        /* These members are protected by the global mutex. */
        gchar *access_token;
+       time_t expiry;
        GHashTable *authorization_domains;
        ENamedParameters *credentials;
 };
@@ -51,6 +58,19 @@ G_DEFINE_TYPE_WITH_CODE (
                e_gdata_oauth2_authorizer_interface_init))
 
 static gboolean
+e_gdata_oauth2_authorizer_is_expired (GDataAuthorizer *authorizer)
+{
+       EGDataOAuth2Authorizer *oauth2_authorizer;
+
+       g_return_val_if_fail (E_IS_GDATA_OAUTH2_AUTHORIZER (authorizer), TRUE);
+
+       oauth2_authorizer = E_GDATA_OAUTH2_AUTHORIZER (authorizer);
+
+       return oauth2_authorizer->priv->expiry == EXPIRY_INVALID ||
+              oauth2_authorizer->priv->expiry <= time (NULL);
+}
+
+static gboolean
 gdata_oauth2_authorizer_is_authorized (GDataAuthorizer *authorizer,
                                        GDataAuthorizationDomain *domain)
 {
@@ -183,7 +203,8 @@ gdata_oauth2_authorizer_process_request (GDataAuthorizer *authorizer,
 
        g_mutex_lock (&mutex);
 
-       if (!gdata_oauth2_authorizer_is_authorized (authorizer, domain))
+       if (!gdata_oauth2_authorizer_is_authorized (authorizer, domain) ||
+           e_gdata_oauth2_authorizer_is_expired (authorizer))
                goto exit;
 
        /* We can't add an Authorization header without an access token.
@@ -229,28 +250,39 @@ gdata_oauth2_authorizer_refresh_authorization (GDataAuthorizer *authorizer,
 {
        EGDataOAuth2Authorizer *oauth2_authorizer;
        ESource *source;
-       gchar **ptr_access_token;
+       gchar *access_token = NULL;
+       gint expires_in_seconds = -1;
        gboolean success = FALSE;
 
        oauth2_authorizer = E_GDATA_OAUTH2_AUTHORIZER (authorizer);
        source = e_gdata_oauth2_authorizer_ref_source (oauth2_authorizer);
        g_return_val_if_fail (source != NULL, FALSE);
 
-       ptr_access_token = &oauth2_authorizer->priv->access_token;
-
        g_mutex_lock (&mutex);
 
-       g_free (*ptr_access_token);
-       *ptr_access_token = NULL;
-
        success = e_util_get_source_oauth2_access_token_sync (source, oauth2_authorizer->priv->credentials,
-               ptr_access_token, NULL, cancellable, error);
+               &access_token, &expires_in_seconds, cancellable, error);
+
+       /* Returned token is the same, thus no refresh happened, thus rather fail. */
+       if (access_token && g_strcmp0 (access_token, oauth2_authorizer->priv->access_token) == 0) {
+               g_free (access_token);
+               access_token = NULL;
+               success = FALSE;
+       }
+
+       g_free (oauth2_authorizer->priv->access_token);
+       oauth2_authorizer->priv->access_token = access_token;
+
+       if (success && expires_in_seconds > 0)
+               oauth2_authorizer->priv->expiry = time (NULL) + expires_in_seconds - 1;
+       else
+               oauth2_authorizer->priv->expiry = EXPIRY_INVALID;
 
        g_mutex_unlock (&mutex);
 
        g_object_unref (source);
 
-       return success;
+       return success && access_token;
 }
 
 static void
@@ -305,6 +337,7 @@ e_gdata_oauth2_authorizer_init (EGDataOAuth2Authorizer *authorizer)
 
        authorizer->priv = E_GDATA_OAUTH2_AUTHORIZER_GET_PRIVATE (authorizer);
        authorizer->priv->authorization_domains = authorization_domains;
+       authorizer->priv->expiry = EXPIRY_INVALID;
        g_weak_ref_init (&authorizer->priv->source, NULL);
 }
 


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