[libgdata/646285-oauth2] core: UNFINISHED work on OAuth2



commit 9e2ea12bafa7a48918873388a699cbde582cbc1b
Author: Philip Withnall <philip tecnocode co uk>
Date:   Sun Jul 3 21:37:49 2011 +0100

    core: UNFINISHED work on OAuth2

 Makefile.am                       |    5 +-
 docs/reference/gdata-docs.xml     |    1 +
 docs/reference/gdata-sections.txt |   32 +
 gdata/gdata-oauth2-authorizer.c   | 1176 +++++++++++++++++++++++++++++++++++++
 gdata/gdata-oauth2-authorizer.h   |  100 ++++
 gdata/gdata.h                     |    1 +
 gdata/gdata.symbols               |   16 +
 gdata/tests/Makefile.am           |    3 +
 gdata/tests/oauth2-authorizer.c   | 1048 +++++++++++++++++++++++++++++++++
 9 files changed, 2381 insertions(+), 1 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 9940f4a..e5c5d07 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -191,7 +191,9 @@ gdata_headers = \
        gdata/gdata-authorizer.h        \
        gdata/gdata-authorization-domain.h      \
        gdata/gdata-client-login-authorizer.h   \
-       gdata/gdata-oauth1-authorizer.h
+       gdata/gdata-oauth1-authorizer.h \
+       gdata/gdata-oauth2-authorizer.h \
+       $(NULL)
 
 if ENABLE_GOA
 gdata_headers += \
@@ -366,6 +368,7 @@ gdata_sources = \
        gdata/gdata-authorization-domain.c      \
        gdata/gdata-client-login-authorizer.c   \
        gdata/gdata-oauth1-authorizer.c         \
+       gdata/gdata-oauth2-authorizer.c         \
        \
        gdata/atom/gdata-author.c       \
        gdata/atom/gdata-category.c     \
diff --git a/docs/reference/gdata-docs.xml b/docs/reference/gdata-docs.xml
index d3b4366..65c77fd 100644
--- a/docs/reference/gdata-docs.xml
+++ b/docs/reference/gdata-docs.xml
@@ -56,6 +56,7 @@
                        <xi:include href="xml/gdata-client-login-authorizer.xml"/>
                        <xi:include href="xml/gdata-goa-authorizer.xml"/>
                        <xi:include href="xml/gdata-oauth1-authorizer.xml"/>
+                       <xi:include href="xml/gdata-oauth2-authorizer.xml"/>
                </chapter>
 
                <chapter>
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index fdcac2f..b7865cf 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -2756,3 +2756,35 @@ GDATA_TYPE_FREEBASE_SEARCH_RESULT
 <SUBSECTION Private>
 GDataFreebaseSearchResultPrivate
 </SECTION>
+
+<SECTION>
+<FILE>gdata-oauth2-authorizer</FILE>
+<TITLE>GDataOAuth2Authorizer</TITLE>
+GDataOAuth2Authorizer
+GDataOAuth2AuthorizerClass
+gdata_oauth2_authorizer_new
+gdata_oauth2_authorizer_new_for_authorization_domains
+gdata_oauth2_authorizer_build_authentication_uri
+gdata_oauth2_authorizer_request_authorization
+gdata_oauth2_authorizer_request_authorization_async
+gdata_oauth2_authorizer_request_authorization_finish
+gdata_oauth2_authorizer_get_client_id
+gdata_oauth2_authorizer_get_redirect_uri
+gdata_oauth2_authorizer_get_client_secret
+gdata_oauth2_authorizer_get_locale
+gdata_oauth2_authorizer_set_locale
+gdata_oauth2_authorizer_get_timeout
+gdata_oauth2_authorizer_set_timeout
+gdata_oauth2_authorizer_get_proxy_resolver
+gdata_oauth2_authorizer_set_proxy_resolver
+<SUBSECTION Standard>
+GDATA_TYPE_OAUTH2_AUTHORIZER
+GDATA_OAUTH2_AUTHORIZER
+GDATA_OAUTH2_AUTHORIZER_CLASS
+GDATA_IS_OAUTH2_AUTHORIZER
+GDATA_IS_OAUTH2_AUTHORIZER_CLASS
+GDATA_OAUTH2_AUTHORIZER_GET_CLASS
+gdata_oauth2_authorizer_get_type
+<SUBSECTION Private>
+GDataOAuth2AuthorizerPrivate
+</SECTION>
diff --git a/gdata/gdata-oauth2-authorizer.c b/gdata/gdata-oauth2-authorizer.c
new file mode 100644
index 0000000..27f7c3f
--- /dev/null
+++ b/gdata/gdata-oauth2-authorizer.c
@@ -0,0 +1,1176 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2011, 2014 <philip tecnocode co uk>
+ *
+ * GData Client is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gdata-oauth2-authorizer
+ * @short_description: GData OAuth 2.0 authorization interface
+ * @stability: Unstable
+ * @include: gdata/gdata-oauth2-authorizer.h
+ *
+ * TODO
+ * note we don't handle TTLs
+ *
+ * Since: UNRELEASED
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+
+#include "gdata-oauth2-authorizer.h"
+#include "gdata-private.h"
+
+/* TODO: Add OAuth 2.0 to the documentation at the head of gdata-authorizer.c, and write a new example for 
GDataService with multiple services with it */
+
+static void authorizer_init (GDataAuthorizerInterface *iface);
+static void dispose (GObject *object);
+static void finalize (GObject *object);
+static void get_property (GObject *object, guint property_id, GValue *value,
+                          GParamSpec *pspec);
+static void set_property (GObject *object, guint property_id,
+                          const GValue *value, GParamSpec *pspec);
+
+static void process_request (GDataAuthorizer *self,
+                             GDataAuthorizationDomain *domain,
+                             SoupMessage *message);
+static void sign_message_locked (GDataOAuth2Authorizer *self,
+                                 SoupMessage *message,
+                                 const gchar *access_token);
+static gboolean is_authorized_for_domain (GDataAuthorizer *self,
+                                          GDataAuthorizationDomain *domain);
+static gboolean refresh_authorization (GDataAuthorizer *self,
+                                       GCancellable *cancellable,
+                                       GError **error);
+
+static void parse_grant_response (GDataOAuth2Authorizer *self, guint status,
+                                  const gchar *reason_phrase,
+                                  const gchar *response_body, gssize length,
+                                  GError **error);
+
+static void notify_timeout_cb (GObject *gobject, GParamSpec *pspec,
+                               GObject *self);
+
+struct _GDataOAuth2AuthorizerPrivate {
+       SoupSession *session;  /* owned */
+       GProxyResolver *proxy_resolver;  /* owned */
+
+       gchar *client_id;  /* owned */
+       gchar *redirect_uri;  /* owned */
+       gchar *client_secret;  /* owned */
+       gchar *locale;  /* owned */
+
+       /* Mutex for access_token, refresh_token and authentication_domains. */
+       GMutex mutex;
+
+       /* These are both non-NULL when authorised, and both NULL otherwise. */
+       gchar *access_token;  /* owned */
+       gchar *refresh_token;  /* owned */
+
+       /* Mapping from GDataAuthorizationDomain to itself; a set of domains for
+        * which ->access_token is valid. */
+       GHashTable *authentication_domains;  /* owned */
+};
+
+enum {
+       PROP_CLIENT_ID = 1,
+       PROP_REDIRECT_URI,
+       PROP_CLIENT_SECRET,
+       PROP_LOCALE,
+       PROP_TIMEOUT,
+       PROP_PROXY_RESOLVER,
+};
+
+G_DEFINE_TYPE_WITH_CODE (GDataOAuth2Authorizer, gdata_oauth2_authorizer,
+                         G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GDATA_TYPE_AUTHORIZER,
+                                                authorizer_init))
+
+static void
+gdata_oauth2_authorizer_class_init (GDataOAuth2AuthorizerClass *klass)
+{
+       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+       g_type_class_add_private (klass, sizeof (GDataOAuth2AuthorizerPrivate));
+
+       gobject_class->get_property = get_property;
+       gobject_class->set_property = set_property;
+       gobject_class->dispose = dispose;
+       gobject_class->finalize = finalize;
+
+       /**
+        * GDataOAuth2Authorizer:client-id:
+        *
+        * A client ID for your application (see the
+        * <ulink url="http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html#Request"; 
type="http">reference documentation</ulink>).
+        *
+        * It is recommended that the ID is of the form
+        * <literal><replaceable>company name</replaceable>-
+        * <replaceable>application name</replaceable>-
+        * <replaceable>version ID</replaceable></literal>.
+        *
+        * Since: UNRELEASED
+        */
+       g_object_class_install_property (gobject_class, PROP_CLIENT_ID,
+                                        g_param_spec_string ("client-id",
+                                                             "Client ID",
+                                                             "A client ID for your application.",
+                                                             NULL,
+                                                             G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | 
G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GDataOAuth2Authorizer:redirect-uri:
+        *
+        * TODO.
+        *
+        * Since: UNRELEASED
+        */
+       g_object_class_install_property (gobject_class, PROP_REDIRECT_URI,
+                                        g_param_spec_string ("redirect-uri",
+                                                             "Redirect URI",
+                                                             "TODO",
+                                                             NULL,
+                                                             G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | 
G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GDataOAuth2Authorizer:client-secret:
+        *
+        * TODO
+        *
+        * Since: UNRELEASED
+        */
+       g_object_class_install_property (gobject_class, PROP_CLIENT_SECRET,
+                                        g_param_spec_string ("client-secret",
+                                                             "Client secret",
+                                                             "TODO",
+                                                             NULL,
+                                                             G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | 
G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GDataOAuth2Authorizer:locale:
+        *
+        * The locale to use for network requests, in UNIX locale format.
+        * (e.g. "en_GB", "cs", "de_DE".) Use %NULL for the default "C" locale
+        * (typically "en_US").
+        *
+        * This locale will be used by the server-side software to localise the
+        * authentication and authorization pages at the URI returned by
+        * gdata_oauth2_authorizer_build_authentication_uri().
+        *
+        * The server-side behaviour is undefined if it doesn't support a given
+        * locale.
+        *
+        * Since: UNRELEASED
+        */
+       g_object_class_install_property (gobject_class, PROP_LOCALE,
+                                        g_param_spec_string ("locale",
+                                                             "Locale",
+                                                             "The locale to use for network requests, in 
UNIX locale format.",
+                                                             NULL,
+                                                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GDataOAuth2Authorizer:timeout:
+        *
+        * A timeout, in seconds, for network operations. If the timeout is
+        * exceeded, the operation will be cancelled and
+        * %GDATA_SERVICE_ERROR_NETWORK_ERROR will be returned.
+        *
+        * If the timeout is <code class="literal">0</code>, operations will
+        * never time out.
+        *
+        * Since: UNRELEASED
+        */
+       g_object_class_install_property (gobject_class, PROP_TIMEOUT,
+                                        g_param_spec_uint ("timeout",
+                                                           "Timeout",
+                                                           "A timeout, in seconds, for network operations.",
+                                                           0, G_MAXUINT, 0,
+                                                           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+       /**
+        * GDataOAuth2Authorizer:proxy-resolver:
+        *
+        * The #GProxyResolver used to determine a proxy URI.
+        *
+        * Since: UNRELEASED
+        */
+       g_object_class_install_property (gobject_class, PROP_PROXY_RESOLVER,
+                                        g_param_spec_object ("proxy-resolver",
+                                                             "Proxy Resolver",
+                                                             "A GProxyResolver used to determine a proxy 
URI.",
+                                                             G_TYPE_PROXY_RESOLVER,
+                                                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+authorizer_init (GDataAuthorizerInterface *iface)
+{
+       iface->process_request = process_request;
+       iface->is_authorized_for_domain = is_authorized_for_domain;
+
+       /* We only implement the synchronous version, as GDataAuthorizer will
+        * automatically wrap it in a thread for the asynchronous versions if
+        * they’re not specifically implemented, which is fine for our needs. We
+        * couldn’t do any better by implementing the asynchronous versions
+        * ourselves. */
+       iface->refresh_authorization = refresh_authorization;
+}
+
+static void
+gdata_oauth2_authorizer_init (GDataOAuth2Authorizer *self)
+{
+       self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+                                                 GDATA_TYPE_OAUTH2_AUTHORIZER,
+                                                 GDataOAuth2AuthorizerPrivate);
+
+       /* Set up the authorizer's mutex */
+       g_mutex_init (&self->priv->mutex);
+       self->priv->authentication_domains = g_hash_table_new_full (g_direct_hash,
+                                                                   g_direct_equal,
+                                                                   g_object_unref,
+                                                                   NULL);
+
+       /* Set up the session */
+       self->priv->session = _gdata_service_build_session ();
+
+       /* Proxy the SoupSession’s timeout property. */
+       g_signal_connect (self->priv->session, "notify::timeout",
+                         (GCallback) notify_timeout_cb, self);
+
+       /* Keep our GProxyResolver synchronized with SoupSession’s. */
+       g_object_bind_property (self->priv->session, "proxy-resolver",
+                               self, "proxy-resolver",
+                               G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+}
+
+static void
+dispose (GObject *object)
+{
+       GDataOAuth2AuthorizerPrivate *priv;
+
+       priv = GDATA_OAUTH2_AUTHORIZER (object)->priv;
+
+       g_clear_object (&priv->session);
+       g_clear_object (&priv->proxy_resolver);
+
+       /* Chain up to the parent class */
+       G_OBJECT_CLASS (gdata_oauth2_authorizer_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+       GDataOAuth2AuthorizerPrivate *priv;
+
+       priv = GDATA_OAUTH2_AUTHORIZER (object)->priv;
+
+       g_free (priv->client_id);
+       g_free (priv->redirect_uri);
+       g_free (priv->client_secret);
+       g_free (priv->locale);
+
+       g_hash_table_unref (priv->authentication_domains);
+       g_mutex_clear (&priv->mutex);
+
+       /* Chain up to the parent class */
+       G_OBJECT_CLASS (gdata_oauth2_authorizer_parent_class)->finalize (object);
+}
+
+static void
+get_property (GObject *object, guint property_id, GValue *value,
+              GParamSpec *pspec)
+{
+       GDataOAuth2Authorizer *self;
+       GDataOAuth2AuthorizerPrivate *priv;
+
+       self = GDATA_OAUTH2_AUTHORIZER (object);
+       priv = self->priv;
+
+       switch (property_id) {
+       case PROP_CLIENT_ID:
+               g_value_set_string (value, priv->client_id);
+               break;
+       case PROP_REDIRECT_URI:
+               g_value_set_string (value, priv->redirect_uri);
+               break;
+       case PROP_CLIENT_SECRET:
+               g_value_set_string (value, priv->client_secret);
+               break;
+       case PROP_LOCALE:
+               g_value_set_string (value, priv->locale);
+               break;
+       case PROP_TIMEOUT:
+               g_value_set_uint (value,
+                                 gdata_oauth2_authorizer_get_timeout (self));
+               break;
+       case PROP_PROXY_RESOLVER:
+               g_value_set_object (value,
+                                   gdata_oauth2_authorizer_get_proxy_resolver (self));
+               break;
+       default:
+               /* We don't have any other property... */
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+               break;
+       }
+}
+
+static void
+set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+       GDataOAuth2Authorizer *self;
+       GDataOAuth2AuthorizerPrivate *priv;
+
+       self = GDATA_OAUTH2_AUTHORIZER (object);
+       priv = self->priv;
+
+       switch (property_id) {
+       /* Construct only. */
+       case PROP_CLIENT_ID:
+               priv->client_id = g_value_dup_string (value);
+               break;
+       /* Construct only. */
+       case PROP_REDIRECT_URI:
+               priv->redirect_uri = g_value_dup_string (value);
+               break;
+       /* Construct only. */
+       case PROP_CLIENT_SECRET:
+               priv->client_secret = g_value_dup_string (value);
+               break;
+       case PROP_LOCALE:
+               gdata_oauth2_authorizer_set_locale (self,
+                                                   g_value_get_string (value));
+               break;
+       case PROP_TIMEOUT:
+               gdata_oauth2_authorizer_set_timeout (self,
+                                                    g_value_get_uint (value));
+               break;
+       case PROP_PROXY_RESOLVER:
+               gdata_oauth2_authorizer_set_proxy_resolver (self,
+                                                           g_value_get_object (value));
+               break;
+       default:
+               /* We don't have any other property... */
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+               break;
+       }
+}
+
+static void
+process_request (GDataAuthorizer *self, GDataAuthorizationDomain *domain,
+                 SoupMessage *message)
+{
+       GDataOAuth2AuthorizerPrivate *priv;
+
+       priv = GDATA_OAUTH2_AUTHORIZER (self)->priv;
+
+       /* Set the authorisation header */
+       g_mutex_lock (&priv->mutex);
+
+       /* Sanity check */
+       g_assert ((priv->access_token == NULL) ==
+                 (priv->refresh_token == NULL));
+
+       if (priv->access_token != NULL &&
+           g_hash_table_lookup (priv->authentication_domains,
+                                domain) != NULL) {
+               sign_message_locked (GDATA_OAUTH2_AUTHORIZER (self), message,
+                                    priv->access_token);
+       }
+
+       g_mutex_unlock (&priv->mutex);
+}
+
+static gboolean
+is_authorized_for_domain (GDataAuthorizer *self,
+                          GDataAuthorizationDomain *domain)
+{
+       GDataOAuth2AuthorizerPrivate *priv;
+       gpointer result;
+       const gchar *access_token;
+
+       priv = GDATA_OAUTH2_AUTHORIZER (self)->priv;
+
+       g_mutex_lock (&priv->mutex);
+       access_token = priv->access_token;
+       result = g_hash_table_lookup (priv->authentication_domains, domain);
+       g_mutex_unlock (&priv->mutex);
+
+       /* Sanity check */
+       g_assert (result == NULL || result == domain);
+
+       return (access_token != NULL && result != NULL);
+}
+
+/* Sign the message and add the Authorization header to it containing the
+ * signature.
+ *
+ * Reference: https://developers.google.com/accounts/docs/OAuth2InstalledApp#callinganapi
+ *
+ * NOTE: This must be called with the mutex locked. */
+static void
+sign_message_locked (GDataOAuth2Authorizer *self, SoupMessage *message,
+                     const gchar *access_token)
+{
+       SoupURI *message_uri;  /* unowned */
+       gchar *auth_header = NULL;  /* owned */
+
+       g_return_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self));
+       g_return_if_fail (SOUP_IS_MESSAGE (message));
+       g_return_if_fail (access_token != NULL && *access_token != '\0');
+
+       /* Ensure that we’re using HTTPS: if not, we shouldn’t set the
+        * Authorization header or we could be revealing the access
+        * token to anyone snooping the connection, which would give
+        * them the same rights as us on the user’s data. Generally a
+        * bad thing to happen. */
+       message_uri = soup_message_get_uri (message);
+
+       if (message_uri->scheme != SOUP_URI_SCHEME_HTTPS) {
+               g_warning ("Not authorizing a non-HTTPS message with the "
+                          "user’s OAuth 2.0 access token as the connection "
+                          "isn’t secure.");
+               return;
+       }
+
+       /* Add the authorisation header. */
+       auth_header = g_strdup_printf ("Bearer %s", access_token);
+       soup_message_headers_append (message->request_headers,
+                                    "Authorization", auth_header);
+       g_free (auth_header);
+}
+
+/* TODO: Link to the OAuth docs more */
+
+static gboolean
+refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable,
+                       GError **error)
+{
+       /* See http://code.google.com/apis/accounts/docs/OAuth2.html#IAMoreToken */
+       GDataOAuth2AuthorizerPrivate *priv;
+       SoupMessage *message;
+       gchar *request_body;
+       guint status;
+       GError *child_error = NULL;
+
+       g_return_val_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self), FALSE);
+
+       priv = GDATA_OAUTH2_AUTHORIZER (self)->priv;
+
+       g_mutex_lock (&priv->mutex);
+
+       /* If we don’t have a refresh token, we can’t refresh the
+        * authorisation */
+       if (priv->refresh_token == NULL) {
+               g_mutex_unlock (&priv->mutex);
+               return FALSE;
+       }
+
+       /* Prepare the request */
+       request_body = soup_form_encode ("client_id", priv->client_id,
+                                        "client_secret", priv->client_secret,
+                                        "refresh_token", priv->refresh_token,
+                                        "grant_type", "refresh_token",
+                                        NULL);
+
+       g_mutex_unlock (&priv->mutex);
+
+       /* Build the message */
+       message = soup_message_new (SOUP_METHOD_POST,
+                                   "https://accounts.google.com/o/oauth2/token";);
+       soup_message_set_request (message, "application/x-www-form-urlencoded",
+                                 SOUP_MEMORY_TAKE, request_body,
+                                 strlen (request_body));
+
+       /* Send the message */
+       _gdata_service_actually_send_message (priv->session, message,
+                                             cancellable, error);
+       status = message->status_code;
+
+       if (status == SOUP_STATUS_CANCELLED) {
+               /* Cancelled (the error has already been set) */
+               g_object_unref (message);
+               return FALSE;
+       } else if (status != SOUP_STATUS_OK) {
+               /* TODO */
+               g_object_unref (message);
+
+               return FALSE;
+       }
+
+       g_assert (message->response_body->data != NULL);
+
+       /* Parse and handle the response */
+       parse_grant_response (GDATA_OAUTH2_AUTHORIZER (self),
+                             status, message->reason_phrase,
+                             message->response_body->data,
+                             message->response_body->length, &child_error);
+
+       g_object_unref (message);
+
+       if (child_error != NULL) {
+               g_propagate_error (error, child_error);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+/**
+ * gdata_oauth2_authorizer_new:
+ * @client_id: your application’s client ID
+ * @redirect_uri: TODO
+ * @client_secret: TODO
+ * @service_type: TODO
+ *
+ * Creates a new #GDataOAuth2Authorizer. The @client_id must be unique for your
+ * application, and as registered with Google.
+ *
+ * Return value: (transfer full): a new #GDataOAuth2Authorizer; unref with
+ * g_object_unref()
+ *
+ * Since: UNRELEASED
+ */
+GDataOAuth2Authorizer *
+gdata_oauth2_authorizer_new (const gchar *client_id, const gchar *redirect_uri,
+                             const gchar *client_secret, GType service_type)
+{
+       g_return_val_if_fail (client_id != NULL && *client_id != '\0', NULL);
+       g_return_val_if_fail (redirect_uri != NULL && *redirect_uri != '\0',
+                             NULL);
+       g_return_val_if_fail (client_secret != NULL && *client_secret != '\0',
+                             NULL);
+       g_return_val_if_fail (g_type_is_a (service_type, GDATA_TYPE_SERVICE),
+                             NULL);
+
+       return gdata_oauth2_authorizer_new_for_authorization_domains (client_id,
+                                                                     redirect_uri,
+                                                                     client_secret,
+                                                                     gdata_service_get_authorization_domains 
(service_type));
+}
+
+/**
+ * gdata_oauth2_authorizer_new_for_authorization_domains:
+ * @client_id: your application’s client ID
+ * @redirect_uri: TODO
+ * @client_secret: TODO
+ * @authorization_domains: (element-type GDataAuthorizationDomain): TODO
+ *
+ * Creates a new #GDataOAuth2Authorizer. The @client_id must be unique for your
+ * application, and as registered with Google.
+ *
+ * Return value: (transfer full): a new #GDataOAuth2Authorizer; unref with
+ * g_object_unref()
+ *
+ * Since: UNRELEASED
+ */
+GDataOAuth2Authorizer *
+gdata_oauth2_authorizer_new_for_authorization_domains (const gchar *client_id,
+                                                       const gchar *redirect_uri,
+                                                       const gchar *client_secret,
+                                                       GList *authorization_domains)
+{
+       GList *i;
+       GDataOAuth2Authorizer *authorizer;
+
+       g_return_val_if_fail (client_id != NULL && *client_id != '\0', NULL);
+       g_return_val_if_fail (redirect_uri != NULL && *redirect_uri != '\0',
+                             NULL);
+       g_return_val_if_fail (client_secret != NULL && *client_secret != '\0',
+                             NULL);
+       g_return_val_if_fail (authorization_domains != NULL, NULL);
+
+       authorizer = GDATA_OAUTH2_AUTHORIZER (g_object_new (GDATA_TYPE_OAUTH2_AUTHORIZER,
+                                                           "client-id", client_id,
+                                                           "redirect-uri", redirect_uri,
+                                                           "client-secret", client_secret,
+                                                           NULL));
+
+       /* Register all the domains with the authorizer */
+       for (i = authorization_domains; i != NULL; i = i->next) {
+               GDataAuthorizationDomain *domain;  /* unowned */
+
+               g_return_val_if_fail (GDATA_IS_AUTHORIZATION_DOMAIN (i->data),
+                                     NULL);
+
+               /* We don’t have to lock the authoriser’s mutex here as no other
+                * code has seen the authoriser yet */
+               domain = GDATA_AUTHORIZATION_DOMAIN (i->data);
+               g_hash_table_insert (authorizer->priv->authentication_domains,
+                                    g_object_ref (domain), domain);
+       }
+
+       return authorizer;
+}
+
+/**
+ * gdata_oauth2_authorizer_build_authentication_uri:
+ * @self: a #GDataOAuth2Authorizer
+ * @login_hint: (nullable): TODO
+ * @include_granted_scopes: TODO; this won't actually work because scopes are
+ * defined at construction time
+ *
+ * TODO
+ *
+ * Return value: the authentication URI to open in a web browser
+ *
+ * Since: UNRELEASED
+ */
+const gchar *
+gdata_oauth2_authorizer_build_authentication_uri (GDataOAuth2Authorizer *self,
+                                                  const gchar *login_hint,
+                                                  gboolean include_granted_scopes)
+{
+       GDataOAuth2AuthorizerPrivate *priv;
+       GString *uri = NULL;  /* owned */
+       GDataAuthorizationDomain *domain;  /* unowned */
+       GHashTableIter iter;
+       gboolean is_first = TRUE;
+
+       g_return_val_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self), NULL);
+
+       priv = self->priv;
+
+       g_mutex_lock (&priv->mutex);
+
+       /* Build and memoise the URI.
+        *
+        * Reference: https://developers.google.com/accounts/docs/OAuth2InstalledApp#formingtheurl
+        */
+       g_assert (g_hash_table_size (priv->authentication_domains) > 0);
+
+       uri = g_string_new ("https://accounts.google.com/o/oauth2/auth";
+                           "?response_type=code"
+                           "&client_id=");
+       g_string_append_uri_escaped (uri, priv->client_id, NULL, TRUE);
+       g_string_append (uri, "&redirect_uri=");
+       g_string_append_uri_escaped (uri, priv->redirect_uri, NULL, TRUE);
+       g_string_append (uri, "&scope=");
+
+       /* Add the scopes of all our domains */
+       g_hash_table_iter_init (&iter, priv->authentication_domains);
+
+       while (g_hash_table_iter_next (&iter, (gpointer *) &domain, NULL)) {
+               const gchar *scope;
+
+               if (!is_first) {
+                       /* Delimiter */
+                       g_string_append (uri, "%20");
+               }
+
+               scope = gdata_authorization_domain_get_scope (domain);
+               g_string_append_uri_escaped (uri, scope, NULL, TRUE);
+
+               is_first = FALSE;
+       }
+
+       if (login_hint != NULL && *login_hint != '\0') {
+               g_string_append (uri, "&login_hint=");
+               g_string_append_uri_escaped (uri, login_hint, NULL, TRUE);
+       }
+
+       if (priv->locale != NULL) {
+               g_string_append (uri, "&hl=");
+               g_string_append_uri_escaped (uri, priv->locale, NULL, TRUE);
+       }
+
+       if (include_granted_scopes) {
+               g_string_append (uri, "&include_granted_scopes=true");
+       } else {
+               g_string_append (uri, "&include_granted_scopes=false");
+       }
+
+       g_mutex_unlock (&priv->mutex);
+
+       return g_string_free (uri, FALSE);
+}
+
+/* NOTE: This has to be thread safe, as it can be called from
+ * refresh_authorization() at any time.
+ *
+ * Reference: https://developers.google.com/accounts/docs/OAuth2InstalledApp#handlingtheresponse
+ */
+static void
+parse_grant_response (GDataOAuth2Authorizer *self, guint status,
+                      const gchar *reason_phrase, const gchar *response_body,
+                      gssize length, GError **error)
+{
+       GDataOAuth2AuthorizerPrivate *priv;
+       JsonParser *parser = NULL;  /* owned */
+       JsonNode *root_node;  /* unowned */
+       JsonObject *root_object;  /* unowned */
+       const gchar *access_token = NULL, *refresh_token = NULL;
+       GError *child_error = NULL;
+
+       priv = self->priv;
+
+       /* Parse the successful response */
+       parser = json_parser_new ();
+
+       json_parser_load_from_data (parser, response_body, length,
+                                   &child_error);
+
+       if (child_error != NULL) {
+               /* TODO: Convert JSON error to libgdata error */
+               goto done;
+       }
+
+       /* Extract the access token, TTL and refresh token */
+       root_node = json_parser_get_root (parser);
+
+       if (JSON_NODE_HOLDS_OBJECT (root_node) == FALSE) {
+               /* TODO: Error */
+               goto done;
+       }
+
+       root_object = json_node_get_object (root_node);
+
+       access_token = json_object_get_string_member (root_object,
+                                                     "access_token");
+       refresh_token = json_object_get_string_member (root_object,
+                                                      "refresh_token");
+
+       /* Always require an access token. */
+       if (access_token == NULL || *access_token == '\0') {
+               /* TODO: error */
+               access_token = NULL;
+               refresh_token = NULL;
+
+               goto done;
+       }
+
+       /* Only require a refresh token if this is the first authentication.
+        * See the documentation for refreshing authentication:
+        * https://developers.google.com/accounts/docs/OAuth2InstalledApp#refresh
+        */
+       if ((refresh_token == NULL || *refresh_token == '\0') &&
+           priv->refresh_token == NULL) {
+               /* TODO: error */
+               access_token = NULL;
+               refresh_token = NULL;
+
+               goto done;
+       }
+
+done:
+       /* Postconditions. */
+       g_assert ((access_token == NULL) == (refresh_token == NULL));
+       g_assert ((child_error != NULL) == (access_token == NULL));
+
+       /* Update state. */
+       g_mutex_lock (&priv->mutex);
+
+       g_free (priv->access_token);
+       priv->access_token = g_strdup (access_token);
+
+       g_free (priv->refresh_token);
+       priv->refresh_token = g_strdup (refresh_token);
+
+       g_mutex_unlock (&priv->mutex);
+
+       if (child_error != NULL) {
+               g_propagate_error (error, child_error);
+       }
+
+       g_object_unref (parser);
+}
+
+/**
+ * gdata_oauth2_authorizer_request_authorization:
+ * @self: a #GDataOAuth2Authorizer
+ * @authorization_code: TODO
+ * @cancellable: (allow-none): TODO
+ * @error: TODO
+ *
+ * TODO
+ *
+ * Return value: TODO
+ *
+ * Since: UNRELEASED
+ */
+gboolean
+gdata_oauth2_authorizer_request_authorization (GDataOAuth2Authorizer *self,
+                                               const gchar *authorization_code,
+                                               GCancellable *cancellable,
+                                               GError **error)
+{
+       GDataOAuth2AuthorizerPrivate *priv;
+       SoupMessage *message = NULL;  /* owned */
+       gchar *request_body = NULL;  /* owned */
+       guint status;
+       GError *child_error = NULL;
+
+       g_return_val_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self), FALSE);
+       g_return_val_if_fail (authorization_code != NULL &&
+                             *authorization_code != '\0', FALSE);
+       g_return_val_if_fail (cancellable == NULL ||
+                             G_IS_CANCELLABLE (cancellable), FALSE);
+       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+       priv = self->priv;
+
+       /* Prepare the request.
+        *
+        * Reference: https://developers.google.com/accounts/docs/OAuth2InstalledApp#handlingtheresponse
+        */
+       request_body = soup_form_encode ("client_id", priv->client_id,
+                                        "client_secret", priv->client_secret,
+                                        "code", authorization_code,
+                                        "redirect_uri", priv->redirect_uri,
+                                        "grant_type", "authorization_code",
+                                        NULL);
+
+       /* Build the message */
+       message = soup_message_new (SOUP_METHOD_POST,
+                                   "https://accounts.google.com/o/oauth2/token";);
+       soup_message_set_request (message, "application/x-www-form-urlencoded",
+                                 SOUP_MEMORY_TAKE, request_body,
+                                 strlen (request_body));
+       request_body = NULL;
+
+       /* Send the message */
+       _gdata_service_actually_send_message (priv->session, message,
+                                             cancellable, error);
+       status = message->status_code;
+
+       if (status == SOUP_STATUS_CANCELLED) {
+               /* Cancelled (the error has already been set) */
+               g_object_unref (message);
+               return FALSE;
+       } else if (status != SOUP_STATUS_OK) {
+               /* TODO */
+               g_object_unref (message);
+
+               return FALSE;
+       }
+
+       g_assert (message->response_body->data != NULL);
+
+       /* Parse and handle the response */
+       parse_grant_response (self, status, message->reason_phrase,
+                             message->response_body->data,
+                             message->response_body->length, &child_error);
+
+       g_object_unref (message);
+
+       if (child_error != NULL) {
+               g_propagate_error (error, child_error);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static void
+request_authorization_thread (GSimpleAsyncResult *result,
+                              GDataOAuth2Authorizer *authorizer,
+                              GCancellable *cancellable)
+{
+       GError *error = NULL;
+       const gchar *authorization_code;
+
+       authorization_code = g_simple_async_result_get_op_res_gpointer (result);
+
+       if (gdata_oauth2_authorizer_request_authorization (authorizer,
+                                                          authorization_code,
+                                                          cancellable,
+                                                          &error) == FALSE) {
+               g_simple_async_result_set_from_error (result, error);
+               g_error_free (error);
+       }
+}
+
+/**
+ * gdata_oauth2_authorizer_request_authorization_async:
+ * @self: a #GDataOAuth2Authorizer
+ * @authorization_code: TODO
+ * @cancellable: (allow-none): an optional #GCancellable, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when authorization is finished
+ * @user_data: (closure): data to pass to the @callback function
+ *
+ * TODO
+ *
+ * Since: UNRELEASED
+ */
+void
+gdata_oauth2_authorizer_request_authorization_async (GDataOAuth2Authorizer *self,
+                                                     const gchar *authorization_code,
+                                                     GCancellable *cancellable,
+                                                     GAsyncReadyCallback callback,
+                                                     gpointer user_data)
+{
+       GSimpleAsyncResult *result;
+
+       g_return_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self));
+       g_return_if_fail (authorization_code != NULL &&
+                         *authorization_code != '\0');
+       g_return_if_fail (cancellable == NULL ||
+                         G_IS_CANCELLABLE (cancellable));
+
+       result = g_simple_async_result_new (G_OBJECT (self), callback,
+                                           user_data,
+                                           gdata_oauth2_authorizer_request_authorization_async);
+       g_simple_async_result_set_op_res_gpointer (result,
+                                                  g_strdup (authorization_code),
+                                                  (GDestroyNotify) g_free);
+       g_simple_async_result_run_in_thread (result,
+                                            (GSimpleAsyncThreadFunc) request_authorization_thread,
+                                            G_PRIORITY_DEFAULT, cancellable);
+       g_object_unref (result);
+}
+
+/**
+ * gdata_oauth2_authorizer_request_authorization_finish:
+ * @self: a #GDataOAuth2Authorizer
+ * @async_result: a #GAsyncResult
+ * @error: a #GError, or %NULL
+ *
+ * Finishes an asynchronous authorization operation started with
+ * gdata_oauth2_authorizer_request_authorization_async().
+ *
+ * Return value: %TRUE if authorization was successful, %FALSE otherwise
+ *
+ * Since: UNRELEASED
+ */
+gboolean
+gdata_oauth2_authorizer_request_authorization_finish (GDataOAuth2Authorizer *self,
+                                                      GAsyncResult *async_result,
+                                                      GError **error)
+{
+       GSimpleAsyncResult *result;
+
+       g_return_val_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self), FALSE);
+       g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), FALSE);
+       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+       result = G_SIMPLE_ASYNC_RESULT (async_result);
+
+       g_warn_if_fail (g_simple_async_result_is_valid (async_result,
+                                                       G_OBJECT (self),
+                                                       gdata_oauth2_authorizer_request_authorization_async));
+
+       if (g_simple_async_result_propagate_error (result, error)) {
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+/**
+ * gdata_oauth2_authorizer_get_client_id:
+ * @self: a #GDataOAuth2Authorizer
+ *
+ * Returns the authorizer's client ID, #GDataOAuth2Authorizer:client-id, as
+ * specified on constructing the #GDataOAuth2Authorizer.
+ *
+ * Return value: the authorizer's client ID
+ *
+ * Since: UNRELEASED
+ */
+const gchar *
+gdata_oauth2_authorizer_get_client_id (GDataOAuth2Authorizer *self)
+{
+       g_return_val_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self), NULL);
+       return self->priv->client_id;
+}
+
+/**
+ * gdata_oauth2_authorizer_get_redirect_uri:
+ * @self: a #GDataOAuth2Authorizer
+ *
+ * Returns the authorizer’s redirect URI, #GDataOAuth2Authorizer:redirect-uri,
+ * as specified on constructing the #GDataOAuth2Authorizer.
+ *
+ * Return value: the authorizer’s redirect URI
+ *
+ * Since: UNRELEASED
+ */
+const gchar *
+gdata_oauth2_authorizer_get_redirect_uri (GDataOAuth2Authorizer *self)
+{
+       g_return_val_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self), NULL);
+       return self->priv->redirect_uri;
+}
+
+/**
+ * gdata_oauth2_authorizer_get_client_secret:
+ * @self: a #GDataOAuth2Authorizer
+ *
+ * Returns the authorizer's client secret, #GDataOAuth2Authorizer:client-secret,
+ * as specified on constructing the #GDataOAuth2Authorizer.
+ *
+ * Return value: the authorizer's client secret
+ *
+ * Since: UNRELEASED
+ */
+const gchar *
+gdata_oauth2_authorizer_get_client_secret (GDataOAuth2Authorizer *self)
+{
+       g_return_val_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self), NULL);
+       return self->priv->client_secret;
+}
+
+/**
+ * gdata_oauth2_authorizer_get_locale:
+ * @self: a #GDataOAuth2Authorizer
+ *
+ * Returns the locale currently being used for network requests, or %NULL if the
+ * locale is the default.
+ *
+ * Return value: (allow-none): the current locale
+ *
+ * Since: UNRELEASED
+ */
+const gchar *
+gdata_oauth2_authorizer_get_locale (GDataOAuth2Authorizer *self)
+{
+       g_return_val_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self), NULL);
+       return self->priv->locale;
+}
+
+/**
+ * gdata_oauth2_authorizer_set_locale:
+ * @self: a #GDataOAuth2Authorizer
+ * @locale: (allow-none): the new locale in UNIX locale format, or %NULL for the
+ * default locale
+ *
+ * Set the locale used for network requests to @locale, given in standard UNIX
+ * locale format. See #GDataOAuth2Authorizer:locale for more details.
+ *
+ * Note that while it’s possible to change the locale after sending network
+ * requests (i.e. calling gdata_oauth2_authorizer_build_authentication_uri() for
+ * the first time), it is unsupported, as the server-side software may behave
+ * unexpectedly. The only supported use of this method is after creation of the
+ * authorizer, but before any network requests are made.
+ *
+ * Since: UNRELEASED
+ */
+void
+gdata_oauth2_authorizer_set_locale (GDataOAuth2Authorizer *self,
+                                    const gchar *locale)
+{
+       g_return_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self));
+
+       if (g_strcmp0 (locale, self->priv->locale) == 0) {
+               /* Already has this value */
+               return;
+       }
+
+       g_free (self->priv->locale);
+       self->priv->locale = g_strdup (locale);
+       g_object_notify (G_OBJECT (self), "locale");
+}
+
+static void
+notify_timeout_cb (GObject *gobject, GParamSpec *pspec, GObject *self)
+{
+       g_object_notify (self, "timeout");
+}
+
+/**
+ * gdata_oauth2_authorizer_get_timeout:
+ * @self: a #GDataOAuth2Authorizer
+ *
+ * Gets the #GDataOAuth2Authorizer:timeout property; the network timeout, in
+ * seconds.
+ *
+ * Return value: the timeout, or <code class="literal">0</code>
+ *
+ * Since: UNRELEASED
+ */
+guint
+gdata_oauth2_authorizer_get_timeout (GDataOAuth2Authorizer *self)
+{
+       guint timeout;
+
+       g_return_val_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self), 0);
+
+       g_object_get (self->priv->session,
+                     SOUP_SESSION_TIMEOUT, &timeout,
+                     NULL);
+
+       return timeout;
+}
+
+/**
+ * gdata_oauth2_authorizer_set_timeout:
+ * @self: a #GDataOAuth2Authorizer
+ * @timeout: the timeout, or <code class="literal">0</code>
+ *
+ * Sets the #GDataOAuth2Authorizer:timeout property; the network timeout, in
+ * seconds.
+ *
+ * If @timeout is <code class="literal">0</code>, network operations will never
+ * time out.
+ *
+ * Since: UNRELEASED
+ */
+void
+gdata_oauth2_authorizer_set_timeout (GDataOAuth2Authorizer *self, guint timeout)
+{
+       g_return_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self));
+       g_object_set (self->priv->session, SOUP_SESSION_TIMEOUT, timeout, NULL);
+       g_object_notify (G_OBJECT (self), "timeout");
+}
+
+/**
+ * gdata_oauth2_authorizer_get_proxy_resolver:
+ * @self: a #GDataOAuth2Authorizer
+ *
+ * Gets the #GProxyResolver on the #GDataOAuth2Authorizer's #SoupSession.
+ *
+ * Return value: (transfer none) (allow-none): a #GProxyResolver, or %NULL
+ *
+ * Since: UNRELEASED
+ */
+GProxyResolver *
+gdata_oauth2_authorizer_get_proxy_resolver (GDataOAuth2Authorizer *self)
+{
+       g_return_val_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self), NULL);
+
+       return self->priv->proxy_resolver;
+}
+
+/**
+ * gdata_oauth2_authorizer_set_proxy_resolver:
+ * @self: a #GDataOAuth2Authorizer
+ * @proxy_resolver: (allow-none): a #GProxyResolver, or %NULL
+ *
+ * Sets the #GProxyResolver on the #SoupSession used internally by the given
+ * #GDataOAuth2Authorizer.
+ *
+ * Since: UNRELEASED
+ */
+void
+gdata_oauth2_authorizer_set_proxy_resolver (GDataOAuth2Authorizer *self,
+                                            GProxyResolver *proxy_resolver)
+{
+       g_return_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self));
+       g_return_if_fail (proxy_resolver == NULL ||
+                         G_IS_PROXY_RESOLVER (proxy_resolver));
+
+       if (proxy_resolver != NULL) {
+               g_object_ref (proxy_resolver);
+       }
+
+       g_clear_object (&self->priv->proxy_resolver);
+       self->priv->proxy_resolver = proxy_resolver;
+
+       g_object_notify (G_OBJECT (self), "proxy-resolver");
+}
diff --git a/gdata/gdata-oauth2-authorizer.h b/gdata/gdata-oauth2-authorizer.h
new file mode 100644
index 0000000..bd96f4a
--- /dev/null
+++ b/gdata/gdata-oauth2-authorizer.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2011, 2014 <philip tecnocode co uk>
+ *
+ * GData Client is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GDATA_OAUTH2_AUTHORIZER_H
+#define GDATA_OAUTH2_AUTHORIZER_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "gdata-authorizer.h"
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_OAUTH2_AUTHORIZER           (gdata_oauth2_authorizer_get_type ())
+#define GDATA_OAUTH2_AUTHORIZER(o)             (G_TYPE_CHECK_INSTANCE_CAST ((o), 
GDATA_TYPE_OAUTH2_AUTHORIZER, GDataOAuth2Authorizer))
+#define GDATA_OAUTH2_AUTHORIZER_CLASS(k)       (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_OAUTH2_AUTHORIZER, 
GDataOAuth2AuthorizerClass))
+#define GDATA_IS_OAUTH2_AUTHORIZER(o)          (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
GDATA_TYPE_OAUTH2_AUTHORIZER))
+#define GDATA_IS_OAUTH2_AUTHORIZER_CLASS(k)    (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_OAUTH2_AUTHORIZER))
+#define GDATA_OAUTH2_AUTHORIZER_GET_CLASS(o)   (G_TYPE_INSTANCE_GET_CLASS ((o), 
GDATA_TYPE_OAUTH2_AUTHORIZER, GDataOAuth2AuthorizerClass))
+
+typedef struct _GDataOAuth2AuthorizerPrivate   GDataOAuth2AuthorizerPrivate;
+
+/**
+ * GDataOAuth2Authorizer:
+ *
+ * All the fields in the #GDataOAuth2Authorizer structure are private and should never be accessed directly.
+ *
+ * Since: UNRELEASED
+ */
+typedef struct {
+       /*< private >*/
+       GObject parent;
+       GDataOAuth2AuthorizerPrivate *priv;
+} GDataOAuth2Authorizer;
+
+/**
+ * GDataOAuth2AuthorizerClass:
+ *
+ * All the fields in the #GDataOAuth2AuthorizerClass structure are private and should never be accessed 
directly.
+ *
+ * Since: UNRELEASED
+ */
+typedef struct {
+       /*< private >*/
+       GObjectClass parent;
+} GDataOAuth2AuthorizerClass;
+
+GType gdata_oauth2_authorizer_get_type (void) G_GNUC_CONST;
+
+GDataOAuth2Authorizer *gdata_oauth2_authorizer_new (const gchar *client_id,
+                                                    const gchar *redirect_uri,
+                                                    const gchar *client_secret,
+                                                    GType service_type) G_GNUC_WARN_UNUSED_RESULT 
G_GNUC_MALLOC;
+GDataOAuth2Authorizer *gdata_oauth2_authorizer_new_for_authorization_domains (const gchar *client_id,
+                                                                              const gchar *redirect_uri,
+                                                                              const gchar *client_secret,
+                                                                              GList *authorization_domains) 
G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+
+const gchar *gdata_oauth2_authorizer_build_authentication_uri (GDataOAuth2Authorizer *self,
+                                                               const gchar *login_hint,
+                                                               gboolean include_granted_scopes) G_GNUC_PURE;
+
+gboolean gdata_oauth2_authorizer_request_authorization (GDataOAuth2Authorizer *self, const gchar 
*authorization_code,
+                                                        GCancellable *cancellable, GError **error);
+void gdata_oauth2_authorizer_request_authorization_async (GDataOAuth2Authorizer *self, const gchar 
*authorization_code,
+                                                          GCancellable *cancellable, GAsyncReadyCallback 
callback, gpointer user_data);
+gboolean gdata_oauth2_authorizer_request_authorization_finish (GDataOAuth2Authorizer *self, GAsyncResult 
*async_result, GError **error);
+
+const gchar *gdata_oauth2_authorizer_get_client_id (GDataOAuth2Authorizer *self) G_GNUC_PURE;
+const gchar *gdata_oauth2_authorizer_get_redirect_uri (GDataOAuth2Authorizer *self) G_GNUC_PURE;
+const gchar *gdata_oauth2_authorizer_get_client_secret (GDataOAuth2Authorizer *self) G_GNUC_PURE;
+
+const gchar *gdata_oauth2_authorizer_get_locale (GDataOAuth2Authorizer *self) G_GNUC_PURE;
+void gdata_oauth2_authorizer_set_locale (GDataOAuth2Authorizer *self, const gchar *locale);
+
+guint gdata_oauth2_authorizer_get_timeout (GDataOAuth2Authorizer *self) G_GNUC_PURE;
+void gdata_oauth2_authorizer_set_timeout (GDataOAuth2Authorizer *self, guint timeout);
+
+GProxyResolver *gdata_oauth2_authorizer_get_proxy_resolver (GDataOAuth2Authorizer *self) G_GNUC_PURE;
+void gdata_oauth2_authorizer_set_proxy_resolver (GDataOAuth2Authorizer *self, GProxyResolver 
*proxy_resolver);
+
+G_END_DECLS
+
+#endif /* !GDATA_OAUTH2_AUTHORIZER_H */
diff --git a/gdata/gdata.h b/gdata/gdata.h
index 158a765..fe7a4c3 100644
--- a/gdata/gdata.h
+++ b/gdata/gdata.h
@@ -39,6 +39,7 @@
 #include <gdata/gdata-authorization-domain.h>
 #include <gdata/gdata-client-login-authorizer.h>
 #include <gdata/gdata-oauth1-authorizer.h>
+#include <gdata/gdata-oauth2-authorizer.h>
 #ifdef GOA_API_IS_SUBJECT_TO_CHANGE
 /* You need to define GOA_API_IS_SUBJECT_TO_CHANGE in order to use the GOA authoriser. */
 #include <gdata/gdata-goa-authorizer.h>
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index 5b83a42..020d136 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -1093,3 +1093,19 @@ gdata_freebase_result_error_quark
 gdata_freebase_result_get_type
 gdata_freebase_result_new
 gdata_freebase_result_dup_variant
+gdata_oauth2_authorizer_get_type
+gdata_oauth2_authorizer_new
+gdata_oauth2_authorizer_new_for_authorization_domains
+gdata_oauth2_authorizer_build_authentication_uri
+gdata_oauth2_authorizer_request_authorization
+gdata_oauth2_authorizer_request_authorization_async
+gdata_oauth2_authorizer_request_authorization_finish
+gdata_oauth2_authorizer_get_client_id
+gdata_oauth2_authorizer_get_redirect_uri
+gdata_oauth2_authorizer_get_client_secret
+gdata_oauth2_authorizer_get_locale
+gdata_oauth2_authorizer_set_locale
+gdata_oauth2_authorizer_get_timeout
+gdata_oauth2_authorizer_set_timeout
+gdata_oauth2_authorizer_get_proxy_resolver
+gdata_oauth2_authorizer_set_proxy_resolver
diff --git a/gdata/tests/Makefile.am b/gdata/tests/Makefile.am
index 77ac9fe..b0b45ba 100644
--- a/gdata/tests/Makefile.am
+++ b/gdata/tests/Makefile.am
@@ -67,6 +67,9 @@ client_login_authorizer_SOURCES        = client-login-authorizer.c $(TEST_SRCS)
 TESTS                          += oauth1-authorizer
 oauth1_authorizer_SOURCES       = oauth1-authorizer.c $(TEST_SRCS)
 
+TESTS                          += oauth2-authorizer
+oauth2_authorizer_SOURCES       = oauth2-authorizer.c $(TEST_SRCS)
+
 EXTRA_DIST += \
        photo.jpg               \
        sample.ogg              \
diff --git a/gdata/tests/oauth2-authorizer.c b/gdata/tests/oauth2-authorizer.c
new file mode 100644
index 0000000..370a009
--- /dev/null
+++ b/gdata/tests/oauth2-authorizer.c
@@ -0,0 +1,1048 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2011, 2014 <philip tecnocode co uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+#include <gdata/gdata.h>
+
+#include "common.h"
+
+static GThread *main_thread = NULL;
+
+static void
+test_oauth2_authorizer_constructor (void)
+{
+       GDataOAuth2Authorizer *authorizer;
+
+       authorizer = gdata_oauth2_authorizer_new ("Application name", GDATA_TYPE_CONTACTS_SERVICE);
+
+       g_assert (authorizer != NULL);
+       g_assert (GDATA_IS_OAUTH2_AUTHORIZER (authorizer));
+       g_assert (GDATA_IS_AUTHORIZER (authorizer));
+
+       g_object_unref (authorizer);
+}
+
+static void
+test_oauth2_authorizer_constructor_for_domains (void)
+{
+       GDataOAuth2Authorizer *authorizer;
+       GDataAuthorizationDomain *domain;
+       GList *domains;
+
+       /* Try with standard domains first */
+       domains = gdata_service_get_authorization_domains (GDATA_TYPE_CONTACTS_SERVICE);
+       authorizer = gdata_oauth2_authorizer_new_for_authorization_domains ("Application name", domains);
+       g_list_free (domains);
+
+       g_assert (authorizer != NULL);
+       g_assert (GDATA_IS_OAUTH2_AUTHORIZER (authorizer));
+       g_assert (GDATA_IS_AUTHORIZER (authorizer));
+
+       g_object_unref (authorizer);
+
+       /* Try again with a custom domain. Note that, as in test_authorization_domain_properties() this 
should not normally happen in client code. */
+       domain = GDATA_AUTHORIZATION_DOMAIN (g_object_new (GDATA_TYPE_AUTHORIZATION_DOMAIN,
+                                                          "service-name", "test",
+                                                          "scope", "test",
+                                                          NULL));
+
+       domains = g_list_prepend (NULL, domain);
+       authorizer = gdata_oauth2_authorizer_new_for_authorization_domains ("Application name", domains);
+       g_list_free (domains);
+
+       g_assert (authorizer != NULL);
+       g_assert (GDATA_IS_OAUTH2_AUTHORIZER (authorizer));
+       g_assert (GDATA_IS_AUTHORIZER (authorizer));
+
+       g_object_unref (authorizer);
+       g_object_unref (domain);
+}
+
+typedef struct {
+       GDataOAuth2Authorizer *authorizer;
+
+       guint locale_notification_count;
+       gulong locale_signal_handler;
+       guint proxy_uri_notification_count;
+       gulong proxy_uri_signal_handler;
+       guint timeout_notification_count;
+       gulong timeout_signal_handler;
+} OAuth2AuthorizerData;
+
+/* Used to count that exactly the right number of notify signals are emitted when setting properties */
+static void
+notify_cb (GObject *object, GParamSpec *pspec, guint *notification_count)
+{
+       /* Check we're running in the main thread */
+       g_assert (g_thread_self () == main_thread);
+
+       /* Increment the notification count */
+       *notification_count = *notification_count + 1;
+}
+
+static void
+connect_to_oauth2_authorizer (OAuth2AuthorizerData *data)
+{
+       /* Connect to notifications from the object to verify they're only emitted the correct number of 
times */
+       data->locale_signal_handler = g_signal_connect (data->authorizer, "notify::locale", (GCallback) 
notify_cb,
+                                                       &(data->locale_notification_count));
+       data->proxy_uri_signal_handler = g_signal_connect (data->authorizer, "notify::proxy-uri", (GCallback) 
notify_cb,
+                                                          &(data->proxy_uri_notification_count));
+       data->timeout_signal_handler = g_signal_connect (data->authorizer, "notify::timeout", (GCallback) 
notify_cb,
+                                                        &(data->timeout_notification_count));
+}
+
+static void
+set_up_oauth2_authorizer_data (OAuth2AuthorizerData *data, gconstpointer user_data)
+{
+       data->authorizer = gdata_oauth2_authorizer_new ("Application name", GDATA_TYPE_CONTACTS_SERVICE);
+       connect_to_oauth2_authorizer (data);
+}
+
+static void
+set_up_oauth2_authorizer_data_fallback_application_name (OAuth2AuthorizerData *data, gconstpointer user_data)
+{
+       g_set_application_name ("Fallback name");
+       data->authorizer = gdata_oauth2_authorizer_new (NULL, GDATA_TYPE_CONTACTS_SERVICE);
+       connect_to_oauth2_authorizer (data);
+}
+
+static void
+set_up_oauth2_authorizer_data_multiple_domains (OAuth2AuthorizerData *data, gconstpointer user_data)
+{
+       GList *authorization_domains = NULL;
+
+       authorization_domains = g_list_prepend (authorization_domains, 
gdata_contacts_service_get_primary_authorization_domain ());
+       authorization_domains = g_list_prepend (authorization_domains, 
gdata_picasaweb_service_get_primary_authorization_domain ());
+       data->authorizer = gdata_oauth2_authorizer_new_for_authorization_domains ("Application name", 
authorization_domains);
+       g_list_free (authorization_domains);
+
+       connect_to_oauth2_authorizer (data);
+}
+
+static void
+set_up_oauth2_authorizer_data_locale (OAuth2AuthorizerData *data, gconstpointer user_data)
+{
+       data->authorizer = gdata_oauth2_authorizer_new ("Application name", GDATA_TYPE_CONTACTS_SERVICE);
+       gdata_oauth2_authorizer_set_locale (data->authorizer, "en_GB");
+       connect_to_oauth2_authorizer (data);
+}
+
+/* Given an authentication URI, prompt the user to go to that URI, grant access to the test application and 
enter the resulting verifier */
+static gchar *
+query_user_for_verifier (const gchar *authentication_uri)
+{
+       char verifier[100];
+
+       /* Wait for the user to retrieve and enter the verifier */
+       g_print ("Please navigate to the following URI and grant access: %s\n", authentication_uri);
+       g_print ("Enter verifier (EOF to skip test): ");
+       if (scanf ("%100s", verifier) != 1) {
+               /* Skip the test */
+               g_test_message ("Skipping test on user request.");
+               return NULL;
+       }
+
+       g_test_message ("Proceeding with user-provided verifier “%s”.", verifier);
+
+       return g_strdup (verifier);
+}
+
+static void
+set_up_oauth2_authorizer_data_authenticated (OAuth2AuthorizerData *data, gconstpointer user_data)
+{
+       gboolean *skip_test = (gboolean*) user_data;
+       gchar *authentication_uri, *token, *token_secret, *verifier;
+
+       /* Chain up */
+       set_up_oauth2_authorizer_data (data, NULL);
+
+       /* Get an authentication URI */
+       authentication_uri = gdata_oauth2_authorizer_request_authentication_uri (data->authorizer, &token, 
&token_secret, NULL, NULL);
+       g_assert (authentication_uri != NULL);
+
+       /* Get the verifier off the user */
+       verifier = query_user_for_verifier (authentication_uri);
+
+       g_free (authentication_uri);
+
+       if (verifier == NULL) {
+               *skip_test = TRUE;
+               goto skip_test;
+       }
+
+       /* Authorise the token */
+       g_assert (gdata_oauth2_authorizer_request_authorization (data->authorizer, token, token_secret, 
verifier, NULL, NULL) == TRUE);
+
+skip_test:
+       g_free (token);
+       g_free (token_secret);
+       g_free (verifier);
+}
+
+static void
+tear_down_oauth2_authorizer_data (OAuth2AuthorizerData *data, gconstpointer user_data)
+{
+       /* Clean up signal handlers */
+       g_signal_handler_disconnect (data->authorizer, data->timeout_signal_handler);
+       g_signal_handler_disconnect (data->authorizer, data->proxy_uri_signal_handler);
+       g_signal_handler_disconnect (data->authorizer, data->locale_signal_handler);
+
+       g_object_unref (data->authorizer);
+}
+
+/* Test getting and setting the application-name property */
+static void
+test_oauth2_authorizer_properties_application_name (OAuth2AuthorizerData *data, gconstpointer user_data)
+{
+       gchar *application_name;
+
+       /* Verifying the normal state of the property in a newly-constructed instance of 
GDataOAuth2Authorizer */
+       g_assert_cmpstr (gdata_oauth2_authorizer_get_application_name (data->authorizer), ==, "Application 
name");
+
+       g_object_get (data->authorizer, "application-name", &application_name, NULL);
+       g_assert_cmpstr (application_name, ==, "Application name");
+       g_free (application_name);
+}
+
+/* Test the fallback for the application-name property */
+static void
+test_oauth2_authorizer_properties_application_name_fallback (OAuth2AuthorizerData *data, gconstpointer 
user_data)
+{
+       gchar *application_name;
+
+       /* Verifying the normal state of the property in a newly-constructed instance of 
GDataOAuth2Authorizer */
+       g_assert_cmpstr (gdata_oauth2_authorizer_get_application_name (data->authorizer), ==, "Fallback 
name");
+
+       g_object_get (data->authorizer, "application-name", &application_name, NULL);
+       g_assert_cmpstr (application_name, ==, "Fallback name");
+       g_free (application_name);
+}
+
+/* Test getting and setting the locale property */
+static void
+test_oauth2_authorizer_properties_locale (OAuth2AuthorizerData *data, gconstpointer user_data)
+{
+       gchar *locale;
+
+       /* Verifying the normal state of the property in a newly-constructed instance of 
GDataOAuth2Authorizer */
+       g_assert_cmpstr (gdata_oauth2_authorizer_get_locale (data->authorizer), ==, NULL);
+
+       g_object_get (data->authorizer, "locale", &locale, NULL);
+       g_assert_cmpstr (locale, ==, NULL);
+       g_free (locale);
+
+       g_assert_cmpuint (data->locale_notification_count, ==, 0);
+
+       /* Check setting it works and emits a notification */
+       gdata_oauth2_authorizer_set_locale (data->authorizer, "en");
+
+       g_assert_cmpuint (data->locale_notification_count, ==, 1);
+
+       g_assert_cmpstr (gdata_oauth2_authorizer_get_locale (data->authorizer), ==, "en");
+
+       g_object_get (data->authorizer, "locale", &locale, NULL);
+       g_assert_cmpstr (locale, ==, "en");
+       g_free (locale);
+
+       /* Check setting it to the same value is a no-op */
+       gdata_oauth2_authorizer_set_locale (data->authorizer, "en");
+       g_assert_cmpuint (data->locale_notification_count, ==, 1);
+
+       /* Check setting it back to NULL works */
+       gdata_oauth2_authorizer_set_locale (data->authorizer, NULL);
+
+       g_assert_cmpuint (data->locale_notification_count, ==, 2);
+
+       g_assert_cmpstr (gdata_oauth2_authorizer_get_locale (data->authorizer), ==, NULL);
+
+       g_object_get (data->authorizer, "locale", &locale, NULL);
+       g_assert_cmpstr (locale, ==, NULL);
+       g_free (locale);
+
+       /* Test that setting it using g_object_set() works */
+       g_object_set (data->authorizer, "locale", "de", NULL);
+       g_assert_cmpstr (gdata_oauth2_authorizer_get_locale (data->authorizer), ==, "de");
+}
+
+/* Test getting and setting the proxy-uri property */
+static void
+test_oauth2_authorizer_properties_proxy_uri (OAuth2AuthorizerData *data, gconstpointer user_data)
+{
+       SoupURI *proxy_uri, *new_proxy_uri;
+
+       /* Verifying the normal state of the property in a newly-constructed instance of 
GDataOAuth2Authorizer */
+       g_assert (gdata_oauth2_authorizer_get_proxy_uri (data->authorizer) == NULL);
+
+       g_object_get (data->authorizer, "proxy-uri", &proxy_uri, NULL);
+       g_assert (proxy_uri == NULL);
+
+       g_assert_cmpuint (data->proxy_uri_notification_count, ==, 0);
+
+       /* Check setting it works and emits a notification */
+       new_proxy_uri = soup_uri_new ("http://example.com/";);
+       gdata_oauth2_authorizer_set_proxy_uri (data->authorizer, new_proxy_uri);
+
+       g_assert_cmpuint (data->proxy_uri_notification_count, ==, 1);
+
+       g_assert (gdata_oauth2_authorizer_get_proxy_uri (data->authorizer) != NULL);
+       g_assert (soup_uri_equal (gdata_oauth2_authorizer_get_proxy_uri (data->authorizer), new_proxy_uri) == 
TRUE);
+
+       g_object_get (data->authorizer, "proxy-uri", &proxy_uri, NULL);
+       g_assert (proxy_uri != NULL);
+       g_assert (soup_uri_equal (gdata_oauth2_authorizer_get_proxy_uri (data->authorizer), new_proxy_uri) == 
TRUE);
+       soup_uri_free (proxy_uri);
+
+       soup_uri_free (new_proxy_uri);
+
+       /* Check setting it back to NULL works */
+       gdata_oauth2_authorizer_set_proxy_uri (data->authorizer, NULL);
+
+       g_assert_cmpuint (data->proxy_uri_notification_count, ==, 2);
+
+       g_assert (gdata_oauth2_authorizer_get_proxy_uri (data->authorizer) == NULL);
+
+       g_object_get (data->authorizer, "proxy-uri", &proxy_uri, NULL);
+       g_assert (proxy_uri == NULL);
+
+       /* Test that setting it using g_object_set() works */
+       new_proxy_uri = soup_uri_new ("http://example.com/";);
+       g_object_set (data->authorizer, "proxy-uri", new_proxy_uri, NULL);
+       soup_uri_free (new_proxy_uri);
+
+       g_assert (gdata_oauth2_authorizer_get_proxy_uri (data->authorizer) != NULL);
+}
+
+/* Test getting and setting the timeout property */
+static void
+test_oauth2_authorizer_properties_timeout (OAuth2AuthorizerData *data, gconstpointer user_data)
+{
+       guint timeout;
+
+       /* Verifying the normal state of the property in a newly-constructed instance of 
GDataOAuth2Authorizer */
+       g_assert_cmpuint (gdata_oauth2_authorizer_get_timeout (data->authorizer), ==, 0);
+
+       g_object_get (data->authorizer, "timeout", &timeout, NULL);
+       g_assert_cmpuint (timeout, ==, 0);
+
+       g_assert_cmpuint (data->timeout_notification_count, ==, 0);
+
+       /* Check setting it works and emits a notification */
+       gdata_oauth2_authorizer_set_timeout (data->authorizer, 30);
+
+       g_assert_cmpuint (data->timeout_notification_count, ==, 1);
+
+       g_assert_cmpuint (gdata_oauth2_authorizer_get_timeout (data->authorizer), ==, 30);
+
+       g_object_get (data->authorizer, "timeout", &timeout, NULL);
+       g_assert_cmpuint (timeout, ==, 30);
+
+       /* Check setting it to the same value is a no-op */
+       gdata_oauth2_authorizer_set_timeout (data->authorizer, 30);
+       g_assert_cmpuint (data->timeout_notification_count, ==, 1);
+
+       /* Check setting it back to 0 works */
+       gdata_oauth2_authorizer_set_timeout (data->authorizer, 0);
+
+       g_assert_cmpuint (data->timeout_notification_count, ==, 2);
+
+       g_assert_cmpuint (gdata_oauth2_authorizer_get_timeout (data->authorizer), ==, 0);
+
+       g_object_get (data->authorizer, "timeout", &timeout, NULL);
+       g_assert_cmpuint (timeout, ==, 0);
+
+       /* Test that setting it using g_object_set() works */
+       g_object_set (data->authorizer, "timeout", 15, NULL);
+       g_assert_cmpuint (gdata_oauth2_authorizer_get_timeout (data->authorizer), ==, 15);
+}
+
+/* Test that gdata_authorizer_refresh_authorization() is a no-op (whether authorised or not) */
+static void
+test_oauth2_authorizer_refresh_authorization (OAuth2AuthorizerData *data, gconstpointer user_data)
+{
+       gboolean *skip_test = (gboolean*) user_data;
+       GError *error = NULL;
+
+       /* Skip the test if the user's requested */
+       if (skip_test != NULL && *skip_test == TRUE) {
+               return;
+       }
+
+       g_assert (gdata_authorizer_refresh_authorization (GDATA_AUTHORIZER (data->authorizer), NULL, &error) 
== FALSE);
+       g_assert_no_error (error);
+       g_clear_error (&error);
+}
+
+/* Test that processing a request with a NULL domain will not change the request. */
+static void
+test_oauth2_authorizer_process_request_null (OAuth2AuthorizerData *data, gconstpointer user_data)
+{
+       SoupMessage *message;
+       SoupMessageHeadersIter iter;
+       guint header_count = 0;
+       const gchar *name, *value;
+
+       /* Create a new message with an empty set of request headers */
+       message = soup_message_new (SOUP_METHOD_GET, "https://example.com/";);
+
+       /* Process the message */
+       gdata_authorizer_process_request (GDATA_AUTHORIZER (data->authorizer), NULL, message);
+
+       /* Check that the set of request headers is still empty */
+       soup_message_headers_iter_init (&iter, message->request_headers);
+
+       while (soup_message_headers_iter_next (&iter, &name, &value) == TRUE) {
+               header_count++;
+       }
+
+       g_assert_cmpuint (header_count, ==, 0);
+
+       g_object_unref (message);
+}
+
+/* Test that processing a request with an authorizer which hasn't been authenticated yet will not change the 
request. */
+static void
+test_oauth2_authorizer_process_request_unauthenticated (OAuth2AuthorizerData *data, gconstpointer user_data)
+{
+       SoupMessage *message;
+       SoupMessageHeadersIter iter;
+       guint header_count = 0;
+       const gchar *name, *value;
+
+       /* Create a new message with an empty set of request headers */
+       message = soup_message_new (SOUP_METHOD_GET, "https://example.com/";);
+
+       /* Process the message */
+       gdata_authorizer_process_request (GDATA_AUTHORIZER (data->authorizer), 
gdata_contacts_service_get_primary_authorization_domain (), message);
+
+       /* Check that the set of request headers is still empty */
+       soup_message_headers_iter_init (&iter, message->request_headers);
+
+       while (soup_message_headers_iter_next (&iter, &name, &value) == TRUE) {
+               header_count++;
+       }
+
+       g_assert_cmpuint (header_count, ==, 0);
+
+       g_object_unref (message);
+}
+
+/* Test that processing a request with an authorizer which has been authenticated will change the request. */
+static void
+test_oauth2_authorizer_process_request_authenticated (OAuth2AuthorizerData *data, gconstpointer user_data)
+{
+       gboolean *skip_test = (gboolean*) user_data;
+       SoupMessage *message;
+       SoupMessageHeadersIter iter;
+       guint header_count = 0;
+       const gchar *name, *value;
+
+       /* Skip the test if the user's requested */
+       if (skip_test != NULL && *skip_test == TRUE) {
+               return;
+       }
+
+       /* Create a new message with an empty set of request headers */
+       message = soup_message_new (SOUP_METHOD_GET, "http://example.com/";);
+
+       /* Process the message */
+       gdata_authorizer_process_request (GDATA_AUTHORIZER (data->authorizer), 
gdata_contacts_service_get_primary_authorization_domain (), message);
+
+       /* Check that at least one new header has been set */
+       soup_message_headers_iter_init (&iter, message->request_headers);
+
+       while (soup_message_headers_iter_next (&iter, &name, &value) == TRUE) {
+               header_count++;
+       }
+
+       g_assert_cmpuint (header_count, >, 0);
+
+       g_object_unref (message);
+}
+
+/* Test that requesting an authentication URI synchronously works correctly */
+static void
+test_oauth2_authorizer_request_authentication_uri_sync (OAuth2AuthorizerData *data, gconstpointer user_data)
+{
+       gchar *authentication_uri, *token, *token_secret;
+       GError *error = NULL;
+
+       authentication_uri = gdata_oauth2_authorizer_request_authentication_uri (data->authorizer, &token, 
&token_secret, NULL, &error);
+       g_assert_no_error (error);
+       g_assert (authentication_uri != NULL && *authentication_uri != '\0');
+       g_assert (token != NULL && *token != '\0');
+       g_assert (token_secret != NULL && *token != '\0');
+       g_clear_error (&error);
+
+       g_test_message ("Requesting an authentication URI gave “%s” with request token “%s” and request token 
secret “%s”.",
+                       authentication_uri, token, token_secret);
+
+       g_free (authentication_uri);
+       g_free (token);
+       g_free (token_secret);
+}
+
+/* Test that requesting an authentication URI synchronously can be cancelled */
+static void
+test_oauth2_authorizer_request_authentication_uri_sync_cancellation (OAuth2AuthorizerData *data, 
gconstpointer user_data)
+{
+       /* Initialise token and token_secret so we check that 
gdata_oauth2_authorizer_request_authentication_uri() NULLifies them on error */
+       gchar *authentication_uri, *token = (gchar*) "error", *token_secret = (gchar*) "error";
+       GCancellable *cancellable;
+       GError *error = NULL;
+
+       /* Set up the cancellable */
+       cancellable = g_cancellable_new ();
+
+       /* Get a request token. This should return immediately as the cancellable was cancelled beforehand. */
+       g_cancellable_cancel (cancellable);
+       authentication_uri = gdata_oauth2_authorizer_request_authentication_uri (data->authorizer, &token, 
&token_secret, cancellable, &error);
+       g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+       g_assert (authentication_uri == NULL);
+       g_assert (token == NULL);
+       g_assert (token_secret == NULL);
+       g_clear_error (&error);
+
+       g_free (authentication_uri);
+       g_free (token);
+       g_free (token_secret);
+
+       g_object_unref (cancellable);
+}
+
+typedef struct {
+       OAuth2AuthorizerData parent;
+       GMainLoop *main_loop;
+} OAuth2AuthorizerAsyncData;
+
+static void
+set_up_oauth2_authorizer_async_data (OAuth2AuthorizerAsyncData *data, gconstpointer user_data)
+{
+       /* Chain up */
+       set_up_oauth2_authorizer_data ((OAuth2AuthorizerData*) data, user_data);
+
+       /* Set up the main loop */
+       data->main_loop = g_main_loop_new (NULL, FALSE);
+}
+
+static void
+set_up_oauth2_authorizer_async_data_multiple_domains (OAuth2AuthorizerAsyncData *data, gconstpointer 
user_data)
+{
+       /* Chain up */
+       set_up_oauth2_authorizer_data_multiple_domains ((OAuth2AuthorizerData*) data, user_data);
+
+       /* Set up the main loop */
+       data->main_loop = g_main_loop_new (NULL, FALSE);
+}
+
+static void
+tear_down_oauth2_authorizer_async_data (OAuth2AuthorizerAsyncData *data, gconstpointer user_data)
+{
+       g_main_loop_unref (data->main_loop);
+
+       /* Chain up */
+       tear_down_oauth2_authorizer_data ((OAuth2AuthorizerData*) data, user_data);
+}
+
+static void
+test_oauth2_authorizer_request_authentication_uri_async_cb (GDataOAuth2Authorizer *authorizer, GAsyncResult 
*async_result,
+                                                            OAuth2AuthorizerAsyncData *data)
+{
+       gchar *authentication_uri, *token, *token_secret;
+       GError *error = NULL;
+
+       authentication_uri = gdata_oauth2_authorizer_request_authentication_uri_finish (authorizer, 
async_result, &token, &token_secret, &error);
+       g_assert_no_error (error);
+       g_assert (authentication_uri != NULL && *authentication_uri != '\0');
+       g_assert (token != NULL && *token != '\0');
+       g_assert (token_secret != NULL && *token != '\0');
+       g_clear_error (&error);
+
+       g_test_message ("Requesting an authentication URI asynchronously gave “%s” with request token “%s” 
and request token secret “%s”.",
+                       authentication_uri, token, token_secret);
+
+       g_free (authentication_uri);
+       g_free (token);
+       g_free (token_secret);
+
+       g_main_loop_quit (data->main_loop);
+}
+
+/* Test that asynchronously requesting an authentication URI for a single authorization domain works */
+static void
+test_oauth2_authorizer_request_authentication_uri_async (OAuth2AuthorizerAsyncData *data, gconstpointer 
user_data)
+{
+       /* Create a main loop and request an authentication URI */
+       gdata_oauth2_authorizer_request_authentication_uri_async (data->parent.authorizer, NULL,
+                                                                 (GAsyncReadyCallback) 
test_oauth2_authorizer_request_authentication_uri_async_cb,
+                                                                 data);
+
+       g_main_loop_run (data->main_loop);
+}
+
+static void
+test_oauth2_authorizer_request_authentication_uri_async_cancellation_cb (GDataOAuth2Authorizer *authorizer, 
GAsyncResult *async_result,
+                                                                         OAuth2AuthorizerAsyncData *data)
+{
+       /* Initialise token and token_secret so we check that 
gdata_oauth2_authorizer_request_authentication_uri_finish() NULLifies them on error */
+       gchar *authentication_uri, *token = (gchar*) "error", *token_secret = (gchar*) "error";
+       GError *error = NULL;
+
+       authentication_uri = gdata_oauth2_authorizer_request_authentication_uri_finish (authorizer, 
async_result, &token, &token_secret, &error);
+       g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+       g_assert (authentication_uri == NULL);
+       g_assert (token == NULL);
+       g_assert (token_secret == NULL);
+       g_clear_error (&error);
+
+       g_free (authentication_uri);
+       g_free (token);
+       g_free (token_secret);
+
+       g_main_loop_quit (data->main_loop);
+}
+
+/* Test that cancellation of asynchronous authentication URI requests work */
+static void
+test_oauth2_authorizer_request_authentication_uri_async_cancellation (OAuth2AuthorizerAsyncData *data, 
gconstpointer user_data)
+{
+       GCancellable *cancellable;
+
+       /* Set up the cancellable */
+       cancellable = g_cancellable_new ();
+
+       /* Create a main loop and request an authentication URI */
+       gdata_oauth2_authorizer_request_authentication_uri_async (data->parent.authorizer, cancellable,
+                                                                 (GAsyncReadyCallback)
+                                                                     
test_oauth2_authorizer_request_authentication_uri_async_cancellation_cb,
+                                                                 data);
+       g_cancellable_cancel (cancellable);
+
+       g_main_loop_run (data->main_loop);
+
+       g_object_unref (cancellable);
+}
+
+typedef struct {
+       OAuth2AuthorizerData parent;
+       gchar *token;
+       gchar *token_secret;
+       gchar *verifier;
+} OAuth2AuthorizerInteractiveData;
+
+/* NOTE: Any consumer of this data has to check for (data->verifier == NULL) and skip the test in that case 
*/
+static void
+set_up_oauth2_authorizer_interactive_data (OAuth2AuthorizerInteractiveData *data, gconstpointer user_data)
+{
+       gchar *authentication_uri;
+
+       /* Chain up */
+       set_up_oauth2_authorizer_data ((OAuth2AuthorizerData*) data, user_data);
+
+       /* Get an authentication URI */
+       authentication_uri = gdata_oauth2_authorizer_request_authentication_uri (data->parent.authorizer, 
&(data->token), &(data->token_secret),
+                                                                                NULL, NULL);
+       g_assert (authentication_uri != NULL);
+
+       /* Wait for the user to retrieve and enter the verifier */
+       data->verifier = query_user_for_verifier (authentication_uri);
+
+       g_free (authentication_uri);
+}
+
+static void
+set_up_oauth2_authorizer_interactive_data_bad_credentials (OAuth2AuthorizerInteractiveData *data, 
gconstpointer user_data)
+{
+       gchar *authentication_uri;
+
+       /* Chain up */
+       set_up_oauth2_authorizer_data ((OAuth2AuthorizerData*) data, user_data);
+
+       /* Get an authentication URI */
+       authentication_uri = gdata_oauth2_authorizer_request_authentication_uri (data->parent.authorizer, 
&(data->token), &(data->token_secret),
+                                                                                NULL, NULL);
+       g_assert (authentication_uri != NULL);
+
+       /* Give a bogus verifier */
+       data->verifier = g_strdup ("test");
+
+       g_free (authentication_uri);
+}
+
+static void
+tear_down_oauth2_authorizer_interactive_data (OAuth2AuthorizerInteractiveData *data, gconstpointer user_data)
+{
+       g_free (data->token);
+       g_free (data->token_secret);
+       g_free (data->verifier);
+
+       /* Chain up */
+       tear_down_oauth2_authorizer_data ((OAuth2AuthorizerData*) data, user_data);
+}
+
+/* Test that synchronously authorizing a request token is successful. Note that this test has to be 
interactive, as the user has to visit the
+ * authentication URI to retrieve a verifier for the request token. */
+static void
+test_oauth2_authorizer_request_authorization_sync (OAuth2AuthorizerInteractiveData *data, gconstpointer 
user_data)
+{
+       gboolean success;
+       GError *error = NULL;
+
+       /* Skip the test if the user's requested */
+       if (data->verifier == NULL) {
+               return;
+       }
+
+       /* Check we're not authorised beforehand */
+       g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (data->parent.authorizer),
+                 gdata_contacts_service_get_primary_authorization_domain ()) == FALSE);
+
+       /* Test that authorising the token retrieved previously is successful */
+       success = gdata_oauth2_authorizer_request_authorization (data->parent.authorizer, data->token, 
data->token_secret, data->verifier, NULL,
+                                                                &error);
+       g_assert_no_error (error);
+       g_assert (success == TRUE);
+       g_clear_error (&error);
+
+       /* Are we authorised now? */
+       g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (data->parent.authorizer),
+                 gdata_contacts_service_get_primary_authorization_domain ()) == TRUE);
+}
+
+/* Test that synchronously authorizing a request token fails if an invalid verifier is provided. */
+static void
+test_oauth2_authorizer_request_authorization_sync_bad_credentials (OAuth2AuthorizerInteractiveData *data, 
gconstpointer user_data)
+{
+       gboolean success;
+       GError *error = NULL;
+
+       /* Check we're not authorised beforehand */
+       g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (data->parent.authorizer),
+                 gdata_contacts_service_get_primary_authorization_domain ()) == FALSE);
+
+       /* Test that authorising the token retrieved above fails */
+       success = gdata_oauth2_authorizer_request_authorization (data->parent.authorizer, data->token, 
data->token_secret, data->verifier, NULL,
+                                                                &error);
+       g_assert_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_FORBIDDEN);
+       g_assert (success == FALSE);
+       g_clear_error (&error);
+
+       /* Are we authorised now? */
+       g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (data->parent.authorizer),
+                 gdata_contacts_service_get_primary_authorization_domain ()) == FALSE);
+}
+
+/* Test that cancellation of synchronously authorizing a request token works. Note that this test has to be 
interactive, as the user has to visit the
+ * authentication URI to retrieve a verifier for the request token. */
+static void
+test_oauth2_authorizer_request_authorization_sync_cancellation (OAuth2AuthorizerInteractiveData *data, 
gconstpointer user_data)
+{
+       gboolean success;
+       GCancellable *cancellable;
+       GError *error = NULL;
+
+       /* Skip the test if the user's requested */
+       if (data->verifier == NULL) {
+               return;
+       }
+
+       /* Check we're not authorised beforehand */
+       g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (data->parent.authorizer),
+                 gdata_contacts_service_get_primary_authorization_domain ()) == FALSE);
+
+       /* Create the cancellable */
+       cancellable = g_cancellable_new ();
+
+       /* Test that authorising the token retrieved above is successful */
+       g_cancellable_cancel (cancellable);
+       success = gdata_oauth2_authorizer_request_authorization (data->parent.authorizer, data->token, 
data->token_secret, data->verifier,
+                                                                cancellable, &error);
+       g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+       g_assert (success == FALSE);
+       g_clear_error (&error);
+
+       /* Are we authorised now? */
+       g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (data->parent.authorizer),
+                 gdata_contacts_service_get_primary_authorization_domain ()) == FALSE);
+
+       g_object_unref (cancellable);
+}
+
+typedef struct {
+       OAuth2AuthorizerInteractiveData parent;
+       GMainLoop *main_loop;
+} OAuth2AuthorizerInteractiveAsyncData;
+
+/* NOTE: Any consumer of this data has to check for (data->verifier == NULL) and skip the test in that case 
*/
+static void
+set_up_oauth2_authorizer_interactive_async_data (OAuth2AuthorizerInteractiveAsyncData *data, gconstpointer 
user_data)
+{
+       /* Chain up */
+       set_up_oauth2_authorizer_interactive_data ((OAuth2AuthorizerInteractiveData*) data, user_data);
+
+       /* Set up the main loop */
+       data->main_loop = g_main_loop_new (NULL, FALSE);
+}
+
+static void
+set_up_oauth2_authorizer_interactive_async_data_bad_credentials (OAuth2AuthorizerInteractiveAsyncData *data, 
gconstpointer user_data)
+{
+       /* Chain up */
+       set_up_oauth2_authorizer_interactive_data_bad_credentials ((OAuth2AuthorizerInteractiveData*) data, 
user_data);
+
+       /* Set up the main loop */
+       data->main_loop = g_main_loop_new (NULL, FALSE);
+}
+
+static void
+tear_down_oauth2_authorizer_interactive_async_data (OAuth2AuthorizerInteractiveAsyncData *data, 
gconstpointer user_data)
+{
+       g_main_loop_unref (data->main_loop);
+
+       /* Chain up */
+       tear_down_oauth2_authorizer_interactive_data ((OAuth2AuthorizerInteractiveData*) data, user_data);
+}
+
+static void
+test_oauth2_authorizer_request_authorization_async_cb (GDataOAuth2Authorizer *authorizer, GAsyncResult 
*async_result,
+                                                       OAuth2AuthorizerInteractiveAsyncData *data)
+{
+       gboolean success;
+       GError *error = NULL;
+
+       success = gdata_oauth2_authorizer_request_authorization_finish (authorizer, async_result, &error);
+       g_assert_no_error (error);
+       g_assert (success == TRUE);
+       g_clear_error (&error);
+
+       /* Are we authorised now? */
+       g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer),
+                 gdata_contacts_service_get_primary_authorization_domain ()) == TRUE);
+
+       g_main_loop_quit (data->main_loop);
+}
+
+/* Test that asynchronously authorizing a request token works. Note that this test has to be interactive, as 
the user has to visit the
+ * authentication URI to retrieve a verifier for the request token. */
+static void
+test_oauth2_authorizer_request_authorization_async (OAuth2AuthorizerInteractiveAsyncData *data, 
gconstpointer user_data)
+{
+       /* Skip the test if the user's requested */
+       if (data->parent.verifier == NULL) {
+               return;
+       }
+
+       /* Check we're not authorised beforehand */
+       g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER 
(data->parent.parent.authorizer),
+                 gdata_contacts_service_get_primary_authorization_domain ()) == FALSE);
+
+       /* Create a main loop and request authorization */
+       gdata_oauth2_authorizer_request_authorization_async (data->parent.parent.authorizer, 
data->parent.token, data->parent.token_secret,
+                                                            data->parent.verifier, NULL,
+                                                            (GAsyncReadyCallback) 
test_oauth2_authorizer_request_authorization_async_cb, data);
+
+       g_main_loop_run (data->main_loop);
+}
+
+static void
+test_oauth2_authorizer_request_authorization_async_bad_credentials_cb (GDataOAuth2Authorizer *authorizer, 
GAsyncResult *async_result,
+                                                                       OAuth2AuthorizerInteractiveAsyncData 
*data)
+{
+       gboolean success;
+       GError *error = NULL;
+
+       success = gdata_oauth2_authorizer_request_authorization_finish (authorizer, async_result, &error);
+       g_assert_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_FORBIDDEN);
+       g_assert (success == FALSE);
+       g_clear_error (&error);
+
+       /* Are we authorised now? */
+       g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer),
+                 gdata_contacts_service_get_primary_authorization_domain ()) == FALSE);
+
+       g_main_loop_quit (data->main_loop);
+}
+
+/* Test that asynchronously authorizing a request token fails if an invalid verifier is provided. */
+static void
+test_oauth2_authorizer_request_authorization_async_bad_credentials (OAuth2AuthorizerInteractiveAsyncData 
*data, gconstpointer user_data)
+{
+       /* Check we're not authorised beforehand */
+       g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER 
(data->parent.parent.authorizer),
+                 gdata_contacts_service_get_primary_authorization_domain ()) == FALSE);
+
+       /* Create a main loop and request authorization */
+       gdata_oauth2_authorizer_request_authorization_async (data->parent.parent.authorizer, 
data->parent.token, data->parent.token_secret,
+                                                            data->parent.verifier, NULL,
+                                                            (GAsyncReadyCallback)
+                                                                
test_oauth2_authorizer_request_authorization_async_bad_credentials_cb,
+                                                            data);
+
+       g_main_loop_run (data->main_loop);
+}
+
+static void
+test_oauth2_authorizer_request_authorization_async_cancellation_cb (GDataOAuth2Authorizer *authorizer, 
GAsyncResult *async_result,
+                                                                    OAuth2AuthorizerInteractiveAsyncData 
*data)
+{
+       gboolean success;
+       GError *error = NULL;
+
+       success = gdata_oauth2_authorizer_request_authorization_finish (authorizer, async_result, &error);
+       g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+       g_assert (success == FALSE);
+       g_clear_error (&error);
+
+       /* Are we authorised now? */
+       g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER (authorizer),
+                 gdata_contacts_service_get_primary_authorization_domain ()) == FALSE);
+
+       g_main_loop_quit (data->main_loop);
+}
+
+/* Test that cancelling asynchronously authorizing a request token works. Note that this test has to be 
interactive, as the user has to visit the
+ * authentication URI to retrieve a verifier for the request token. */
+static void
+test_oauth2_authorizer_request_authorization_async_cancellation (OAuth2AuthorizerInteractiveAsyncData *data, 
gconstpointer user_data)
+{
+       GCancellable *cancellable;
+
+       /* Skip the test if the user's requested */
+       if (data->parent.verifier == NULL) {
+               return;
+       }
+
+       /* Check we're not authorised beforehand */
+       g_assert (gdata_authorizer_is_authorized_for_domain (GDATA_AUTHORIZER 
(data->parent.parent.authorizer),
+                 gdata_contacts_service_get_primary_authorization_domain ()) == FALSE);
+
+       /* Create the cancellable */
+       cancellable = g_cancellable_new ();
+
+       /* Create a main loop and request authorization */
+       gdata_oauth2_authorizer_request_authorization_async (data->parent.parent.authorizer, 
data->parent.token, data->parent.token_secret,
+                                                            data->parent.verifier, cancellable,
+                                                            (GAsyncReadyCallback) 
test_oauth2_authorizer_request_authorization_async_cancellation_cb,
+                                                            data);
+       g_cancellable_cancel (cancellable);
+
+       g_main_loop_run (data->main_loop);
+
+       g_object_unref (cancellable);
+}
+
+int
+main (int argc, char *argv[])
+{
+       gdata_test_init (argc, argv);
+
+       main_thread = g_thread_self ();
+
+       g_test_add_func ("/oauth2-authorizer/constructor", test_oauth2_authorizer_constructor);
+       g_test_add_func ("/oauth2-authorizer/constructor/for-domains", 
test_oauth2_authorizer_constructor_for_domains);
+
+       g_test_add ("/oauth2-authorizer/properties/application-name", OAuth2AuthorizerData, NULL, 
set_up_oauth2_authorizer_data,
+                   test_oauth2_authorizer_properties_application_name, tear_down_oauth2_authorizer_data);
+       g_test_add ("/oauth2-authorizer/properties/application-name/fallback", OAuth2AuthorizerData, NULL,
+                   set_up_oauth2_authorizer_data_fallback_application_name, 
test_oauth2_authorizer_properties_application_name_fallback,
+                   tear_down_oauth2_authorizer_data);
+       g_test_add ("/oauth2-authorizer/properties/locale", OAuth2AuthorizerData, NULL, 
set_up_oauth2_authorizer_data,
+                   test_oauth2_authorizer_properties_locale, tear_down_oauth2_authorizer_data);
+       g_test_add ("/oauth2-authorizer/properties/proxy-uri", OAuth2AuthorizerData, NULL, 
set_up_oauth2_authorizer_data,
+                   test_oauth2_authorizer_properties_proxy_uri, tear_down_oauth2_authorizer_data);
+       g_test_add ("/oauth2-authorizer/properties/timeout", OAuth2AuthorizerData, NULL, 
set_up_oauth2_authorizer_data,
+                   test_oauth2_authorizer_properties_timeout, tear_down_oauth2_authorizer_data);
+
+       g_test_add ("/oauth2-authorizer/refresh-authorization/unauthenticated", OAuth2AuthorizerData, NULL,
+                   set_up_oauth2_authorizer_data, test_oauth2_authorizer_refresh_authorization, 
tear_down_oauth2_authorizer_data);
+
+       g_test_add ("/oauth2-authorizer/process-request/null", OAuth2AuthorizerData, NULL,
+                   set_up_oauth2_authorizer_data, test_oauth2_authorizer_process_request_null, 
tear_down_oauth2_authorizer_data);
+       g_test_add ("/oauth2-authorizer/process-request/unauthenticated", OAuth2AuthorizerData, NULL,
+                   set_up_oauth2_authorizer_data, test_oauth2_authorizer_process_request_unauthenticated, 
tear_down_oauth2_authorizer_data);
+
+       if (gdata_test_internet () == TRUE) {
+               /* Sync request-authentication-uri tests */
+               g_test_add ("/oauth2-authorizer/request-authentication-uri/sync", OAuth2AuthorizerData, NULL, 
set_up_oauth2_authorizer_data,
+                           test_oauth2_authorizer_request_authentication_uri_sync, 
tear_down_oauth2_authorizer_data);
+               g_test_add ("/oauth2-authorizer/request-authentication-uri/sync/multiple-domains", 
OAuth2AuthorizerData, NULL,
+                           set_up_oauth2_authorizer_data_multiple_domains, 
test_oauth2_authorizer_request_authentication_uri_sync,
+                           tear_down_oauth2_authorizer_data);
+               g_test_add ("/oauth2-authorizer/request-authentication-uri/sync/multiple-domains", 
OAuth2AuthorizerData, NULL,
+                           set_up_oauth2_authorizer_data_locale, 
test_oauth2_authorizer_request_authentication_uri_sync,
+                           tear_down_oauth2_authorizer_data);
+               g_test_add ("/oauth2-authorizer/request-authentication-uri/sync/cancellation", 
OAuth2AuthorizerData, NULL,
+                           set_up_oauth2_authorizer_data, 
test_oauth2_authorizer_request_authentication_uri_sync_cancellation,
+                           tear_down_oauth2_authorizer_data);
+
+               /* Async request-authentication-uri tests */
+               g_test_add ("/oauth2-authorizer/request-authentication-uri/async", OAuth2AuthorizerAsyncData, 
NULL,
+                           set_up_oauth2_authorizer_async_data, 
test_oauth2_authorizer_request_authentication_uri_async,
+                           tear_down_oauth2_authorizer_async_data);
+               g_test_add ("/oauth2-authorizer/request-authentication-uri/async/multiple-domains", 
OAuth2AuthorizerAsyncData, NULL,
+                           set_up_oauth2_authorizer_async_data_multiple_domains, 
test_oauth2_authorizer_request_authentication_uri_async,
+                           tear_down_oauth2_authorizer_async_data);
+               g_test_add ("/oauth2-authorizer/request-authentication-uri/async/cancellation", 
OAuth2AuthorizerAsyncData, NULL,
+                           set_up_oauth2_authorizer_async_data, 
test_oauth2_authorizer_request_authentication_uri_async_cancellation,
+                           tear_down_oauth2_authorizer_async_data);
+
+               /* Sync request-authorization tests */
+               if (gdata_test_interactive () == TRUE) {
+                       g_test_add ("/oauth2-authorizer/request-authorization/sync", 
OAuth2AuthorizerInteractiveData, NULL,
+                                   set_up_oauth2_authorizer_interactive_data, 
test_oauth2_authorizer_request_authorization_sync,
+                                   tear_down_oauth2_authorizer_interactive_data);
+                       g_test_add ("/oauth2-authorizer/request-authorization/sync/cancellation", 
OAuth2AuthorizerInteractiveData, NULL,
+                                   set_up_oauth2_authorizer_interactive_data, 
test_oauth2_authorizer_request_authorization_sync_cancellation,
+                                   tear_down_oauth2_authorizer_interactive_data);
+               }
+
+               g_test_add ("/oauth2-authorizer/request-authorization/sync/bad-credentials", 
OAuth2AuthorizerInteractiveData, NULL,
+                           set_up_oauth2_authorizer_interactive_data_bad_credentials,
+                           test_oauth2_authorizer_request_authorization_sync_bad_credentials, 
tear_down_oauth2_authorizer_interactive_data);
+
+               /* Async request-authorization tests */
+               if (gdata_test_interactive () == TRUE) {
+                       g_test_add ("/oauth2-authorizer/request-authorization/async", 
OAuth2AuthorizerInteractiveAsyncData, NULL,
+                                   set_up_oauth2_authorizer_interactive_async_data, 
test_oauth2_authorizer_request_authorization_async,
+                                   tear_down_oauth2_authorizer_interactive_async_data);
+                       g_test_add ("/oauth2-authorizer/request-authorization/async/cancellation", 
OAuth2AuthorizerInteractiveAsyncData, NULL,
+                                   set_up_oauth2_authorizer_interactive_async_data, 
test_oauth2_authorizer_request_authorization_async_cancellation,
+                                   tear_down_oauth2_authorizer_interactive_async_data);
+               }
+
+               g_test_add ("/oauth2-authorizer/request-authorization/async/bad-credentials", 
OAuth2AuthorizerInteractiveAsyncData, NULL,
+                           set_up_oauth2_authorizer_interactive_async_data_bad_credentials,
+                           test_oauth2_authorizer_request_authorization_async_bad_credentials, 
tear_down_oauth2_authorizer_interactive_async_data);
+
+               /* Miscellaneous tests */
+               if (gdata_test_interactive () == TRUE) {
+                       gboolean skip_test = FALSE;
+
+                       g_test_add ("/oauth2-authorizer/refresh-authorization/authenticated", 
OAuth2AuthorizerData, &skip_test,
+                                   set_up_oauth2_authorizer_data_authenticated, 
test_oauth2_authorizer_refresh_authorization,
+                                   tear_down_oauth2_authorizer_data);
+
+                       g_test_add ("/oauth2-authorizer/process-request/authenticated", OAuth2AuthorizerData, 
&skip_test,
+                                   set_up_oauth2_authorizer_data_authenticated, 
test_oauth2_authorizer_process_request_authenticated,
+                                   tear_down_oauth2_authorizer_data);
+               }
+       }
+
+       return g_test_run ();
+}



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