[gnome-online-accounts/f20] Adapt to changes in the redirect URI used by Facebook (GNOME #710363)

Debarshi Ray rishi at fedoraproject.org
Fri Oct 18 12:14:34 UTC 2013


commit e0441a444f334d82a4ef6674b98392356b9e6d51
Author: Debarshi Ray <debarshir at gnome.org>
Date:   Fri Oct 18 14:14:23 2013 +0200

    Adapt to changes in the redirect URI used by Facebook (GNOME #710363)

 gnome-online-accounts-facebook.patch |  487 ++++++++++++++++++++++++++++++++++
 gnome-online-accounts.spec           |    9 +-
 2 files changed, 495 insertions(+), 1 deletions(-)
---
diff --git a/gnome-online-accounts-facebook.patch b/gnome-online-accounts-facebook.patch
new file mode 100644
index 0000000..e257022
--- /dev/null
+++ b/gnome-online-accounts-facebook.patch
@@ -0,0 +1,487 @@
+From e52d671808139e8bce90e87f81486288267b645f Mon Sep 17 00:00:00 2001
+From: Debarshi Ray <debarshir at gnome.org>
+Date: Thu, 17 Oct 2013 16:26:16 +0200
+Subject: [PATCH 1/4] oauth2: Clean up
+
+Consolidate the ways in which the query and fragment are accessed.
+
+Fixes: https://bugzilla.gnome.org/710363
+---
+ src/goabackend/goaoauth2provider.c | 12 ++++++++----
+ 1 file changed, 8 insertions(+), 4 deletions(-)
+
+diff --git a/src/goabackend/goaoauth2provider.c b/src/goabackend/goaoauth2provider.c
+index 8efdea5..3a5fdf9 100644
+--- a/src/goabackend/goaoauth2provider.c
++++ b/src/goabackend/goaoauth2provider.c
+@@ -888,9 +888,13 @@ on_web_view_navigation_policy_decision_requested (WebKitWebView             *web
+     {
+       SoupMessage *message;
+       SoupURI *uri;
++      const gchar *fragment;
++      const gchar *query;
+ 
+       message = webkit_network_request_get_message (request);
+       uri = soup_message_get_uri (message);
++      fragment = soup_uri_get_fragment (uri);
++      query = soup_uri_get_query (uri);
+ 
+       /* Two cases:
+        * 1) we can have either the auth code in the query part of the
+@@ -898,11 +902,11 @@ on_web_view_navigation_policy_decision_requested (WebKitWebView             *web
+        * 2) the access_token and other information directly in the
+        *    fragment part of the URI.
+        */
+-      if (soup_uri_get_query (uri) != NULL)
++      if (query != NULL)
+         {
+           GHashTable *key_value_pairs;
+ 
+-          key_value_pairs = soup_form_decode (uri->query);
++          key_value_pairs = soup_form_decode (query);
+ 
+           priv->authorization_code = g_strdup (g_hash_table_lookup (key_value_pairs, "code"));
+           if (priv->authorization_code != NULL)
+@@ -927,13 +931,13 @@ on_web_view_navigation_policy_decision_requested (WebKitWebView             *web
+           g_hash_table_unref (key_value_pairs);
+           webkit_web_policy_decision_ignore (policy_decision);
+         }
+-      else if (soup_uri_get_fragment (uri) != NULL)
++      else if (fragment != NULL)
+         {
+           GHashTable *key_value_pairs;
+ 
+           /* fragment is encoded into a key/value pairs for the token and
+            * expiration values, using the same syntax as a URL query */
+-          key_value_pairs = soup_form_decode (soup_uri_get_fragment (uri));
++          key_value_pairs = soup_form_decode (fragment);
+ 
+           /* We might use oauth2_proxy_extract_access_token() here but
+            * we can also extract other information.
+-- 
+1.8.3.1
+
+
+From d3d339b32122789e8422c0a66f1e580a3af29e26 Mon Sep 17 00:00:00 2001
+From: Debarshi Ray <debarshir at gnome.org>
+Date: Fri, 18 Oct 2013 12:13:52 +0200
+Subject: [PATCH 2/4] oauth2: Clean up
+
+Simplify the maze of nested if-else branches and multiple return sites.
+
+Fixes: https://bugzilla.gnome.org/710363
+---
+ src/goabackend/goaoauth2provider.c | 173 +++++++++++++++++++------------------
+ 1 file changed, 88 insertions(+), 85 deletions(-)
+
+diff --git a/src/goabackend/goaoauth2provider.c b/src/goabackend/goaoauth2provider.c
+index 3a5fdf9..45228ca 100644
+--- a/src/goabackend/goaoauth2provider.c
++++ b/src/goabackend/goaoauth2provider.c
+@@ -876,120 +876,123 @@ on_web_view_navigation_policy_decision_requested (WebKitWebView             *web
+ {
+   GoaOAuth2Provider *provider = GOA_OAUTH2_PROVIDER (user_data);
+   GoaOAuth2ProviderPrivate *priv = provider->priv;
++  SoupMessage *message;
++  SoupURI *uri;
++  const gchar *fragment;
+   const gchar *oauth2_error;
++  const gchar *query;
+   const gchar *redirect_uri;
+   const gchar *requested_uri;
++  gint response_id;
+ 
+   /* TODO: use oauth2_proxy_extract_access_token() */
+ 
+   requested_uri = webkit_network_request_get_uri (request);
+   redirect_uri = goa_oauth2_provider_get_redirect_uri (provider);
+-  if (g_str_has_prefix (requested_uri, redirect_uri))
++  if (!g_str_has_prefix (requested_uri, redirect_uri))
++    goto default_behaviour;
++
++  message = webkit_network_request_get_message (request);
++  uri = soup_message_get_uri (message);
++  fragment = soup_uri_get_fragment (uri);
++  query = soup_uri_get_query (uri);
++
++  /* Two cases:
++   * 1) we can have either the auth code in the query part of the
++   *    URI, with which we'll obtain the token, or
++   * 2) the access_token and other information directly in the
++   *    fragment part of the URI.
++   */
++  if (query != NULL)
+     {
+-      SoupMessage *message;
+-      SoupURI *uri;
+-      const gchar *fragment;
+-      const gchar *query;
+-
+-      message = webkit_network_request_get_message (request);
+-      uri = soup_message_get_uri (message);
+-      fragment = soup_uri_get_fragment (uri);
+-      query = soup_uri_get_query (uri);
+-
+-      /* Two cases:
+-       * 1) we can have either the auth code in the query part of the
+-       *    URI, with which we'll obtain the token, or
+-       * 2) the access_token and other information directly in the
+-       *    fragment part of the URI.
+-       */
+-      if (query != NULL)
+-        {
+-          GHashTable *key_value_pairs;
++      GHashTable *key_value_pairs;
+ 
+-          key_value_pairs = soup_form_decode (query);
++      key_value_pairs = soup_form_decode (query);
+ 
+-          priv->authorization_code = g_strdup (g_hash_table_lookup (key_value_pairs, "code"));
+-          if (priv->authorization_code != NULL)
+-            {
+-              gtk_dialog_response (priv->dialog, GTK_RESPONSE_OK);
+-            }
++      priv->authorization_code = g_strdup (g_hash_table_lookup (key_value_pairs, "code"));
++      if (priv->authorization_code != NULL)
++        {
++          response_id = GTK_RESPONSE_OK;
++        }
++      else
++        {
++          oauth2_error = (const gchar *) g_hash_table_lookup (key_value_pairs, "error");
++          if (g_strcmp0 (oauth2_error, GOA_OAUTH2_ACCESS_DENIED) == 0)
++            response_id = GTK_RESPONSE_CANCEL;
+           else
+             {
+-              oauth2_error = (const gchar *) g_hash_table_lookup (key_value_pairs, "error");
+-              if (g_strcmp0 (oauth2_error, GOA_OAUTH2_ACCESS_DENIED) == 0)
+-                gtk_dialog_response (priv->dialog, GTK_RESPONSE_CANCEL);
+-              else
+-                {
+-                  g_set_error (&priv->error,
+-                               GOA_ERROR,
+-                               GOA_ERROR_NOT_AUTHORIZED,
+-                               _("Authorization response was \"%s\""),
+-                               oauth2_error);
+-                  gtk_dialog_response (priv->dialog, GTK_RESPONSE_CLOSE);
+-                }
++              g_set_error (&priv->error,
++                           GOA_ERROR,
++                           GOA_ERROR_NOT_AUTHORIZED,
++                           _("Authorization response was \"%s\""),
++                           oauth2_error);
++              response_id = GTK_RESPONSE_CLOSE;
+             }
+-          g_hash_table_unref (key_value_pairs);
+-          webkit_web_policy_decision_ignore (policy_decision);
+         }
+-      else if (fragment != NULL)
+-        {
+-          GHashTable *key_value_pairs;
++      g_hash_table_unref (key_value_pairs);
++      goto ignore_request;
++    }
++  else if (fragment != NULL)
++    {
++      GHashTable *key_value_pairs;
+ 
+-          /* fragment is encoded into a key/value pairs for the token and
+-           * expiration values, using the same syntax as a URL query */
+-          key_value_pairs = soup_form_decode (fragment);
++      /* fragment is encoded into a key/value pairs for the token and
++       * expiration values, using the same syntax as a URL query */
++      key_value_pairs = soup_form_decode (fragment);
+ 
+-          /* We might use oauth2_proxy_extract_access_token() here but
+-           * we can also extract other information.
+-           */
+-          priv->access_token = g_strdup (g_hash_table_lookup (key_value_pairs, "access_token"));
+-          if (priv->access_token != NULL)
+-            {
+-              gchar *expires_in_str = NULL;
++      /* We might use oauth2_proxy_extract_access_token() here but
++       * we can also extract other information.
++       */
++      priv->access_token = g_strdup (g_hash_table_lookup (key_value_pairs, "access_token"));
++      if (priv->access_token != NULL)
++        {
++          gchar *expires_in_str = NULL;
+ 
+-              expires_in_str = g_hash_table_lookup (key_value_pairs, "expires_in");
+-              /* sometimes "expires_in" appears as "expires" */
+-              if (expires_in_str == NULL)
+-                expires_in_str = g_hash_table_lookup (key_value_pairs, "expires");
++          expires_in_str = g_hash_table_lookup (key_value_pairs, "expires_in");
++          /* sometimes "expires_in" appears as "expires" */
++          if (expires_in_str == NULL)
++            expires_in_str = g_hash_table_lookup (key_value_pairs, "expires");
+ 
+-              if (expires_in_str != NULL)
+-                 priv->access_token_expires_in = atoi (expires_in_str);
++          if (expires_in_str != NULL)
++            priv->access_token_expires_in = atoi (expires_in_str);
+ 
+-              priv->refresh_token = g_strdup (g_hash_table_lookup (key_value_pairs, "refresh_token"));
++          priv->refresh_token = g_strdup (g_hash_table_lookup (key_value_pairs, "refresh_token"));
+ 
+-              gtk_dialog_response (priv->dialog, GTK_RESPONSE_OK);
+-            }
+-          else
+-            {
+-              oauth2_error = (const gchar *) g_hash_table_lookup (key_value_pairs, "error");
+-              if (g_strcmp0 (oauth2_error, GOA_OAUTH2_ACCESS_DENIED) == 0)
+-                gtk_dialog_response (priv->dialog, GTK_RESPONSE_CANCEL);
+-              else
+-                {
+-                  g_set_error (&priv->error,
+-                               GOA_ERROR,
+-                               GOA_ERROR_NOT_AUTHORIZED,
+-                               _("Authorization response was \"%s\""),
+-                               oauth2_error);
+-                  gtk_dialog_response (priv->dialog, GTK_RESPONSE_CLOSE);
+-                }
+-            }
+-          g_hash_table_unref (key_value_pairs);
+-          webkit_web_policy_decision_ignore (policy_decision);
++          response_id = GTK_RESPONSE_OK;
+         }
+       else
+         {
+-          /* this actually means that something unexpected happened, either we
+-           * did something wrong or the provider's flow changed */
+-          goa_debug ("URI format not recognized, DEFAULT BEHAVIOUR");
+-          return FALSE;
++          oauth2_error = (const gchar *) g_hash_table_lookup (key_value_pairs, "error");
++          if (g_strcmp0 (oauth2_error, GOA_OAUTH2_ACCESS_DENIED) == 0)
++            response_id = GTK_RESPONSE_CANCEL;
++          else
++            {
++              g_set_error (&priv->error,
++                           GOA_ERROR,
++                           GOA_ERROR_NOT_AUTHORIZED,
++                           _("Authorization response was \"%s\""),
++                           oauth2_error);
++              response_id = GTK_RESPONSE_CLOSE;
++            }
+         }
+-      return TRUE; /* ignore the request */
++      g_hash_table_unref (key_value_pairs);
++      goto ignore_request;
+     }
+   else
+     {
+-      return FALSE; /* make default behavior apply */
++      /* this actually means that something unexpected happened, either we
++       * did something wrong or the provider's flow changed */
++      goa_debug ("URI format not recognized, DEFAULT BEHAVIOUR");
++      goto default_behaviour;
+     }
++
++ ignore_request:
++  gtk_dialog_response (priv->dialog, response_id);
++  webkit_web_policy_decision_ignore (policy_decision);
++  return TRUE;
++
++ default_behaviour:
++  return FALSE;
+ }
+ 
+ static void
+-- 
+1.8.3.1
+
+
+From d35556c6d522941add2c48aade2515f0ae6c5d50 Mon Sep 17 00:00:00 2001
+From: Debarshi Ray <debarshir at gnome.org>
+Date: Fri, 18 Oct 2013 13:34:02 +0200
+Subject: [PATCH 3/4] oauth2: Adapt to changes in the redirect URI used by
+ Facebook
+
+Once the user has logged into the embedded web view and granted
+permissions, Facebook tries to redirect us to an URI of the form:
+  <get_redirect_uri>?#access_token=...
+
+Earlier this used to be of the form:
+  <get_redirect_uri>#access_token=...
+
+This confuses the navigation-policy-decision-requested handler to think
+that Facebook is using the query part of the URI to return the
+authorization code, which we will exchange for the access token. In
+reality Facebook is still using the fragment to directly give us the
+access token.
+
+Therefore, lets first look at the fragment, and then the query.
+
+According to the OAuth2 specification, the error is always passed in
+the query component of the URI. So, if we failed to obtain the
+authorization code or the access token, we should look at the query to
+ascertain the cause of the error. See 4.1.2.1 for more information:
+https://tools.ietf.org/html/draft-ietf-oauth-v2-23
+
+Fixes: https://bugzilla.gnome.org/710363
+---
+ src/goabackend/goaoauth2provider.c | 95 ++++++++++++++++----------------------
+ 1 file changed, 39 insertions(+), 56 deletions(-)
+
+diff --git a/src/goabackend/goaoauth2provider.c b/src/goabackend/goaoauth2provider.c
+index 45228ca..523a235 100644
+--- a/src/goabackend/goaoauth2provider.c
++++ b/src/goabackend/goaoauth2provider.c
+@@ -876,6 +876,7 @@ on_web_view_navigation_policy_decision_requested (WebKitWebView             *web
+ {
+   GoaOAuth2Provider *provider = GOA_OAUTH2_PROVIDER (user_data);
+   GoaOAuth2ProviderPrivate *priv = provider->priv;
++  GHashTable *key_value_pairs;
+   SoupMessage *message;
+   SoupURI *uri;
+   const gchar *fragment;
+@@ -898,44 +899,13 @@ on_web_view_navigation_policy_decision_requested (WebKitWebView             *web
+   query = soup_uri_get_query (uri);
+ 
+   /* Two cases:
+-   * 1) we can have either the auth code in the query part of the
+-   *    URI, with which we'll obtain the token, or
+-   * 2) the access_token and other information directly in the
+-   *    fragment part of the URI.
++   * 1) we can either have the access_token and other information
++   *    directly in the fragment part of the URI, or
++   * 2) the auth code can be in the query part of the URI, with which
++   *    we'll obtain the token later.
+    */
+-  if (query != NULL)
+-    {
+-      GHashTable *key_value_pairs;
+-
+-      key_value_pairs = soup_form_decode (query);
+-
+-      priv->authorization_code = g_strdup (g_hash_table_lookup (key_value_pairs, "code"));
+-      if (priv->authorization_code != NULL)
+-        {
+-          response_id = GTK_RESPONSE_OK;
+-        }
+-      else
+-        {
+-          oauth2_error = (const gchar *) g_hash_table_lookup (key_value_pairs, "error");
+-          if (g_strcmp0 (oauth2_error, GOA_OAUTH2_ACCESS_DENIED) == 0)
+-            response_id = GTK_RESPONSE_CANCEL;
+-          else
+-            {
+-              g_set_error (&priv->error,
+-                           GOA_ERROR,
+-                           GOA_ERROR_NOT_AUTHORIZED,
+-                           _("Authorization response was \"%s\""),
+-                           oauth2_error);
+-              response_id = GTK_RESPONSE_CLOSE;
+-            }
+-        }
+-      g_hash_table_unref (key_value_pairs);
+-      goto ignore_request;
+-    }
+-  else if (fragment != NULL)
++  if (fragment != NULL)
+     {
+-      GHashTable *key_value_pairs;
+-
+       /* fragment is encoded into a key/value pairs for the token and
+        * expiration values, using the same syntax as a URL query */
+       key_value_pairs = soup_form_decode (fragment);
+@@ -960,31 +930,44 @@ on_web_view_navigation_policy_decision_requested (WebKitWebView             *web
+ 
+           response_id = GTK_RESPONSE_OK;
+         }
+-      else
+-        {
+-          oauth2_error = (const gchar *) g_hash_table_lookup (key_value_pairs, "error");
+-          if (g_strcmp0 (oauth2_error, GOA_OAUTH2_ACCESS_DENIED) == 0)
+-            response_id = GTK_RESPONSE_CANCEL;
+-          else
+-            {
+-              g_set_error (&priv->error,
+-                           GOA_ERROR,
+-                           GOA_ERROR_NOT_AUTHORIZED,
+-                           _("Authorization response was \"%s\""),
+-                           oauth2_error);
+-              response_id = GTK_RESPONSE_CLOSE;
+-            }
+-        }
+       g_hash_table_unref (key_value_pairs);
+-      goto ignore_request;
+     }
++
++  if (priv->access_token != NULL)
++    goto ignore_request;
++
++  if (query != NULL)
++    {
++      key_value_pairs = soup_form_decode (query);
++
++      priv->authorization_code = g_strdup (g_hash_table_lookup (key_value_pairs, "code"));
++      if (priv->authorization_code != NULL)
++        response_id = GTK_RESPONSE_OK;
++
++      g_hash_table_unref (key_value_pairs);
++    }
++
++  if (priv->authorization_code != NULL)
++    goto ignore_request;
++
++  /* In case we don't find the access_token or auth code, then look
++   * for the error in the query part of the URI.
++   */
++  key_value_pairs = soup_form_decode (query);
++  oauth2_error = (const gchar *) g_hash_table_lookup (key_value_pairs, "error");
++  if (g_strcmp0 (oauth2_error, GOA_OAUTH2_ACCESS_DENIED) == 0)
++    response_id = GTK_RESPONSE_CANCEL;
+   else
+     {
+-      /* this actually means that something unexpected happened, either we
+-       * did something wrong or the provider's flow changed */
+-      goa_debug ("URI format not recognized, DEFAULT BEHAVIOUR");
+-      goto default_behaviour;
++      g_set_error (&priv->error,
++                   GOA_ERROR,
++                   GOA_ERROR_NOT_AUTHORIZED,
++                   _("Authorization response was \"%s\""),
++                   oauth2_error);
++      response_id = GTK_RESPONSE_CLOSE;
+     }
++  g_hash_table_unref (key_value_pairs);
++  goto ignore_request;
+ 
+  ignore_request:
+   gtk_dialog_response (priv->dialog, response_id);
+-- 
+1.8.3.1
+
+
+From 333822428ac130905f67023cb30aab78169505d5 Mon Sep 17 00:00:00 2001
+From: Debarshi Ray <debarshir at gnome.org>
+Date: Fri, 18 Oct 2013 11:51:01 +0200
+Subject: [PATCH 4/4] facebook: Update README
+
+Fixes: https://bugzilla.gnome.org/710363
+---
+ README | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/README b/README
+index dae1257..f80a891 100644
+--- a/README
++++ b/README
+@@ -9,8 +9,11 @@ OAuth 2.0: https://developers.facebook.com/docs/authentication/
+ Scopes: https://developers.facebook.com/docs/authentication/permissions/
+ 
+ Notes:
+-The client-side flow returns the access_token and expires_in in the URI
+-fragment, and does not provide a refresh_token.
++The client-side flow returns the access_token and expires_in in the URI's
++fragment, and does not provide a refresh_token. However, if the user denied
++access then the error is returned in the URI's query. The URIs look like this:
++  - <get_redirect_uri>?#access_token=...
++  - <get_redirect_uri>?error=access_denied...#_=_
+ 
+ 
+ Flickr
+-- 
+1.8.3.1
+
diff --git a/gnome-online-accounts.spec b/gnome-online-accounts.spec
index d32cdbe..a19daf0 100644
--- a/gnome-online-accounts.spec
+++ b/gnome-online-accounts.spec
@@ -1,6 +1,6 @@
 Name:		gnome-online-accounts
 Version:	3.10.1
-Release:	1%{?dist}
+Release:	2%{?dist}
 Summary:	Single sign-on framework for GNOME
 
 Group:		System Environment/Libraries
@@ -8,6 +8,9 @@ License:	LGPLv2+
 URL:		https://live.gnome.org/GnomeOnlineAccounts
 Source0:	http://download.gnome.org/sources/gnome-online-accounts/3.10/%{name}-%{version}.tar.xz
 
+# https://bugzilla.gnome.org/show_bug.cgi?id=710363
+Patch0:		%{name}-facebook.patch
+
 BuildRequires:	gcr-devel
 BuildRequires:	glib2-devel >= 2.35
 BuildRequires:	gtk3-devel >= 3.5.1
@@ -43,6 +46,7 @@ developing applications that use %{name}.
 
 %prep
 %setup -q
+%patch0 -p1
 
 %build
 %configure \
@@ -113,6 +117,9 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :
 %{_libdir}/goa-1.0/include
 
 %changelog
+* Fri Oct 19 2013 Debarshi Ray <rishi at fedoraproject.org> - 3.10.1-2
+- Adapt to changes in the redirect URI used by Facebook (GNOME #710363)
+
 * Wed Oct 16 2013 Richard Hughes <rhughes at redhat.com> - 3.10.1-1
 - Update to 3.10.1
 


More information about the scm-commits mailing list