From e2b9dd3478e873050de4a886ba1d3eb828615aa9 Mon Sep 17 00:00:00 2001
From: Milan Crha <mcrha@redhat.com>
Date: Wed, 4 May 2022 15:30:49 +0200
Subject: [PATCH] I#388 - Google OAuth out-of-band (oob) flow will be
 deprecated

Closes https://gitlab.gnome.org/GNOME/evolution-data-server/-/issues/388
---
 src/libedataserver/e-oauth2-service-google.c | 62 +++++++++++++++++---
 1 file changed, 55 insertions(+), 7 deletions(-)

diff -urp evolution-data-server-3.22.7.orig/libedataserver/e-source-credentials-provider-impl-google.c evolution-data-server-3.22.7/libedataserver/e-source-credentials-provider-impl-google.c
--- evolution-data-server-3.22.7.orig/libedataserver/e-source-credentials-provider-impl-google.c	2017-03-20 04:07:46.000000000 -0500
+++ evolution-data-server-3.22.7/libedataserver/e-source-credentials-provider-impl-google.c	2023-06-08 13:30:05.270717618 -0500
@@ -288,7 +288,7 @@ e_source_credentials_google_is_supported
 /* 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"
+#define GOOGLE_TOKEN_URI "https://oauth2.googleapis.com/token"
 
 static gchar *
 cpi_google_create_refresh_token_post_data (const gchar *refresh_token)
diff -urp evolution-data-server-3.22.7.orig/libedataserverui/e-credentials-prompter-impl-google.c evolution-data-server-3.22.7/libedataserverui/e-credentials-prompter-impl-google.c
--- evolution-data-server-3.22.7.orig/libedataserverui/e-credentials-prompter-impl-google.c	2017-03-20 04:07:46.000000000 -0500
+++ evolution-data-server-3.22.7/libedataserverui/e-credentials-prompter-impl-google.c	2023-06-08 13:30:05.274717639 -0500
@@ -32,10 +32,9 @@
 #ifdef ENABLE_GOOGLE_AUTH
 #include <webkit2/webkit2.h>
 
-/* https://developers.google.com/identity/protocols/OAuth2InstalledApp */
-#define GOOGLE_AUTH_URI "https://accounts.google.com/o/oauth2/auth"
-#define GOOGLE_TOKEN_URI "https://www.googleapis.com/oauth2/v3/token"
-#define GOOGLE_REDIRECT_URI "urn:ietf:wg:oauth:2.0:oob"
+/* https://developers.google.com/identity/protocols/oauth2/native-app */
+#define GOOGLE_AUTH_URI "https://accounts.google.com/o/oauth2/v2/auth"
+#define GOOGLE_TOKEN_URI "https://oauth2.googleapis.com/token"
 
 static const gchar *GOOGLE_SCOPE =
 	/* GMail IMAP and SMTP access */
@@ -72,6 +71,43 @@ struct _ECredentialsPrompterImplGooglePr
 G_DEFINE_TYPE (ECredentialsPrompterImplGoogle, e_credentials_prompter_impl_google, E_TYPE_CREDENTIALS_PROMPTER_IMPL)
 
 #ifdef ENABLE_GOOGLE_AUTH
+static const gchar *
+eos_google_get_redirect_uri ()
+{
+  static gchar *value = NULL;
+
+	if (value)
+		return value;
+
+	const gchar *client_id = GOOGLE_CLIENT_ID;
+
+	if (client_id) {
+		GPtrArray *array;
+		gchar **strv;
+		gchar *joinstr;
+		guint ii;
+
+		strv = g_strsplit (client_id, ".", -1);
+		array = g_ptr_array_new ();
+
+		for (ii = 0; strv[ii]; ii++) {
+			g_ptr_array_insert (array, 0, strv[ii]);
+		}
+
+		g_ptr_array_add (array, NULL);
+
+		joinstr = g_strjoinv (".", (gchar **) array->pdata);
+		/* Use reverse-DNS of the client ID with the below path */
+		value = g_strconcat (joinstr, ":/oauth2redirect", NULL);
+
+		g_ptr_array_free (array, TRUE);
+		g_strfreev (strv);
+		g_free (joinstr);
+	}
+
+	return value;
+}
+
 static gchar *
 cpi_google_create_auth_uri (ESource *source)
 {
@@ -88,7 +124,7 @@ cpi_google_create_auth_uri (ESource *sou
 
 	add_to_form ("response_type", "code");
 	add_to_form ("client_id", GOOGLE_CLIENT_ID);
-	add_to_form ("redirect_uri", GOOGLE_REDIRECT_URI);
+	add_to_form ("redirect_uri", eos_google_get_redirect_uri ());
 	add_to_form ("scope", GOOGLE_SCOPE);
 
 	if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) {
@@ -125,7 +161,7 @@ cpi_google_create_token_post_data (const
 		"code", authorization_code,
 		"client_id", GOOGLE_CLIENT_ID,
 		"client_secret", GOOGLE_CLIENT_SECRET,
-		"redirect_uri", GOOGLE_REDIRECT_URI,
+		"redirect_uri", eos_google_get_redirect_uri (),
 		"grant_type", "authorization_code",
 		NULL);
 }
@@ -462,12 +498,70 @@ cpi_google_get_access_token_thread (gpoi
 	return NULL;
 }
 
+static gboolean
+eos_google_extract_authorization_code (const gchar *page_title,
+				       const gchar *page_uri,
+				       gchar **out_authorization_code)
+{
+	g_return_val_if_fail (out_authorization_code != NULL, FALSE);
+
+	*out_authorization_code = NULL;
+
+	if (page_title && *page_title) {
+		/* Known response, but no authorization code */
+		if (g_ascii_strncasecmp (page_title, "Denied ", 7) == 0)
+			return TRUE;
+
+		if (g_ascii_strncasecmp (page_title, "Success code=", 13) == 0) {
+			*out_authorization_code = g_strdup (page_title + 13);
+			return TRUE;
+		}
+	}
+
+	if (page_uri && *page_uri) {
+		SoupURI *suri;
+
+		suri = soup_uri_new (page_uri);
+		if (suri) {
+			const gchar *query = soup_uri_get_query (suri);
+			gboolean known = FALSE;
+
+			if (query && *query) {
+				GHashTable *params;
+
+				params = soup_form_decode (query);
+				if (params) {
+					const gchar *code;
+
+					code = g_hash_table_lookup (params, "code");
+					if (code && *code) {
+						*out_authorization_code = g_strdup (code);
+						known = TRUE;
+					} else if (g_hash_table_lookup (params, "error")) {
+						known = TRUE;
+					}
+
+					g_hash_table_destroy (params);
+				}
+			}
+
+			soup_uri_free (suri);
+
+			if (known)
+				return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
 static void
 cpi_google_document_load_changed_cb (WebKitWebView *web_view,
 				     WebKitLoadEvent load_event,
 				     ECredentialsPrompterImplGoogle *prompter_google)
 {
-	const gchar *title;
+	const gchar *title, *uri;
+	gchar *authorization_code = NULL;
 
 	g_return_if_fail (WEBKIT_IS_WEB_VIEW (web_view));
 	g_return_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_GOOGLE (prompter_google));
@@ -476,16 +570,18 @@ cpi_google_document_load_changed_cb (Web
 		return;
 
 	title = webkit_web_view_get_title (web_view);
-	if (!title)
+	uri = webkit_web_view_get_uri (web_view);
+	if (!title || !uri)
+		return;
+
+	if (!eos_google_extract_authorization_code (title, uri, &authorization_code))
 		return;
 
-	if (g_ascii_strncasecmp (title, "Denied ", 7) == 0) {
+	if (!authorization_code) {
 		g_cancellable_cancel (prompter_google->priv->cancellable);
 		gtk_dialog_response (prompter_google->priv->dialog, GTK_RESPONSE_CANCEL);
 		return;
-	}
-
-	if (g_ascii_strncasecmp (title, "Success code=", 13) == 0) {
+	} else {
 		ECredentialsPrompter *prompter;
 		ECredentialsPrompterImpl *prompter_impl;
 		AccessTokenThreadData *td;
@@ -506,7 +602,7 @@ cpi_google_document_load_changed_cb (Web
 		td->cancellable = g_object_ref (prompter_google->priv->cancellable);
 		td->cred_source = g_object_ref (prompter_google->priv->cred_source);
 		td->registry = g_object_ref (e_credentials_prompter_get_registry (prompter));
-		td->authorization_code = g_strdup (title + 13);
+		td->authorization_code = authorization_code;
 
 		thread = g_thread_new (G_STRFUNC, cpi_google_get_access_token_thread, td);
 		g_thread_unref (thread);
