[evolution-data-server] Bug 771547 - Internal Google OAuth2 authentication fails with expired token ][
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server] Bug 771547 - Internal Google OAuth2 authentication fails with expired token ][
- Date: Thu, 6 Oct 2016 15:51:36 +0000 (UTC)
commit 1231c17a43e85a754f117bf7eb458c8dc2ee2356
Author: Milan Crha <mcrha redhat com>
Date: Thu Oct 6 17:51:17 2016 +0200
Bug 771547 - Internal Google OAuth2 authentication fails with expired token ][
calendar/backends/caldav/e-cal-backend-caldav.c | 3 +-
libedataserver/e-data-server-util.c | 14 +-
libedataserver/e-soup-auth-bearer.c | 4 +-
.../e-source-credentials-provider-impl-google.c | 357 ++++++++++++++++++--
.../e-source-credentials-provider-impl-google.h | 6 +
.../e-credentials-prompter-impl-google.c | 11 +
6 files changed, 345 insertions(+), 50 deletions(-)
---
diff --git a/calendar/backends/caldav/e-cal-backend-caldav.c b/calendar/backends/caldav/e-cal-backend-caldav.c
index bebd87e..0fe4cb2 100644
--- a/calendar/backends/caldav/e-cal-backend-caldav.c
+++ b/calendar/backends/caldav/e-cal-backend-caldav.c
@@ -1148,9 +1148,10 @@ soup_authenticate (SoupSession *session,
auth_extension = e_source_get_extension (source, extension_name);
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 = g_object_ref (auth);
+ cbdav->priv->using_bearer_auth = E_SOUP_AUTH_BEARER (auth);
}
if (retrying)
diff --git a/libedataserver/e-data-server-util.c b/libedataserver/e-data-server-util.c
index 63dc300..39351ba 100644
--- a/libedataserver/e-data-server-util.c
+++ b/libedataserver/e-data-server-util.c
@@ -2915,18 +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) {
- gint expires_in_seconds = -1;
-
- success = e_source_credentials_google_util_extract_from_credentials (
- credentials, out_access_token, &expires_in_seconds);
- if (!success || expires_in_seconds <= 0) {
- /* Ask to refresh the token, if it's expired */
- e_source_invoke_credentials_required_sync (source,
- expires_in_seconds < 0 ? E_SOURCE_AUTHENTICATION_REQUIRED :
E_SOURCE_AUTHENTICATION_REJECTED,
- NULL, 0, NULL, cancellable, error);
- } else if (out_expires_in_seconds) {
- *out_expires_in_seconds = 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 41ce32e..2e0583f 100644
--- a/libedataserver/e-soup-auth-bearer.c
+++ b/libedataserver/e-soup-auth-bearer.c
@@ -174,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;
@@ -203,7 +203,7 @@ e_soup_auth_bearer_is_expired (ESoupAuthBearer *bearer)
g_return_val_if_fail (E_IS_SOUP_AUTH_BEARER (bearer), TRUE);
if (bearer->priv->expiry != EXPIRY_INVALID)
- expired = (bearer->priv->expiry < time (NULL));
+ expired = (bearer->priv->expiry <= time (NULL));
return expired;
}
diff --git a/libedataserver/e-source-credentials-provider-impl-google.c
b/libedataserver/e-source-credentials-provider-impl-google.c
index 7aecf47..9a775b3 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,264 @@ 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);
+
+ return soup_form_encode (
+ "refresh_token", refresh_token,
+ "client_id", GOOGLE_CLIENT_ID,
+ "client_secret", GOOGLE_CLIENT_SECRET,
+ "grant_type", "refresh_token",
+ NULL);
+}
+
+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 +750,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 f968d7a..2e8fe3b 100644
--- a/libedataserverui/e-credentials-prompter-impl-google.c
+++ b/libedataserverui/e-credentials-prompter-impl-google.c
@@ -346,6 +346,7 @@ e_credentials_prompter_impl_google_finish_dialog_idle_cb (gpointer user_data)
"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);
}
@@ -870,7 +871,17 @@ e_credentials_prompter_impl_google_manage_dialog_idle_cb (gpointer user_data)
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;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]