[libgdata] core: Add an OAuth 1.0 authoriser



commit 47d20c21e8986cdbab21098428dd57bca203f6ad
Author: Philip Withnall <philip tecnocode co uk>
Date:   Sun May 29 22:38:39 2011 +0100

    core: Add an OAuth 1.0 authoriser
    
    This adds a GDataAuthorizer implementation for OAuth 1.0, the current
    favourite authentication/authorization protocol in Google-land.
    
    This includes full documentation and a test suite.
    
    This makes the following API changes:
     â?¢ Add GDataOAuth1Authorizer and all its properties and methods.
    
    It also adds a dependency on liboauth â?¥ 0.9.4.
    
    Helps: bgo#646285

 Makefile.am                       |    4 +-
 README                            |    1 +
 configure.ac                      |    3 +-
 docs/reference/gdata-docs.xml     |    1 +
 docs/reference/gdata-sections.txt |   32 +
 gdata/gdata-authorizer.c          |   12 +-
 gdata/gdata-oauth1-authorizer.c   | 1337 +++++++++++++++++++++++++++++++++++++
 gdata/gdata-oauth1-authorizer.h   |   97 +++
 gdata/gdata.h                     |    1 +
 gdata/gdata.symbols               |   16 +
 gdata/tests/Makefile.am           |    3 +
 gdata/tests/common.c              |   28 +-
 gdata/tests/common.h              |    1 +
 gdata/tests/oauth1-authorizer.c   | 1048 +++++++++++++++++++++++++++++
 po/POTFILES.in                    |    1 +
 15 files changed, 2575 insertions(+), 10 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index f64cf7d..8a44956 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -161,7 +161,8 @@ gdata_headers = \
 	gdata/gdata-batchable.h		\
 	gdata/gdata-authorizer.h	\
 	gdata/gdata-authorization-domain.h	\
-	gdata/gdata-client-login-authorizer.h
+	gdata/gdata-client-login-authorizer.h	\
+	gdata/gdata-oauth1-authorizer.h
 # The following headers are private, and shouldn't be installed:
 private_headers = \
 	gdata/gdata-private.h		\
@@ -299,6 +300,7 @@ gdata_sources = \
 	gdata/gdata-authorizer.c	\
 	gdata/gdata-authorization-domain.c	\
 	gdata/gdata-client-login-authorizer.c	\
+	gdata/gdata-oauth1-authorizer.c		\
 	\
 	gdata/atom/gdata-author.c	\
 	gdata/atom/gdata-category.c	\
diff --git a/README b/README
index 06fbfaf..c3dd09e 100644
--- a/README
+++ b/README
@@ -17,6 +17,7 @@ Dependencies
  * libxml-2.0
  * gio-2.0 >= 2.17.3
  * libsoup-2.4 >= 2.26.1
+ * liboauth >= 0.94
 
 If compiling with --enable-gnome (for GNOME support):
  * libsoup-gnome-2.4
diff --git a/configure.ac b/configure.ac
index e93f61a..747bf62 100644
--- a/configure.ac
+++ b/configure.ac
@@ -33,6 +33,7 @@ AC_PATH_PROG([GLIB_MKENUMS],[glib-mkenums])
 GLIB_REQS=2.19.0
 GIO_REQS=2.17.3
 SOUP_REQS=2.26.1
+OAUTH_REQS=0.9.4
 
 # Before making a release, the GDATA_LT_VERSION string should be modified. The string is of the form c:r:a. Follow these instructions sequentially:
 #
@@ -58,7 +59,7 @@ AC_SUBST(GDATA_API_VERSION)
 AC_SUBST(GDATA_API_VERSION_MAJOR)
 AC_SUBST(GDATA_API_VERSION_MINOR)
 
-pkg_modules="glib-2.0 >= $GLIB_REQS libxml-2.0 gthread-2.0 gio-2.0 >= $GIO_REQS libsoup-2.4 >= $SOUP_REQS"
+pkg_modules="glib-2.0 >= $GLIB_REQS libxml-2.0 gthread-2.0 gio-2.0 >= $GIO_REQS libsoup-2.4 >= $SOUP_REQS oauth >= $OAUTH_REQS"
 PKG_CHECK_MODULES(GDATA, [$pkg_modules])
 AC_SUBST(GDATA_CFLAGS)
 AC_SUBST(GDATA_LIBS)
diff --git a/docs/reference/gdata-docs.xml b/docs/reference/gdata-docs.xml
index 21b5b6a..4d56a45 100644
--- a/docs/reference/gdata-docs.xml
+++ b/docs/reference/gdata-docs.xml
@@ -53,6 +53,7 @@
 			<xi:include href="xml/gdata-authorizer.xml"/>
 			<xi:include href="xml/gdata-authorization-domain.xml"/>
 			<xi:include href="xml/gdata-client-login-authorizer.xml"/>
+			<xi:include href="xml/gdata-oauth1-authorizer.xml"/>
 		</chapter>
 	</part>
 
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index 715f6ce..1820696 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -2226,3 +2226,35 @@ gdata_client_login_authorizer_error_get_type
 <SUBSECTION Private>
 GDataClientLoginAuthorizerPrivate
 </SECTION>
+
+<SECTION>
+<FILE>gdata-oauth1-authorizer</FILE>
+<TITLE>GDataOAuth1Authorizer</TITLE>
+GDataOAuth1Authorizer
+GDataOAuth1AuthorizerClass
+gdata_oauth1_authorizer_new
+gdata_oauth1_authorizer_new_for_authorization_domains
+gdata_oauth1_authorizer_request_authentication_uri
+gdata_oauth1_authorizer_request_authentication_uri_async
+gdata_oauth1_authorizer_request_authentication_uri_finish
+gdata_oauth1_authorizer_request_authorization
+gdata_oauth1_authorizer_request_authorization_async
+gdata_oauth1_authorizer_request_authorization_finish
+gdata_oauth1_authorizer_get_application_name
+gdata_oauth1_authorizer_get_locale
+gdata_oauth1_authorizer_set_locale
+gdata_oauth1_authorizer_get_proxy_uri
+gdata_oauth1_authorizer_set_proxy_uri
+gdata_oauth1_authorizer_get_timeout
+gdata_oauth1_authorizer_set_timeout
+<SUBSECTION Standard>
+GDATA_TYPE_OAUTH1_AUTHORIZER
+GDATA_OAUTH1_AUTHORIZER
+GDATA_OAUTH1_AUTHORIZER_CLASS
+GDATA_IS_OAUTH1_AUTHORIZER
+GDATA_IS_OAUTH1_AUTHORIZER_CLASS
+GDATA_OAUTH1_AUTHORIZER_GET_CLASS
+gdata_OAUTH1_authorizer_get_type
+<SUBSECTION Private>
+GDataClientLoginAuthorizerPrivate
+</SECTION>
diff --git a/gdata/gdata-authorizer.c b/gdata/gdata-authorizer.c
index 413dc5f..f3994d6 100644
--- a/gdata/gdata-authorizer.c
+++ b/gdata/gdata-authorizer.c
@@ -24,10 +24,10 @@
  * @include: gdata/gdata-authorizer.h
  *
  * The #GDataAuthorizer interface provides a uniform way to implement authentication and authorization processes for use by #GDataService<!-- -->s.
- * Client code will construct a new #GDataAuthorizer instance of their choosing, such as #GDataClientLoginAuthorizer, for the #GDataService<!-- -->s
- * which will be used by the client, then authenticates and authorizes with the #GDataAuthorizer instead of the #GDataService. The #GDataService then
- * uses the #GDataAuthorizer to authorize individual network requests using whatever authorization token was returned to the #GDataAuthorizer by the
- * Google Accounts service.
+ * Client code will construct a new #GDataAuthorizer instance of their choosing, such as #GDataClientLoginAuthorizer or #GDataOAuth1Authorizer, for
+ * the #GDataService<!-- -->s which will be used by the client, then authenticates and authorizes with the #GDataAuthorizer instead of the
+ * #GDataService. The #GDataService then uses the #GDataAuthorizer to authorize individual network requests using whatever authorization token was
+ * returned to the #GDataAuthorizer by the Google Accounts service.
  *
  * All #GDataAuthorizer implementations are expected to operate against a set of #GDataAuthorizationDomain<!-- -->s which are provided to the
  * authorizer at construction time. These domains specify which data domains the client expects to access using the #GDataService<!-- -->s they
@@ -39,7 +39,9 @@
  * listed in their <ulink type="http" url="http://code.google.com/apis/accounts/docs/GettingStarted.html";>online documentation</ulink>:
  * <itemizedlist>
  *  <listitem>#GDataClientLoginAuthorizer for
- *    <ulink type="http" url="http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html";>ClientLogin</ulink></listitem>
+ *    <ulink type="http" url="http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html";>ClientLogin</ulink> (deprecated)</listitem>
+ *  <listitem>#GDataOAuth1Authorizer for
+ *    <ulink type="http" url="http://code.google.com/apis/accounts/docs/OAuthForInstalledApps.html";>OAuth 1.0</ulink> (preferred)</listitem>
  * </itemizedlist>
  *
  * It is quite possible for clients to write their own #GDataAuthorizer implementation. For example, if a client uses OAuth 2.0 and handles
diff --git a/gdata/gdata-oauth1-authorizer.c b/gdata/gdata-oauth1-authorizer.c
new file mode 100644
index 0000000..0b635dc
--- /dev/null
+++ b/gdata/gdata-oauth1-authorizer.c
@@ -0,0 +1,1337 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2011 <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-oauth1-authorizer
+ * @short_description: GData OAuth 1.0 authorization interface
+ * @stability: Unstable
+ * @include: gdata/gdata-oauth1-authorizer.h
+ *
+ * #GDataOAuth1Authorizer provides an implementation of the #GDataAuthorizer interface for authentication and authorization using the
+ * <ulink type="http" url="http://code.google.com/apis/accounts/docs/OAuthForInstalledApps.html";>OAuth 1.0</ulink> process, which is Google's
+ * currently preferred authentication and authorization process, though OAuth 2.0 will be transitioned to in future.
+ *
+ * OAuth 1.0 replaces the deprecated ClientLogin process. One of the main reasons for this is to allow two-factor authentication to be supported, by
+ * moving the authentication interface to a web page under Google's control.
+ *
+ * The OAuth 1.0 process as implemented by Google follows the <ulink type="http" url="http://tools.ietf.org/html/rfc5849";>OAuth 1.0 protocol as
+ * specified by IETF in RFC 5849</ulink>, with a few additions to support scopes (implemented in libgdata by #GDataAuthorizationDomain<!-- -->s),
+ * locales and custom domains. Briefly, the process is initiated by requesting an authenticated request token from the Google accounts service
+ * (using gdata_oauth1_authorizer_request_authentication_uri()), going to the authentication URI for the request token, authenticating and authorizing
+ * access to the desired scopes, then providing the verifier returned by Google to the Google accounts service again (using
+ * gdata_oauth1_authorizer_request_authorization()) to authorize the token. This results in an access token which is attached to all future requests
+ * to the online service.
+ *
+ * While Google supports unregistered and registered modes for OAuth 1.0 authorization, it only supports unregistered mode for installed applications.
+ * Consequently, libgdata also only supports unregistered mode. For this purpose, the application name to be presented to the user on the
+ * authentication page at the URI returned by gdata_oauth1_authorizer_request_authentication_uri() can be specified when constructing the
+ * #GDataOAuth1Authorizer.
+ *
+ * As described, each authentication/authorization operation is in two parts: gdata_oauth1_authorizer_request_authentication_uri() and
+ * gdata_oauth1_authorizer_request_authorization(). #GDataOAuth1Authorizer stores no state about ongoing authentication operations (i.e. ones which
+ * have successfully called gdata_oauth1_authorizer_request_authentication_uri(), but are yet to successfully call
+ * gdata_oauth1_authorizer_request_authorization()). Consequently, operations can be abandoned before calling
+ * gdata_oauth1_authorizer_request_authorization() without problems. The only state necessary between the calls is the request token and request token
+ * secret, as returned by gdata_oauth1_authorizer_request_authentication_uri() and taken as parameters to
+ * gdata_oauth1_authorizer_request_authorization().
+ *
+ * #GDataOAuth1Authorizer natively supports authorization against multiple services in a single authorization request (unlike
+ * #GDataClientLoginAuthorizer).
+ *
+ * Each access token is long lived, so reauthorization is rarely necessary with #GDataOAuth1Authorizer. Consequently, refreshing authorization using
+ * gdata_authorizer_refresh_authorization() is not supported by #GDataOAuth1Authorizer, and will immediately return %FALSE with no error set.
+ *
+ * <example>
+ *	<title>Authenticating Asynchronously Using OAuth 1.0</title>
+ *	<programlisting>
+ *	GDataSomeService *service;
+ *	GDataOAuth1Authorizer *authorizer;
+ *
+ *	/<!-- -->* Create an authorizer and authenticate and authorize the service we're using, asynchronously. *<!-- -->/
+ *	authorizer = gdata_oauth1_authorizer_new (_("My libgdata application"), GDATA_TYPE_SOME_SERVICE);
+ *	gdata_oauth1_authorizer_request_authentication_uri_async (authorizer, cancellable,
+ *	                                                          (GAsyncReadyCallback) request_authentication_uri_cb, user_data);
+ *
+ *	/<!-- -->* Create a service object and link it with the authorizer *<!-- -->/
+ *	service = gdata_some_service_new (GDATA_AUTHORIZER (authorizer));
+ *
+ *	static void
+ *	request_authentication_uri_cb (GDataOAuth1Authorizer *authorizer, GAsyncResult *async_result, gpointer user_data)
+ *	{
+ *		gchar *authentication_uri, *token, *token_secret, *verifier;
+ *		GError *error = NULL;
+ *
+ *		authentication_uri = gdata_oauth1_authorizer_request_authentication_uri_finish (authorizer, async_result, &token, &token_secret,
+ *		                                                                                &error);
+ *
+ *		if (error != NULL) {
+ *			/<!-- -->* Notify the user of all errors except cancellation errors *<!-- -->/
+ *			if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ *				g_error ("Requesting a token failed: %s", error->message);
+ *			}
+ *
+ *			g_error_free (error);
+ *			goto finish;
+ *		}
+ *
+ *		/<!-- -->* (Present the page at the authentication URI to the user, either in an embedded or stand-alone web browser, and
+ *		 * ask them to grant access to the application and return the verifier Google gives them.) *<!-- -->/
+ *		verifier = ask_user_for_verifier (authentication_uri);
+ *
+ *		gdata_oauth1_authorizer_request_authorization_async (authorizer, token, token_secret, verifier, cancellable,
+ *		                                                     (GAsyncReadyCallback) request_authorization_cb, user_data);
+ *
+ *	finish:
+ *		g_free (verifier);
+ *		g_free (authentication_uri);
+ *		g_free (token);
+ *		g_free (token_secret);
+ *	}
+ *
+ *	static void
+ *	request_authorization_cb (GDataOAuth1Authorizer *authorizer, GAsyncResult *async_result, gpointer user_data)
+ *	{
+ *		GError *error = NULL;
+ *
+ *		if (gdata_oauth1_authorizer_request_authorization_finish (authorizer, async_result, &error) == FALSE) {
+ *			/<!-- -->* Notify the user of all errors except cancellation errors *<!-- -->/
+ *			if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ *				g_error ("Authorization failed: %s", error->message);
+ *			}
+ *
+ *			g_error_free (error);
+ *			return;
+ *		}
+ *
+ *		/<!-- -->* (The client is now authenticated and authorized against the service.
+ *		 * It can now proceed to execute queries on the service object which require the user to be authenticated.) *<!-- -->/
+ *	}
+ *
+ *	g_object_unref (service);
+ *	g_object_unref (authorizer);
+ *	</programlisting>
+ * </example>
+ *
+ * Since: 0.9.0
+ */
+
+#include <config.h>
+#include <string.h>
+#include <oauth.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+
+#include "gdata-oauth1-authorizer.h"
+#include "gdata-private.h"
+
+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 gboolean is_authorized_for_domain (GDataAuthorizer *self, GDataAuthorizationDomain *domain);
+
+static void sign_message (GDataOAuth1Authorizer *self, SoupMessage *message, const gchar *token, const gchar *token_secret, GHashTable *parameters);
+
+static void notify_proxy_uri_cb (GObject *object, GParamSpec *pspec, GDataOAuth1Authorizer *self);
+static void notify_timeout_cb (GObject *gobject, GParamSpec *pspec, GObject *self);
+
+struct _GDataOAuth1AuthorizerPrivate {
+	SoupSession *session;
+	SoupURI *proxy_uri; /* cached version only set if gdata_oauth1_authorizer_get_proxy_uri() is called */
+
+	gchar *application_name;
+	gchar *locale;
+
+	GStaticMutex mutex; /* mutex for token, token_secret and authorization_domains */
+
+	/* Note: This is the access token, not the request token returned by gdata_oauth1_authorizer_request_authentication_uri().
+	 * It's NULL iff the authorizer isn't authenticated. token_secret must be NULL iff token is NULL. */
+	gchar *token;
+	gchar *token_secret;
+
+	/* Mapping from GDataAuthorizationDomain to itself; a set of domains for which ->access_token is valid. */
+	GHashTable *authorization_domains;
+};
+
+enum {
+	PROP_APPLICATION_NAME = 1,
+	PROP_LOCALE,
+	PROP_PROXY_URI,
+	PROP_TIMEOUT,
+};
+
+G_DEFINE_TYPE_WITH_CODE (GDataOAuth1Authorizer, gdata_oauth1_authorizer, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GDATA_TYPE_AUTHORIZER, authorizer_init))
+
+static void
+gdata_oauth1_authorizer_class_init (GDataOAuth1AuthorizerClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	g_type_class_add_private (klass, sizeof (GDataOAuth1AuthorizerPrivate));
+
+	gobject_class->get_property = get_property;
+	gobject_class->set_property = set_property;
+	gobject_class->dispose = dispose;
+	gobject_class->finalize = finalize;
+
+	/**
+	 * GDataOAuth1Authorizer:application-name:
+	 *
+	 * The human-readable, translated application name for the client, to be presented to the user on the authentication page at the URI
+	 * returned by gdata_oauth1_authorizer_request_authentication_uri().
+	 *
+	 * If %NULL is provided in the constructor to #GDataOAuth1Authorizer, the value returned by g_get_application_name() will be used as a
+	 * fallback. Note that this may also be %NULL: in this case, the authentication page will use the application name â??anonymousâ??.
+	 *
+	 * Since: 0.9.0
+	 */
+	g_object_class_install_property (gobject_class, PROP_APPLICATION_NAME,
+	                                 g_param_spec_string ("application-name",
+	                                                      "Application name", "The human-readable, translated application name for the client.",
+	                                                      NULL,
+	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataOAuth1Authorizer: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_oauth1_authorizer_request_authentication_uri().
+	 *
+	 * The server-side behaviour is undefined if it doesn't support a given locale.
+	 *
+	 * Since: 0.9.0
+	 */
+	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));
+
+	/**
+	 * GDataOAuth1Authorizer:proxy-uri:
+	 *
+	 * The proxy URI used internally for all network requests.
+	 *
+	 * Since: 0.9.0
+	 */
+	g_object_class_install_property (gobject_class, PROP_PROXY_URI,
+	                                 g_param_spec_boxed ("proxy-uri",
+	                                                     "Proxy URI", "The proxy URI used internally for all network requests.",
+	                                                     SOUP_TYPE_URI,
+	                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataOAuth1Authorizer: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: 0.9.0
+	 */
+	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));
+}
+
+static void
+authorizer_init (GDataAuthorizerInterface *iface)
+{
+	iface->process_request = process_request;
+	iface->is_authorized_for_domain = is_authorized_for_domain;
+}
+
+static void
+gdata_oauth1_authorizer_init (GDataOAuth1Authorizer *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_OAUTH1_AUTHORIZER, GDataOAuth1AuthorizerPrivate);
+
+	/* Set up the authorizer's mutex */
+	g_static_mutex_init (&(self->priv->mutex));
+	self->priv->authorization_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 proxy-uri and timeout properties */
+	g_signal_connect (self->priv->session, "notify::proxy-uri", (GCallback) notify_proxy_uri_cb, self);
+	g_signal_connect (self->priv->session, "notify::timeout", (GCallback) notify_timeout_cb, self);
+}
+
+static void
+dispose (GObject *object)
+{
+	GDataOAuth1AuthorizerPrivate *priv = GDATA_OAUTH1_AUTHORIZER (object)->priv;
+
+	if (priv->session != NULL)
+		g_object_unref (priv->session);
+	priv->session = NULL;
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (gdata_oauth1_authorizer_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+	GDataOAuth1AuthorizerPrivate *priv = GDATA_OAUTH1_AUTHORIZER (object)->priv;
+
+	g_free (priv->application_name);
+	g_free (priv->locale);
+
+	g_hash_table_destroy (priv->authorization_domains);
+	g_static_mutex_free (&(priv->mutex));
+
+	if (priv->proxy_uri != NULL) {
+		soup_uri_free (priv->proxy_uri);
+	}
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (gdata_oauth1_authorizer_parent_class)->finalize (object);
+}
+
+static void
+get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+	GDataOAuth1AuthorizerPrivate *priv = GDATA_OAUTH1_AUTHORIZER (object)->priv;
+
+	switch (property_id) {
+		case PROP_APPLICATION_NAME:
+			g_value_set_string (value, priv->application_name);
+			break;
+		case PROP_LOCALE:
+			g_value_set_string (value, priv->locale);
+			break;
+		case PROP_PROXY_URI:
+			g_value_set_boxed (value, gdata_oauth1_authorizer_get_proxy_uri (GDATA_OAUTH1_AUTHORIZER (object)));
+			break;
+		case PROP_TIMEOUT:
+			g_value_set_uint (value, gdata_oauth1_authorizer_get_timeout (GDATA_OAUTH1_AUTHORIZER (object)));
+			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)
+{
+	GDataOAuth1AuthorizerPrivate *priv = GDATA_OAUTH1_AUTHORIZER (object)->priv;
+
+	switch (property_id) {
+		/* Construct only */
+		case PROP_APPLICATION_NAME:
+			priv->application_name = g_value_dup_string (value);
+
+			/* Default to the value of g_get_application_name() */
+			if (priv->application_name == NULL || *(priv->application_name) == '\0') {
+				g_free (priv->application_name);
+				priv->application_name = g_strdup (g_get_application_name ());
+			}
+
+			break;
+		case PROP_LOCALE:
+			gdata_oauth1_authorizer_set_locale (GDATA_OAUTH1_AUTHORIZER (object), g_value_get_string (value));
+			break;
+		case PROP_PROXY_URI:
+			gdata_oauth1_authorizer_set_proxy_uri (GDATA_OAUTH1_AUTHORIZER (object), g_value_get_boxed (value));
+			break;
+		case PROP_TIMEOUT:
+			gdata_oauth1_authorizer_set_timeout (GDATA_OAUTH1_AUTHORIZER (object), g_value_get_uint (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)
+{
+	GDataOAuth1AuthorizerPrivate *priv = GDATA_OAUTH1_AUTHORIZER (self)->priv;
+
+	/* Set the authorisation header */
+	g_static_mutex_lock (&(priv->mutex));
+
+	/* Sanity check */
+	g_assert ((priv->token == NULL) == (priv->token_secret == NULL));
+
+	if (priv->token != NULL && g_hash_table_lookup (priv->authorization_domains, domain) != NULL) {
+		sign_message (GDATA_OAUTH1_AUTHORIZER (self), message, priv->token, priv->token_secret, NULL);
+	}
+
+	g_static_mutex_unlock (&(priv->mutex));
+}
+
+static gboolean
+is_authorized_for_domain (GDataAuthorizer *self, GDataAuthorizationDomain *domain)
+{
+	GDataOAuth1AuthorizerPrivate *priv = GDATA_OAUTH1_AUTHORIZER (self)->priv;
+	gpointer result;
+	const gchar *token;
+
+	g_static_mutex_lock (&(priv->mutex));
+	token = priv->token;
+	result = g_hash_table_lookup (priv->authorization_domains, domain);
+	g_static_mutex_unlock (&(priv->mutex));
+
+	/* Sanity check */
+	g_assert (result == NULL || result == domain);
+
+	return (token != NULL && result != NULL) ? TRUE : FALSE;
+}
+
+/* Sign the message and add the Authorization header to it containing the signature.
+ * NOTE: This must not lock priv->mutex, as it's called from within a critical section in process_request() and priv->mutex isn't recursive. */
+static void
+sign_message (GDataOAuth1Authorizer *self, SoupMessage *message, const gchar *token, const gchar *token_secret, GHashTable *parameters)
+{
+	GHashTableIter iter;
+	const gchar *key, *value, *consumer_key, *consumer_secret, *signature_method;
+	gsize params_length = 0;
+	GList *sorted_params = NULL, *i;
+	GString *query_string, *signature_base_string, *secret_string, *authorization_header;
+	SoupURI *normalised_uri;
+	gchar *uri, *signature, *timestamp;
+	char *nonce;
+	gboolean is_first = TRUE;
+	GTimeVal time_val;
+
+	g_return_if_fail (GDATA_IS_OAUTH1_AUTHORIZER (self));
+	g_return_if_fail (SOUP_IS_MESSAGE (message));
+	g_return_if_fail (token == NULL || *token != '\0');
+	g_return_if_fail (token_secret == NULL || *token_secret != '\0');
+	g_return_if_fail ((token == NULL) == (token_secret == NULL));
+
+	/* Build and return a HMAC-SHA1 signature for the given SoupMessage. We always use HMAC-SHA1, since installed applications have to be
+	 * unregistered (see: http://code.google.com/apis/accounts/docs/OAuth_ref.html#SigningOAuth).
+	 * Reference: http://tools.ietf.org/html/rfc5849#section-3.4 */
+	signature_method = "HMAC-SHA1";
+
+	/* As described here, we use an anonymous consumer key and secret, since we're designed for installed applications:
+	 * http://code.google.com/apis/accounts/docs/OAuth_ref.html#SigningOAuth */
+	consumer_key = "anonymous";
+	consumer_secret = "anonymous";
+
+	/* Add various standard parameters to the list (note: this modifies the hash table belonging to the caller) */
+	nonce = oauth_gen_nonce ();
+	g_get_current_time (&time_val);
+	timestamp = g_strdup_printf ("%li", time_val.tv_sec);
+
+	if (parameters == NULL) {
+		parameters = g_hash_table_new (g_str_hash, g_str_equal);
+	} else {
+		g_hash_table_ref (parameters);
+	}
+
+	g_hash_table_insert (parameters, (gpointer) "oauth_signature_method", (gpointer) signature_method);
+	g_hash_table_insert (parameters, (gpointer) "oauth_consumer_key", (gpointer) consumer_key);
+	g_hash_table_insert (parameters, (gpointer) "oauth_nonce", nonce);
+	g_hash_table_insert (parameters, (gpointer) "oauth_timestamp", timestamp);
+	g_hash_table_insert (parameters, (gpointer) "oauth_version", (gpointer) "1.0");
+
+	/* Only add the token if it's been provided */
+	if (token != NULL) {
+		g_hash_table_insert (parameters, (gpointer) "oauth_token", (gpointer) token);
+	}
+
+	/* Sort the parameters and build a query string, as defined here: http://tools.ietf.org/html/rfc5849#section-3.4.1.3 */
+	g_hash_table_iter_init (&iter, parameters);
+
+	while (g_hash_table_iter_next (&iter, (gpointer*) &key, (gpointer*) &value) == TRUE) {
+		GString *pair = g_string_new (NULL);
+
+		g_string_append_uri_escaped (pair, key, NULL, FALSE);
+		g_string_append_c (pair, '=');
+		g_string_append_uri_escaped (pair, value, NULL, FALSE);
+
+		/* Append the pair to the list for sorting, and keep track of the total length of the strings in the list so far */
+		params_length += pair->len + 1 /* sep */;
+		sorted_params = g_list_prepend (sorted_params, g_string_free (pair, FALSE));
+	}
+
+	g_hash_table_unref (parameters);
+
+	sorted_params = g_list_sort (sorted_params, (GCompareFunc) g_strcmp0);
+
+	/* Concatenate the parameters to give the query string */
+	query_string = g_string_sized_new (params_length);
+
+	for (i = sorted_params; i != NULL; i = i->next) {
+		if (is_first == FALSE) {
+			g_string_append_c (query_string, '&');
+		}
+
+		g_string_append (query_string, i->data);
+
+		g_free (i->data);
+		is_first = FALSE;
+	}
+
+	g_list_free (sorted_params);
+
+	/* Normalise the URI as described here: http://tools.ietf.org/html/rfc5849#section-3.4.1.2 */
+	normalised_uri = soup_uri_copy (soup_message_get_uri (message));
+	soup_uri_set_query (normalised_uri, NULL);
+	soup_uri_set_fragment (normalised_uri, NULL);
+
+	/* Append it to the signature base string */
+	uri = soup_uri_to_string (normalised_uri, FALSE);
+
+	/* Start building the signature base string as described here: http://tools.ietf.org/html/rfc5849#section-3.4.1.1 */
+	signature_base_string = g_string_sized_new (4 /* method */ + 1 /* sep */ + strlen (uri) + 1 /* sep */ + params_length /* query string */);
+	g_string_append_uri_escaped (signature_base_string, message->method, NULL, FALSE);
+	g_string_append_c (signature_base_string, '&');
+	g_string_append_uri_escaped (signature_base_string, uri, NULL, FALSE);
+	g_string_append_c (signature_base_string, '&');
+	g_string_append_uri_escaped (signature_base_string, query_string->str, NULL, FALSE);
+
+	g_free (uri);
+	soup_uri_free (normalised_uri);
+	g_string_free (query_string, TRUE);
+
+	/* Build the secret key to use in the HMAC */
+	secret_string = g_string_new (NULL);
+	g_string_append_uri_escaped (secret_string, consumer_secret, NULL, FALSE);
+	g_string_append_c (secret_string, '&');
+
+	/* Only add token_secret if it was provided */
+	if (token_secret != NULL) {
+		g_string_append_uri_escaped (secret_string, token_secret, NULL, FALSE);
+	}
+
+	/* Create the signature as described here: http://tools.ietf.org/html/rfc5849#section-3.4.2 */
+	signature = oauth_sign_hmac_sha1 (signature_base_string->str, secret_string->str);
+
+	/*g_debug ("Signing message using Signature Base String: â??%sâ?? and key â??%sâ?? using method â??%sâ?? to give signature: â??%sâ??.",
+	         signature_base_string->str, secret_string->str, signature_method, signature);*/
+
+	g_string_free (secret_string, TRUE);
+	g_string_free (signature_base_string, TRUE);
+
+	/* Build the Authorization header and append it to the message */
+	authorization_header = g_string_new ("OAuth oauth_consumer_key=\"");
+	g_string_append_uri_escaped (authorization_header, consumer_key, NULL, FALSE);
+
+	/* Only add the token if it's been provided */
+	if (token != NULL) {
+		g_string_append (authorization_header, "\",oauth_token=\"");
+		g_string_append_uri_escaped (authorization_header, token, NULL, FALSE);
+	}
+
+	g_string_append (authorization_header, "\",oauth_signature_method=\"");
+	g_string_append_uri_escaped (authorization_header, signature_method, NULL, FALSE);
+	g_string_append (authorization_header, "\",oauth_signature=\"");
+	g_string_append_uri_escaped (authorization_header, signature, NULL, FALSE);
+	g_string_append (authorization_header, "\",oauth_timestamp=\"");
+	g_string_append_uri_escaped (authorization_header, timestamp, NULL, FALSE);
+	g_string_append (authorization_header, "\",oauth_nonce=\"");
+	g_string_append_uri_escaped (authorization_header, nonce, NULL, FALSE);
+	g_string_append (authorization_header, "\",oauth_version=\"1.0\"");
+
+	soup_message_headers_append (message->request_headers, "Authorization", authorization_header->str);
+
+	g_string_free (authorization_header, TRUE);
+	free (signature);
+	g_free (timestamp);
+	free (nonce);
+}
+
+/**
+ * gdata_oauth1_authorizer_new:
+ * @application_name: (allow-none): a human-readable, translated application name to use on authentication pages, or %NULL
+ * @service_type: the #GType of a #GDataService subclass which the #GDataOAuth1Authorizer will be used with
+ *
+ * Creates a new #GDataOAuth1Authorizer.
+ *
+ * The #GDataAuthorizationDomain<!-- -->s for the given @service_type (i.e. as returned by gdata_service_get_authorization_domains()) are the ones the
+ * user will be requested to authorize access to on the page at the URI returned by gdata_oauth1_authorizer_request_authentication_uri().
+ *
+ * The given @application_name will set the value of #GDataOAuth1Authorizer:application-name and will be displayed to the user on authentication pages
+ * returned by Google. If %NULL is provided, the value of g_get_application_name() will be used as a fallback.
+ *
+ * Return value: (transfer full): a new #GDataOAuth1Authorizer; unref with g_object_unref()
+ *
+ * Since: 0.9.0
+ */
+GDataOAuth1Authorizer *
+gdata_oauth1_authorizer_new (const gchar *application_name, GType service_type)
+{
+	g_return_val_if_fail (g_type_is_a (service_type, GDATA_TYPE_SERVICE), NULL);
+
+	return gdata_oauth1_authorizer_new_for_authorization_domains (application_name, gdata_service_get_authorization_domains (service_type));
+}
+
+/**
+ * gdata_oauth1_authorizer_new_for_authorization_domains:
+ * @application_name: (allow-none): a human-readable, translated application name to use on authentication pages, or %NULL
+ * @authorization_domains: (element-type GDataAuthorizationDomain) (transfer none): a non-empty list of #GDataAuthorizationDomain<!-- -->s to be
+ * authorized against by the #GDataOAuth1Authorizer
+ *
+ * Creates a new #GDataOAuth1Authorizer. This function is intended to be used only when the default authorization domain list for a single
+ * #GDataService, as used by gdata_oauth1_authorizer_new(), isn't suitable. For example, this could be because the #GDataOAuth1Authorizer will be used
+ * with multiple #GDataService subclasses, or because the client requires a specific set of authorization domains.
+ *
+ * The specified #GDataAuthorizationDomain<!-- -->s are the ones the user will be requested to authorize access to on the page at the URI returned by
+ * gdata_oauth1_authorizer_request_authentication_uri().
+ *
+ * The given @application_name will set the value of #GDataOAuth1Authorizer:application-name and will be displayed to the user on authentication pages
+ * returned by Google. If %NULL is provided, the value of g_get_application_name() will be used as a fallback.
+ *
+ * Return value: (transfer full): a new #GDataOAuth1Authorizer; unref with g_object_unref()
+ *
+ * Since: 0.9.0
+ */
+GDataOAuth1Authorizer *
+gdata_oauth1_authorizer_new_for_authorization_domains (const gchar *application_name, GList *authorization_domains)
+{
+	GList *i;
+	GDataOAuth1Authorizer *authorizer;
+
+	g_return_val_if_fail (authorization_domains != NULL, NULL);
+
+	authorizer = GDATA_OAUTH1_AUTHORIZER (g_object_new (GDATA_TYPE_OAUTH1_AUTHORIZER,
+	                                                    "application-name", application_name,
+	                                                    NULL));
+
+	/* Register all the domains with the authorizer */
+	for (i = authorization_domains; i != NULL; i = i->next) {
+		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 */
+		g_hash_table_insert (authorizer->priv->authorization_domains, g_object_ref (GDATA_AUTHORIZATION_DOMAIN (i->data)), i->data);
+	}
+
+	return authorizer;
+}
+
+/**
+ * gdata_oauth1_authorizer_request_authentication_uri:
+ * @self: a #GDataOAuth1Authorizer
+ * @token: (out callee-allocates): return location for the temporary credentials token returned by the authentication service; free with g_free()
+ * @token_secret: (out callee-allocates): return location for the temporary credentials token secret returned by the authentication service; free with
+ * g_free()
+ * @cancellable: (allow-none): optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Requests a fresh unauthenticated token from the Google accounts service and builds and returns the URI of an authentication page for that token.
+ * This should then be presented to the user (e.g. in an embedded or stand alone web browser). The authentication page will ask the user to log in
+ * using their Google account, then ask them to grant access to the #GDataAuthorizationDomain<!-- -->s passed to the constructor of the
+ * #GDataOAuth1Authorizer. If the user grants access, they will be given a verifier, which can then be passed to
+ * gdata_oauth1_authorizer_request_authorization() (along with the @token and @token_secret values returned by this method) to authorize the token.
+ *
+ * This method can fail if the server returns an error, but this is unlikely. If it does happen, a %GDATA_SERVICE_ERROR_PROTOCOL_ERROR will be
+ * raised, @token and @token_secret will be set to %NULL and %NULL will be returned.
+ *
+ * This method implements <ulink type="http" url="http://tools.ietf.org/html/rfc5849#section-2.1";>Section 2.1</ulink> and
+ * <ulink type="http" url="http://tools.ietf.org/html/rfc5849#section-2.2";>Section 2.2</ulink> of the
+ * <ulink type="http" url="http://tools.ietf.org/html/rfc5849";>OAuth 1.0 protocol</ulink>.
+ *
+ * Return value: (transfer full): the URI of an authentication page for the user to use; free with g_free()
+ *
+ * Since: 0.9.0
+ */
+gchar *
+gdata_oauth1_authorizer_request_authentication_uri (GDataOAuth1Authorizer *self, gchar **token, gchar **token_secret,
+                                                    GCancellable *cancellable, GError **error)
+{
+	GDataOAuth1AuthorizerPrivate *priv;
+	SoupMessage *message;
+	guint status;
+	gchar *request_body;
+	GString *scope_string, *authentication_uri;
+	GHashTable *parameters;
+	GHashTableIter iter;
+	gboolean is_first = TRUE;
+	GDataAuthorizationDomain *domain;
+	GHashTable *response_details;
+	const gchar *callback_uri, *_token, *_token_secret, *callback_confirmed;
+
+	g_return_val_if_fail (GDATA_IS_OAUTH1_AUTHORIZER (self), NULL);
+	g_return_val_if_fail (token != NULL, NULL);
+	g_return_val_if_fail (token_secret != NULL, NULL);
+	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	priv = self->priv;
+
+	/* This implements OAuthGetRequestToken and returns the URI for OAuthAuthorizeToken, which the client must then use themselves (e.g. in an
+	 * embedded web browser) to authorise the temporary credentials token. They then pass the request token and verification code they get back
+	 * from that to gdata_oauth1_authorizer_request_authorization(). */
+
+	/* We default to out-of-band callbacks */
+	callback_uri = "oob";
+
+	/* Set the output parameters to NULL in case of failure */
+	*token = NULL;
+	*token_secret = NULL;
+
+	/* Build up the space-separated list of scopes we're requesting authorisation for */
+	g_static_mutex_lock (&(priv->mutex));
+
+	scope_string = g_string_new (NULL);
+	g_hash_table_iter_init (&iter, priv->authorization_domains);
+
+	while (g_hash_table_iter_next (&iter, (gpointer*) &domain, NULL) == TRUE) {
+		if (is_first == FALSE) {
+			/* Delimiter */
+			g_string_append_c (scope_string, ' ');
+		}
+
+		g_string_append (scope_string, gdata_authorization_domain_get_scope (domain));
+
+		is_first = FALSE;
+	}
+
+	g_static_mutex_unlock (&(priv->mutex));
+
+	/* Build the request body and the set of parameters to be signed */
+	parameters = g_hash_table_new (g_str_hash, g_str_equal);
+	g_hash_table_insert (parameters, (gpointer) "scope", scope_string->str);
+	g_hash_table_insert (parameters, (gpointer) "xoauth_displayname", priv->application_name);
+	g_hash_table_insert (parameters, (gpointer) "oauth_callback", (gpointer) callback_uri);
+	request_body = soup_form_encode_hash (parameters);
+
+	/* Build the message */
+	message = soup_message_new (SOUP_METHOD_POST, "https://www.google.com/accounts/OAuthGetRequestToken";);
+	soup_message_set_request (message, "application/x-www-form-urlencoded", SOUP_MEMORY_TAKE, request_body, strlen (request_body));
+
+	sign_message (self, message, NULL, NULL, parameters);
+
+	g_hash_table_destroy (parameters);
+	g_string_free (scope_string, TRUE);
+
+	/* 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 NULL;
+	} else if (status != SOUP_STATUS_OK) {
+		/* Server returned an error. Not much we can do, since the error codes aren't documented and it shouldn't normally ever happen
+		 * anyway. */
+		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
+		                     _("The server rejected the temporary credentials request."));
+		g_object_unref (message);
+
+		return NULL;
+	}
+
+	g_assert (message->response_body->data != NULL);
+
+	/* Parse the response. We expect something like:
+	 *   oauth_token=ab3cd9j4ks73hf7g&oauth_token_secret=ZXhhbXBsZS5jb20&oauth_callback_confirmed=true
+	 * See: http://code.google.com/apis/accounts/docs/OAuth_ref.html#RequestToken and
+	 * http://tools.ietf.org/html/rfc5849#section-2.1 for details. */
+	response_details = soup_form_decode (message->response_body->data);
+
+	g_object_unref (message);
+
+	_token = g_hash_table_lookup (response_details, "oauth_token");
+	_token_secret = g_hash_table_lookup (response_details, "oauth_token_secret");
+	callback_confirmed = g_hash_table_lookup (response_details, "oauth_callback_confirmed");
+
+	/* Validate the returned values */
+	if (_token == NULL || _token_secret == NULL || callback_confirmed == NULL ||
+	    *_token == '\0' || *_token_secret == '\0' ||
+	    strcmp (callback_confirmed, "true") != 0) {
+		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
+		                     _("The server returned a malformed response."));
+		g_hash_table_destroy (response_details);
+
+		return NULL;
+	}
+
+	/* Build the authentication URI which the user will then open in a web browser and use to authenticate and authorise our application.
+	 * We expect to build something like this:
+	 *   https://www.google.com/accounts/OAuthAuthorizeToken?oauth_token=ab3cd9j4ks73hf7g&hd=mycollege.edu&hl=en&btmpl=mobile
+	 * See: http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth for more details. */
+	authentication_uri = g_string_new ("https://www.google.com/accounts/OAuthAuthorizeToken?oauth_token=";);
+	g_string_append_uri_escaped (authentication_uri, g_hash_table_lookup (response_details, "oauth_token"), NULL, TRUE);
+
+	if (priv->locale != NULL) {
+		g_string_append (authentication_uri, "&hl=");
+		g_string_append_uri_escaped (authentication_uri, priv->locale, NULL, TRUE);
+	}
+
+	/* Return the token and token secret */
+	*token = g_strdup (_token);
+	*token_secret = g_strdup (_token_secret);
+
+	g_hash_table_destroy (response_details);
+
+	return g_string_free (authentication_uri, FALSE);
+}
+
+typedef struct {
+	/* All return values */
+	gchar *token;
+	gchar *token_secret;
+	gchar *authentication_uri;
+} RequestAuthenticationUriAsyncData;
+
+static void
+request_authentication_uri_async_data_free (RequestAuthenticationUriAsyncData *data)
+{
+	g_free (data->token);
+	g_free (data->token_secret);
+	g_free (data->authentication_uri);
+
+	g_slice_free (RequestAuthenticationUriAsyncData, data);
+}
+
+static void
+request_authentication_uri_thread (GSimpleAsyncResult *result, GDataOAuth1Authorizer *authorizer, GCancellable *cancellable)
+{
+	RequestAuthenticationUriAsyncData *data;
+	GError *error = NULL;
+
+	data = g_simple_async_result_get_op_res_gpointer (result);
+
+	data->authentication_uri = gdata_oauth1_authorizer_request_authentication_uri (authorizer, &(data->token), &(data->token_secret),
+	                                                                               cancellable, &error);
+
+	if (error != NULL) {
+		g_simple_async_result_set_from_error (result, error);
+		g_error_free (error);
+	}
+}
+
+/**
+ * gdata_oauth1_authorizer_request_authentication_uri_async:
+ * @self: a #GDataOAuth1Authorizer
+ * @cancellable: (allow-none): optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when building the URI is finished
+ * @user_data: (closure): data to pass to the @callback function
+ *
+ * Requests a fresh unauthenticated token from the Google accounts service and builds and returns the URI of an authentication page for that token.
+ * @self is reffed when this method is called, so can safely be unreffed after this method returns.
+ *
+ * For more details, see gdata_oauth1_authorizer_request_authentication_uri(), which is the synchronous version of this method.
+ *
+ * When the operation is finished, @callback will be called. You can then call gdata_oauth1_authorizer_request_authentication_uri_finish() to get the
+ * results of the operation.
+ *
+ * Since: 0.9.0
+ */
+void
+gdata_oauth1_authorizer_request_authentication_uri_async (GDataOAuth1Authorizer *self, GCancellable *cancellable,
+                                                          GAsyncReadyCallback callback, gpointer user_data)
+{
+	GSimpleAsyncResult *result;
+	RequestAuthenticationUriAsyncData *data;
+
+	g_return_if_fail (GDATA_IS_OAUTH1_AUTHORIZER (self));
+	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+	g_return_if_fail (callback != NULL);
+
+	data = g_slice_new (RequestAuthenticationUriAsyncData);
+	data->token = NULL;
+	data->token_secret = NULL;
+	data->authentication_uri = NULL;
+
+	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_oauth1_authorizer_request_authentication_uri_async);
+	g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify) request_authentication_uri_async_data_free);
+	g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) request_authentication_uri_thread, G_PRIORITY_DEFAULT, cancellable);
+	g_object_unref (result);
+}
+
+/**
+ * gdata_oauth1_authorizer_request_authentication_uri_finish:
+ * @self: a #GDataOAuth1Authorizer
+ * @async_result: a #GAsyncResult
+ * @token: (out callee-allocates): return location for the temporary credentials token returned by the authentication service; free with g_free()
+ * @token_secret: (out callee-allocates): return location for the temporary credentials token secret returned by the authentication service; free with
+ * g_free()
+ * @error: a #GError, or %NULL
+ *
+ * Finishes an asynchronous authentication URI building operation started with gdata_oauth1_authorizer_request_authentication_uri_async().
+ *
+ * This method can fail if the server has returned an error, but this is unlikely. If it does happen, a %GDATA_SERVICE_ERROR_PROTOCOL_ERROR will be
+ * raised, @token and @token_secret will be set to %NULL and %NULL will be returned.
+ *
+ * Return value: (transfer full): the URI of an authentication page for the user to use; free with g_free()
+ *
+ * Since: 0.9.0
+ */
+gchar *
+gdata_oauth1_authorizer_request_authentication_uri_finish (GDataOAuth1Authorizer *self, GAsyncResult *async_result, gchar **token,
+                                                           gchar **token_secret, GError **error)
+{
+	RequestAuthenticationUriAsyncData *data;
+	gchar *authentication_uri;
+
+	g_return_val_if_fail (GDATA_IS_OAUTH1_AUTHORIZER (self), FALSE);
+	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), FALSE);
+	g_return_val_if_fail (token != NULL, NULL);
+	g_return_val_if_fail (token_secret != NULL, NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	g_warn_if_fail (g_simple_async_result_is_valid (async_result, G_OBJECT (self), gdata_oauth1_authorizer_request_authentication_uri_async));
+
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (async_result), error) == TRUE) {
+		/* Return the error and set all of the output parameters to NULL */
+		*token = NULL;
+		*token_secret = NULL;
+
+		return NULL;
+	}
+
+	/* Success! Transfer the output to the appropriate parameters and nullify it so it doesn't get freed when the async result gets finalised */
+	data = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (async_result));
+
+	*token = data->token;
+	*token_secret = data->token_secret;
+	authentication_uri = data->authentication_uri;
+
+	data->token = NULL;
+	data->token_secret = NULL;
+	data->authentication_uri = NULL;
+
+	return authentication_uri;
+}
+
+/**
+ * gdata_oauth1_authorizer_request_authorization:
+ * @self: a #GDataOAuth1Authorizer
+ * @token: the request token returned by gdata_oauth1_authorizer_request_authentication_uri()
+ * @token_secret: the request token secret returned by gdata_oauth1_authorizer_request_authentication_uri()
+ * @verifier: the verifier entered by the user from the authentication page
+ * @cancellable: (allow-none): optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Requests authorization of the given request @token from the Google accounts service using the given @verifier as entered by the user from the
+ * authentication page at the URI returned by gdata_oauth1_authorizer_request_authentication_uri(). @token and @token_secret must be the same values
+ * as were returned by gdata_oauth1_authorizer_request_authentication_uri() if it was successful.
+ *
+ * If the verifier is valid (i.e. the user granted access to the application and the Google accounts service has no reason to distrust the client),
+ * %TRUE will be returned and any operations performed from that point onwards on #GDataService<!-- -->s using this #GDataAuthorizer will be
+ * authorized.
+ *
+ * If the user denies access to the application or the Google accounts service distrusts it, a bogus verifier could be returned. In this case, %FALSE
+ * will be returned and a %GDATA_SERVICE_ERROR_FORBIDDEN error will be raised.
+ *
+ * Note that if the user denies access to the application, it may be the case that they have no verifier to enter. In this case, the client can simply
+ * not call this method. The #GDataOAuth1Authorizer stores no state for authentication operations which have succeeded in calling
+ * gdata_oauth1_authorizer_request_authentication_uri() but not yet successfully called gdata_oauth1_authorizer_request_authorization().
+ *
+ * This method implements <ulink type="http" url="http://tools.ietf.org/html/rfc5849#section-2.3";>Section 2.3</ulink> of the
+ * <ulink type="http" url="http://tools.ietf.org/html/rfc5849";>OAuth 1.0 protocol</ulink>.
+ *
+ * Return value: %TRUE if authorization was successful, %FALSE otherwise
+ *
+ * Since: 0.9.0
+ */
+gboolean
+gdata_oauth1_authorizer_request_authorization (GDataOAuth1Authorizer *self, const gchar *token, const gchar *token_secret, const gchar *verifier,
+                                               GCancellable *cancellable, GError **error)
+{
+	GDataOAuth1AuthorizerPrivate *priv;
+	SoupMessage *message;
+	guint status;
+	gchar *request_body;
+	GHashTable *parameters;
+	GHashTable *response_details;
+	const gchar *_token, *_token_secret;
+
+	g_return_val_if_fail (GDATA_IS_OAUTH1_AUTHORIZER (self), FALSE);
+	g_return_val_if_fail (token != NULL && *token != '\0', FALSE);
+	g_return_val_if_fail (token_secret != NULL && *token_secret != '\0', FALSE);
+	g_return_val_if_fail (verifier != NULL && *verifier != '\0', FALSE);
+	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	/* This implements OAuthGetAccessToken using the request token returned by OAuthGetRequestToken and the verification code returned by
+	 * OAuthAuthorizeToken. See:
+	 *  â?¢ http://code.google.com/apis/accounts/docs/OAuth_ref.html#AccessToken
+	 *  â?¢ http://tools.ietf.org/html/rfc5849#section-2.3
+	 */
+
+	priv = self->priv;
+
+	/* Build the request body and the set of parameters to be signed */
+	parameters = g_hash_table_new (g_str_hash, g_str_equal);
+	g_hash_table_insert (parameters, (gpointer) "oauth_verifier", (gpointer) verifier);
+	request_body = soup_form_encode_hash (parameters);
+
+	/* Build the message */
+	message = soup_message_new (SOUP_METHOD_POST, "https://www.google.com/accounts/OAuthGetAccessToken";);
+	soup_message_set_request (message, "application/x-www-form-urlencoded", SOUP_MEMORY_TAKE, request_body, strlen (request_body));
+
+	sign_message (self, message, token, token_secret, parameters);
+
+	g_hash_table_destroy (parameters);
+
+	/* 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) {
+		/* Server returned an error. This either means that there was a server error or, more likely, the server doesn't trust the client
+		 * or the user denied authorization to the token on the authorization web page. */
+		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_FORBIDDEN, _("Access was denied by the user or server."));
+		g_object_unref (message);
+
+		return FALSE;
+	}
+
+	g_assert (message->response_body->data != NULL);
+
+	/* Parse the response. We expect something like:
+	 *   oauth_token=ab3cd9j4ks73hf7g&oauth_token_secret=ZXhhbXBsZS5jb20&oauth_callback_confirmed=true
+	 * See: http://code.google.com/apis/accounts/docs/OAuth_ref.html#AccessToken and
+	 * http://tools.ietf.org/html/rfc5849#section-2.3 for details. */
+	response_details = soup_form_decode (message->response_body->data);
+
+	g_object_unref (message);
+
+	_token = g_hash_table_lookup (response_details, "oauth_token");
+	_token_secret = g_hash_table_lookup (response_details, "oauth_token_secret");
+
+	/* Validate the returned values */
+	if (_token == NULL || _token_secret == NULL ||
+	    *_token == '\0' || *_token_secret == '\0') {
+		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, _("The server returned a malformed response."));
+		g_hash_table_destroy (response_details);
+
+		return FALSE;
+	}
+
+	/* Store the token and token secret in the authoriser */
+	g_static_mutex_lock (&(priv->mutex));
+
+	g_free (priv->token);
+	priv->token = g_strdup (_token);
+
+	g_free (priv->token_secret);
+	priv->token_secret = g_strdup (_token_secret);
+
+	g_static_mutex_unlock (&(priv->mutex));
+
+	g_hash_table_destroy (response_details);
+
+	return TRUE;
+}
+
+typedef struct {
+	/* Input */
+	gchar *token;
+	gchar *token_secret;
+	gchar *verifier;
+} RequestAuthorizationAsyncData;
+
+static void
+request_authorization_async_data_free (RequestAuthorizationAsyncData *data)
+{
+	g_free (data->token);
+	g_free (data->token_secret);
+	g_free (data->verifier);
+
+	g_slice_free (RequestAuthorizationAsyncData, data);
+}
+
+static void
+request_authorization_thread (GSimpleAsyncResult *result, GDataOAuth1Authorizer *authorizer, GCancellable *cancellable)
+{
+	RequestAuthorizationAsyncData *data;
+	gboolean success;
+	GError *error = NULL;
+
+	data = g_simple_async_result_get_op_res_gpointer (result);
+
+	success = gdata_oauth1_authorizer_request_authorization (authorizer, data->token, data->token_secret, data->verifier, cancellable, &error);
+	g_simple_async_result_set_op_res_gboolean (result, success);
+
+	if (error != NULL) {
+		g_simple_async_result_set_from_error (result, error);
+		g_error_free (error);
+	}
+}
+
+/**
+ * gdata_oauth1_authorizer_request_authorization_async:
+ * @self: a #GDataOAuth1Authorizer
+ * @token: the request token returned by gdata_oauth1_authorizer_request_authentication_uri()
+ * @token_secret: the request token secret returned by gdata_oauth1_authorizer_request_authentication_uri()
+ * @verifier: the verifier entered by the user from the authentication page
+ * @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
+ *
+ * Requests authorization of the given request @token from the Google accounts service using the given @verifier as entered by the user.
+ * @self, @token, @token_secret and @verifier are reffed/copied when this method is called, so can safely be freed after this method returns.
+ *
+ * For more details, see gdata_oauth1_authorizer_request_authorization(), which is the synchronous version of this method.
+ *
+ * When the operation is finished, @callback will be called. You can then call gdata_oauth1_authorizer_request_authorization_finish() to get the
+ * results of the operation.
+ *
+ * Since: 0.9.0
+ */
+void
+gdata_oauth1_authorizer_request_authorization_async (GDataOAuth1Authorizer *self, const gchar *token, const gchar *token_secret,
+                                                     const gchar *verifier,
+                                                     GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
+{
+	GSimpleAsyncResult *result;
+	RequestAuthorizationAsyncData *data;
+
+	g_return_if_fail (GDATA_IS_OAUTH1_AUTHORIZER (self));
+	g_return_if_fail (token != NULL && *token != '\0');
+	g_return_if_fail (token_secret != NULL && *token_secret != '\0');
+	g_return_if_fail (verifier != NULL && *verifier != '\0');
+	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+	data = g_slice_new (RequestAuthorizationAsyncData);
+	data->token = g_strdup (token);
+	data->token_secret = g_strdup (token_secret);
+	data->verifier = g_strdup (verifier);
+
+	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_oauth1_authorizer_request_authorization_async);
+	g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify) request_authorization_async_data_free);
+	g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) request_authorization_thread, G_PRIORITY_DEFAULT, cancellable);
+	g_object_unref (result);
+}
+
+/**
+ * gdata_oauth1_authorizer_request_authorization_finish:
+ * @self: a #GDataOAuth1Authorizer
+ * @async_result: a #GAsyncResult
+ * @error: a #GError, or %NULL
+ *
+ * Finishes an asynchronous authorization operation started with gdata_oauth1_authorizer_request_authorization_async().
+ *
+ * Return value: %TRUE if authorization was successful, %FALSE otherwise
+ *
+ * Since: 0.9.0
+ */
+gboolean
+gdata_oauth1_authorizer_request_authorization_finish (GDataOAuth1Authorizer *self, GAsyncResult *async_result, GError **error)
+{
+	g_return_val_if_fail (GDATA_IS_OAUTH1_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);
+
+	g_warn_if_fail (g_simple_async_result_is_valid (async_result, G_OBJECT (self), gdata_oauth1_authorizer_request_authorization_async));
+
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (async_result), error) == TRUE) {
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/**
+ * gdata_oauth1_authorizer_get_application_name:
+ * @self: a #GDataOAuth1Authorizer
+ *
+ * Returns the application name being used on the authentication page at the URI returned by gdata_oauth1_authorizer_request_authentication_uri();
+ * i.e. the value of #GDataOAuth1Authorizer:application-name.
+ *
+ * Return value: (allow-none): the application name, or %NULL if one isn't set
+ *
+ * Since: 0.9.0
+ */
+const gchar *
+gdata_oauth1_authorizer_get_application_name (GDataOAuth1Authorizer *self)
+{
+	g_return_val_if_fail (GDATA_IS_OAUTH1_AUTHORIZER (self), NULL);
+	return self->priv->application_name;
+}
+
+/**
+ * gdata_oauth1_authorizer_get_locale:
+ * @self: a #GDataOAuth1Authorizer
+ *
+ * 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: 0.9.0
+ */
+const gchar *
+gdata_oauth1_authorizer_get_locale (GDataOAuth1Authorizer *self)
+{
+	g_return_val_if_fail (GDATA_IS_OAUTH1_AUTHORIZER (self), NULL);
+	return self->priv->locale;
+}
+
+/**
+ * gdata_oauth1_authorizer_set_locale:
+ * @self: a #GDataOAuth1Authorizer
+ * @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 #GDataOAuth1Authorizer:locale for more details.
+ *
+ * Note that while it's possible to change the locale after sending network requests (i.e. calling
+ * gdata_oauth1_authorizer_request_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: 0.9.0
+ */
+void
+gdata_oauth1_authorizer_set_locale (GDataOAuth1Authorizer *self, const gchar *locale)
+{
+	g_return_if_fail (GDATA_IS_OAUTH1_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_proxy_uri_cb (GObject *gobject, GParamSpec *pspec, GDataOAuth1Authorizer *self)
+{
+	/* Flush our cached version */
+	if (self->priv->proxy_uri != NULL) {
+		soup_uri_free (self->priv->proxy_uri);
+		self->priv->proxy_uri = NULL;
+	}
+
+	g_object_notify (G_OBJECT (self), "proxy-uri");
+}
+
+/**
+ * gdata_oauth1_authorizer_get_proxy_uri:
+ * @self: a #GDataOAuth1Authorizer
+ *
+ * Gets the proxy URI on the #GDataOAuth1Authorizer's #SoupSession.
+ *
+ * Return value: (transfer full) (allow-none): the proxy URI, or %NULL; free with soup_uri_free()
+ *
+ * Since: 0.9.0
+ */
+SoupURI *
+gdata_oauth1_authorizer_get_proxy_uri (GDataOAuth1Authorizer *self)
+{
+	SoupURI *proxy_uri;
+
+	g_return_val_if_fail (GDATA_IS_OAUTH1_AUTHORIZER (self), NULL);
+
+	/* If we have a cached version, return that */
+	if (self->priv->proxy_uri != NULL) {
+		return self->priv->proxy_uri;
+	}
+
+	g_object_get (self->priv->session, SOUP_SESSION_PROXY_URI, &proxy_uri, NULL);
+
+	/* Update the cache; it takes ownership of the URI */
+	self->priv->proxy_uri = proxy_uri;
+
+	return proxy_uri;
+}
+
+/**
+ * gdata_oauth1_authorizer_set_proxy_uri:
+ * @self: a #GDataOAuth1Authorizer
+ * @proxy_uri: (allow-none): the proxy URI, or %NULL
+ *
+ * Sets the proxy URI on the #SoupSession used internally by the #GDataOAuth1Authorizer. This forces all requests through the given proxy.
+ *
+ * If @proxy_uri is %NULL, no proxy will be used.
+ *
+ * Since: 0.9.0
+ */
+void
+gdata_oauth1_authorizer_set_proxy_uri (GDataOAuth1Authorizer *self, SoupURI *proxy_uri)
+{
+	g_return_if_fail (GDATA_IS_OAUTH1_AUTHORIZER (self));
+
+	g_object_set (self->priv->session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
+
+	/* Notification is handled in notify_proxy_uri_cb() which is called as a result of setting the property on the session */
+}
+
+static void
+notify_timeout_cb (GObject *gobject, GParamSpec *pspec, GObject *self)
+{
+	g_object_notify (self, "timeout");
+}
+
+/**
+ * gdata_oauth1_authorizer_get_timeout:
+ * @self: a #GDataOAuth1Authorizer
+ *
+ * Gets the #GDataOAuth1Authorizer:timeout property; the network timeout, in seconds.
+ *
+ * Return value: the timeout, or <code class="literal">0</code>
+ *
+ * Since: 0.9.0
+ */
+guint
+gdata_oauth1_authorizer_get_timeout (GDataOAuth1Authorizer *self)
+{
+	guint timeout;
+
+	g_return_val_if_fail (GDATA_IS_OAUTH1_AUTHORIZER (self), 0);
+
+	g_object_get (self->priv->session, SOUP_SESSION_TIMEOUT, &timeout, NULL);
+
+	return timeout;
+}
+
+/**
+ * gdata_oauth1_authorizer_set_timeout:
+ * @self: a #GDataOAuth1Authorizer
+ * @timeout: the timeout, or <code class="literal">0</code>
+ *
+ * Sets the #GDataOAuth1Authorizer:timeout property; the network timeout, in seconds.
+ *
+ * If @timeout is <code class="literal">0</code>, network operations will never time out.
+ *
+ * Since: 0.9.0
+ */
+void
+gdata_oauth1_authorizer_set_timeout (GDataOAuth1Authorizer *self, guint timeout)
+{
+	g_return_if_fail (GDATA_IS_OAUTH1_AUTHORIZER (self));
+
+	if (gdata_oauth1_authorizer_get_timeout (self) == timeout) {
+		/* Already has this value */
+		return;
+	}
+
+	g_object_set (self->priv->session, SOUP_SESSION_TIMEOUT, timeout, NULL);
+
+	/* Notification is handled in notify_timeout_cb() which is called as a result of setting the property on the session */
+}
diff --git a/gdata/gdata-oauth1-authorizer.h b/gdata/gdata-oauth1-authorizer.h
new file mode 100644
index 0000000..7843e3d
--- /dev/null
+++ b/gdata/gdata-oauth1-authorizer.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2011 <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_OAUTH1_AUTHORIZER_H
+#define GDATA_OAUTH1_AUTHORIZER_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "gdata-authorizer.h"
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_OAUTH1_AUTHORIZER		(gdata_oauth1_authorizer_get_type ())
+#define GDATA_OAUTH1_AUTHORIZER(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_OAUTH1_AUTHORIZER, GDataOAuth1Authorizer))
+#define GDATA_OAUTH1_AUTHORIZER_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_OAUTH1_AUTHORIZER, GDataOAuth1AuthorizerClass))
+#define GDATA_IS_OAUTH1_AUTHORIZER(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_OAUTH1_AUTHORIZER))
+#define GDATA_IS_OAUTH1_AUTHORIZER_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_OAUTH1_AUTHORIZER))
+#define GDATA_OAUTH1_AUTHORIZER_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_OAUTH1_AUTHORIZER, GDataOAuth1AuthorizerClass))
+
+typedef struct _GDataOAuth1AuthorizerPrivate	GDataOAuth1AuthorizerPrivate;
+
+/**
+ * GDataOAuth1Authorizer:
+ *
+ * All the fields in the #GDataOAuth1Authorizer structure are private and should never be accessed directly.
+ *
+ * Since: 0.9.0
+ */
+typedef struct {
+	/*< private >*/
+	GObject parent;
+	GDataOAuth1AuthorizerPrivate *priv;
+} GDataOAuth1Authorizer;
+
+/**
+ * GDataOAuth1AuthorizerClass:
+ *
+ * All the fields in the #GDataOAuth1AuthorizerClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.9.0
+ */
+typedef struct {
+	/*< private >*/
+	GObjectClass parent;
+} GDataOAuth1AuthorizerClass;
+
+GType gdata_oauth1_authorizer_get_type (void) G_GNUC_CONST;
+
+GDataOAuth1Authorizer *gdata_oauth1_authorizer_new (const gchar *application_name, GType service_type) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+GDataOAuth1Authorizer *gdata_oauth1_authorizer_new_for_authorization_domains (const gchar *application_name,
+                                                                              GList *authorization_domains) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+
+gchar *gdata_oauth1_authorizer_request_authentication_uri (GDataOAuth1Authorizer *self, gchar **token, gchar **token_secret,
+                                                           GCancellable *cancellable, GError **error);
+void gdata_oauth1_authorizer_request_authentication_uri_async (GDataOAuth1Authorizer *self, GCancellable *cancellable,
+                                                               GAsyncReadyCallback callback, gpointer user_data);
+gchar *gdata_oauth1_authorizer_request_authentication_uri_finish (GDataOAuth1Authorizer *self, GAsyncResult *async_result, gchar **token,
+                                                                  gchar **token_secret, GError **error);
+
+gboolean gdata_oauth1_authorizer_request_authorization (GDataOAuth1Authorizer *self, const gchar *token, const gchar *token_secret,
+                                                        const gchar *verifier, GCancellable *cancellable, GError **error);
+void gdata_oauth1_authorizer_request_authorization_async (GDataOAuth1Authorizer *self, const gchar *token, const gchar *token_secret,
+                                                          const gchar *verifier, GCancellable *cancellable, GAsyncReadyCallback callback,
+                                                          gpointer user_data);
+gboolean gdata_oauth1_authorizer_request_authorization_finish (GDataOAuth1Authorizer *self, GAsyncResult *async_result, GError **error);
+
+const gchar *gdata_oauth1_authorizer_get_application_name (GDataOAuth1Authorizer *self) G_GNUC_PURE;
+
+const gchar *gdata_oauth1_authorizer_get_locale (GDataOAuth1Authorizer *self) G_GNUC_PURE;
+void gdata_oauth1_authorizer_set_locale (GDataOAuth1Authorizer *self, const gchar *locale);
+
+SoupURI *gdata_oauth1_authorizer_get_proxy_uri (GDataOAuth1Authorizer *self) G_GNUC_PURE;
+void gdata_oauth1_authorizer_set_proxy_uri (GDataOAuth1Authorizer *self, SoupURI *proxy_uri);
+
+guint gdata_oauth1_authorizer_get_timeout (GDataOAuth1Authorizer *self) G_GNUC_PURE;
+void gdata_oauth1_authorizer_set_timeout (GDataOAuth1Authorizer *self, guint timeout);
+
+G_END_DECLS
+
+#endif /* !GDATA_OAUTH1_AUTHORIZER_H */
diff --git a/gdata/gdata.h b/gdata/gdata.h
index 4980c2b..86c0024 100644
--- a/gdata/gdata.h
+++ b/gdata/gdata.h
@@ -38,6 +38,7 @@
 #include <gdata/gdata-authorizer.h>
 #include <gdata/gdata-authorization-domain.h>
 #include <gdata/gdata-client-login-authorizer.h>
+#include <gdata/gdata-oauth1-authorizer.h>
 
 /* Namespaces */
 
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index 153c5b0..8827c54 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -890,3 +890,19 @@ gdata_documents_service_get_spreadsheet_authorization_domain
 gdata_picasaweb_service_get_primary_authorization_domain
 gdata_youtube_service_get_primary_authorization_domain
 gdata_service_get_authorization_domains
+gdata_oauth1_authorizer_get_type
+gdata_oauth1_authorizer_new
+gdata_oauth1_authorizer_new_for_authorization_domains
+gdata_oauth1_authorizer_request_authentication_uri
+gdata_oauth1_authorizer_request_authentication_uri_async
+gdata_oauth1_authorizer_request_authentication_uri_finish
+gdata_oauth1_authorizer_request_authorization
+gdata_oauth1_authorizer_request_authorization_async
+gdata_oauth1_authorizer_request_authorization_finish
+gdata_oauth1_authorizer_get_application_name
+gdata_oauth1_authorizer_get_locale
+gdata_oauth1_authorizer_set_locale
+gdata_oauth1_authorizer_get_proxy_uri
+gdata_oauth1_authorizer_set_proxy_uri
+gdata_oauth1_authorizer_get_timeout
+gdata_oauth1_authorizer_set_timeout
diff --git a/gdata/tests/Makefile.am b/gdata/tests/Makefile.am
index 29d5635..9e052fe 100644
--- a/gdata/tests/Makefile.am
+++ b/gdata/tests/Makefile.am
@@ -54,6 +54,9 @@ authorization_SOURCES		 = authorization.c $(TEST_SRCS)
 TEST_PROGS			+= client-login-authorizer
 client_login_authorizer_SOURCES	 = client-login-authorizer.c $(TEST_SRCS)
 
+TEST_PROGS			+= oauth1-authorizer
+oauth1_authorizer_SOURCES	 = oauth1-authorizer.c $(TEST_SRCS)
+
 EXTRA_DIST += \
 	photo.jpg		\
 	sample.ogg		\
diff --git a/gdata/tests/common.c b/gdata/tests/common.c
index d033186..4b1ce39 100644
--- a/gdata/tests/common.c
+++ b/gdata/tests/common.c
@@ -29,6 +29,9 @@
 /* %TRUE if there's no Internet connection, so we should only run local tests */
 static gboolean no_internet = FALSE;
 
+/* %TRUE if interactive tests should be skipped because we're running automatically (for example) */
+static gboolean no_interactive = FALSE;
+
 void
 gdata_test_init (int argc, char **argv)
 {
@@ -37,13 +40,16 @@ gdata_test_init (int argc, char **argv)
 	g_type_init ();
 	g_thread_init (NULL);
 
-	/* Parse the --no-internet option */
+	/* Parse the --no-internet and --no-interactive options */
 	for (i = 1; i < argc; i++) {
 		if (strcmp ("--no-internet", argv[i]) == 0 || strcmp ("-n", argv[i]) == 0) {
 			no_internet = TRUE;
 			argv[i] = (char*) "";
+		} else if (strcmp ("--no-interactive", argv[i]) == 0 || strcmp ("-i", argv[i]) == 0) {
+			no_interactive = TRUE;
+			argv[i] = (char*) "";
 		} else if (strcmp ("-?", argv[i]) == 0 || strcmp ("--help", argv[i]) == 0 || strcmp ("-h" , argv[i]) == 0) {
-			/* We have to override --help in order to document --no-internet */
+			/* We have to override --help in order to document --no-internet and --no-interactive */
 			printf ("Usage:\n"
 			          "  %s [OPTION...]\n\n"
 			          "Help Options:\n"
@@ -57,7 +63,8 @@ gdata_test_init (int argc, char **argv)
 			          "  -p TESTPATH                    Execute all tests matching TESTPATH\n"
 			          "  -m {perf|slow|thorough|quick}  Execute tests according modes\n"
 			          "  --debug-log                    Debug test logging output\n"
-			          "  -n, --no-internet              Only execute tests which don't require Internet connectivity\n",
+			          "  -n, --no-internet              Only execute tests which don't require Internet connectivity\n"
+			          "  -i, --no-interactive           Only execute tests which don't require user interaction\n",
 			          argv[0]);
 			exit (0);
 		}
@@ -85,6 +92,21 @@ gdata_test_internet (void)
 	return (no_internet == FALSE) ? TRUE : FALSE;
 }
 
+/*
+ * gdata_test_interactive:
+ *
+ * Returns whether tests which require interactivity should be run.
+ *
+ * Return value: %TRUE if interactive tests should be run, %FALSE otherwise
+ *
+ * Since: 0.9.0
+ */
+gboolean
+gdata_test_interactive (void)
+{
+	return (no_interactive == FALSE) ? TRUE : FALSE;
+}
+
 typedef struct {
 	GDataBatchOperation *operation;
 	guint op_id;
diff --git a/gdata/tests/common.h b/gdata/tests/common.h
index 488f9be..7f17c25 100644
--- a/gdata/tests/common.h
+++ b/gdata/tests/common.h
@@ -42,6 +42,7 @@ G_BEGIN_DECLS
 void gdata_test_init (int argc, char **argv);
 
 gboolean gdata_test_internet (void);
+gboolean gdata_test_interactive (void);
 
 guint gdata_test_batch_operation_query (GDataBatchOperation *operation, const gchar *id, GType entry_type,
                                         GDataEntry *entry, GDataEntry **returned_entry, GError **error);
diff --git a/gdata/tests/oauth1-authorizer.c b/gdata/tests/oauth1-authorizer.c
new file mode 100644
index 0000000..0cc4efc
--- /dev/null
+++ b/gdata/tests/oauth1-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 <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_oauth1_authorizer_constructor (void)
+{
+	GDataOAuth1Authorizer *authorizer;
+
+	authorizer = gdata_oauth1_authorizer_new ("Application name", GDATA_TYPE_CONTACTS_SERVICE);
+
+	g_assert (authorizer != NULL);
+	g_assert (GDATA_IS_OAUTH1_AUTHORIZER (authorizer));
+	g_assert (GDATA_IS_AUTHORIZER (authorizer));
+
+	g_object_unref (authorizer);
+}
+
+static void
+test_oauth1_authorizer_constructor_for_domains (void)
+{
+	GDataOAuth1Authorizer *authorizer;
+	GDataAuthorizationDomain *domain;
+	GList *domains;
+
+	/* Try with standard domains first */
+	domains = gdata_service_get_authorization_domains (GDATA_TYPE_CONTACTS_SERVICE);
+	authorizer = gdata_oauth1_authorizer_new_for_authorization_domains ("Application name", domains);
+	g_list_free (domains);
+
+	g_assert (authorizer != NULL);
+	g_assert (GDATA_IS_OAUTH1_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_oauth1_authorizer_new_for_authorization_domains ("Application name", domains);
+	g_list_free (domains);
+
+	g_assert (authorizer != NULL);
+	g_assert (GDATA_IS_OAUTH1_AUTHORIZER (authorizer));
+	g_assert (GDATA_IS_AUTHORIZER (authorizer));
+
+	g_object_unref (authorizer);
+	g_object_unref (domain);
+}
+
+typedef struct {
+	GDataOAuth1Authorizer *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;
+} OAuth1AuthorizerData;
+
+/* 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_oauth1_authorizer (OAuth1AuthorizerData *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_oauth1_authorizer_data (OAuth1AuthorizerData *data, gconstpointer user_data)
+{
+	data->authorizer = gdata_oauth1_authorizer_new ("Application name", GDATA_TYPE_CONTACTS_SERVICE);
+	connect_to_oauth1_authorizer (data);
+}
+
+static void
+set_up_oauth1_authorizer_data_fallback_application_name (OAuth1AuthorizerData *data, gconstpointer user_data)
+{
+	g_set_application_name ("Fallback name");
+	data->authorizer = gdata_oauth1_authorizer_new (NULL, GDATA_TYPE_CONTACTS_SERVICE);
+	connect_to_oauth1_authorizer (data);
+}
+
+static void
+set_up_oauth1_authorizer_data_multiple_domains (OAuth1AuthorizerData *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_oauth1_authorizer_new_for_authorization_domains ("Application name", authorization_domains);
+	g_list_free (authorization_domains);
+
+	connect_to_oauth1_authorizer (data);
+}
+
+static void
+set_up_oauth1_authorizer_data_locale (OAuth1AuthorizerData *data, gconstpointer user_data)
+{
+	data->authorizer = gdata_oauth1_authorizer_new ("Application name", GDATA_TYPE_CONTACTS_SERVICE);
+	gdata_oauth1_authorizer_set_locale (data->authorizer, "en_GB");
+	connect_to_oauth1_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_oauth1_authorizer_data_authenticated (OAuth1AuthorizerData *data, gconstpointer user_data)
+{
+	gboolean *skip_test = (gboolean*) user_data;
+	gchar *authentication_uri, *token, *token_secret, *verifier;
+
+	/* Chain up */
+	set_up_oauth1_authorizer_data (data, NULL);
+
+	/* Get an authentication URI */
+	authentication_uri = gdata_oauth1_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_oauth1_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_oauth1_authorizer_data (OAuth1AuthorizerData *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_oauth1_authorizer_properties_application_name (OAuth1AuthorizerData *data, gconstpointer user_data)
+{
+	gchar *application_name;
+
+	/* Verifying the normal state of the property in a newly-constructed instance of GDataOAuth1Authorizer */
+	g_assert_cmpstr (gdata_oauth1_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_oauth1_authorizer_properties_application_name_fallback (OAuth1AuthorizerData *data, gconstpointer user_data)
+{
+	gchar *application_name;
+
+	/* Verifying the normal state of the property in a newly-constructed instance of GDataOAuth1Authorizer */
+	g_assert_cmpstr (gdata_oauth1_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_oauth1_authorizer_properties_locale (OAuth1AuthorizerData *data, gconstpointer user_data)
+{
+	gchar *locale;
+
+	/* Verifying the normal state of the property in a newly-constructed instance of GDataOAuth1Authorizer */
+	g_assert_cmpstr (gdata_oauth1_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_oauth1_authorizer_set_locale (data->authorizer, "en");
+
+	g_assert_cmpuint (data->locale_notification_count, ==, 1);
+
+	g_assert_cmpstr (gdata_oauth1_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_oauth1_authorizer_set_locale (data->authorizer, "en");
+	g_assert_cmpuint (data->locale_notification_count, ==, 1);
+
+	/* Check setting it back to NULL works */
+	gdata_oauth1_authorizer_set_locale (data->authorizer, NULL);
+
+	g_assert_cmpuint (data->locale_notification_count, ==, 2);
+
+	g_assert_cmpstr (gdata_oauth1_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_oauth1_authorizer_get_locale (data->authorizer), ==, "de");
+}
+
+/* Test getting and setting the proxy-uri property */
+static void
+test_oauth1_authorizer_properties_proxy_uri (OAuth1AuthorizerData *data, gconstpointer user_data)
+{
+	SoupURI *proxy_uri, *new_proxy_uri;
+
+	/* Verifying the normal state of the property in a newly-constructed instance of GDataOAuth1Authorizer */
+	g_assert (gdata_oauth1_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_oauth1_authorizer_set_proxy_uri (data->authorizer, new_proxy_uri);
+
+	g_assert_cmpuint (data->proxy_uri_notification_count, ==, 1);
+
+	g_assert (gdata_oauth1_authorizer_get_proxy_uri (data->authorizer) != NULL);
+	g_assert (soup_uri_equal (gdata_oauth1_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_oauth1_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_oauth1_authorizer_set_proxy_uri (data->authorizer, NULL);
+
+	g_assert_cmpuint (data->proxy_uri_notification_count, ==, 2);
+
+	g_assert (gdata_oauth1_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_oauth1_authorizer_get_proxy_uri (data->authorizer) != NULL);
+}
+
+/* Test getting and setting the timeout property */
+static void
+test_oauth1_authorizer_properties_timeout (OAuth1AuthorizerData *data, gconstpointer user_data)
+{
+	guint timeout;
+
+	/* Verifying the normal state of the property in a newly-constructed instance of GDataOAuth1Authorizer */
+	g_assert_cmpuint (gdata_oauth1_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_oauth1_authorizer_set_timeout (data->authorizer, 30);
+
+	g_assert_cmpuint (data->timeout_notification_count, ==, 1);
+
+	g_assert_cmpuint (gdata_oauth1_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_oauth1_authorizer_set_timeout (data->authorizer, 30);
+	g_assert_cmpuint (data->timeout_notification_count, ==, 1);
+
+	/* Check setting it back to 0 works */
+	gdata_oauth1_authorizer_set_timeout (data->authorizer, 0);
+
+	g_assert_cmpuint (data->timeout_notification_count, ==, 2);
+
+	g_assert_cmpuint (gdata_oauth1_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_oauth1_authorizer_get_timeout (data->authorizer), ==, 15);
+}
+
+/* Test that gdata_authorizer_refresh_authorization() is a no-op (whether authorised or not) */
+static void
+test_oauth1_authorizer_refresh_authorization (OAuth1AuthorizerData *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_oauth1_authorizer_process_request_null (OAuth1AuthorizerData *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_oauth1_authorizer_process_request_unauthenticated (OAuth1AuthorizerData *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_oauth1_authorizer_process_request_authenticated (OAuth1AuthorizerData *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_oauth1_authorizer_request_authentication_uri_sync (OAuth1AuthorizerData *data, gconstpointer user_data)
+{
+	gchar *authentication_uri, *token, *token_secret;
+	GError *error = NULL;
+
+	authentication_uri = gdata_oauth1_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_oauth1_authorizer_request_authentication_uri_sync_cancellation (OAuth1AuthorizerData *data, gconstpointer user_data)
+{
+	/* Initialise token and token_secret so we check that gdata_oauth1_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_oauth1_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 {
+	OAuth1AuthorizerData parent;
+	GMainLoop *main_loop;
+} OAuth1AuthorizerAsyncData;
+
+static void
+set_up_oauth1_authorizer_async_data (OAuth1AuthorizerAsyncData *data, gconstpointer user_data)
+{
+	/* Chain up */
+	set_up_oauth1_authorizer_data ((OAuth1AuthorizerData*) data, user_data);
+
+	/* Set up the main loop */
+	data->main_loop = g_main_loop_new (NULL, FALSE);
+}
+
+static void
+set_up_oauth1_authorizer_async_data_multiple_domains (OAuth1AuthorizerAsyncData *data, gconstpointer user_data)
+{
+	/* Chain up */
+	set_up_oauth1_authorizer_data_multiple_domains ((OAuth1AuthorizerData*) data, user_data);
+
+	/* Set up the main loop */
+	data->main_loop = g_main_loop_new (NULL, FALSE);
+}
+
+static void
+tear_down_oauth1_authorizer_async_data (OAuth1AuthorizerAsyncData *data, gconstpointer user_data)
+{
+	g_main_loop_unref (data->main_loop);
+
+	/* Chain up */
+	tear_down_oauth1_authorizer_data ((OAuth1AuthorizerData*) data, user_data);
+}
+
+static void
+test_oauth1_authorizer_request_authentication_uri_async_cb (GDataOAuth1Authorizer *authorizer, GAsyncResult *async_result,
+                                                            OAuth1AuthorizerAsyncData *data)
+{
+	gchar *authentication_uri, *token, *token_secret;
+	GError *error = NULL;
+
+	authentication_uri = gdata_oauth1_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_oauth1_authorizer_request_authentication_uri_async (OAuth1AuthorizerAsyncData *data, gconstpointer user_data)
+{
+	/* Create a main loop and request an authentication URI */
+	gdata_oauth1_authorizer_request_authentication_uri_async (data->parent.authorizer, NULL,
+	                                                          (GAsyncReadyCallback) test_oauth1_authorizer_request_authentication_uri_async_cb,
+	                                                          data);
+
+	g_main_loop_run (data->main_loop);
+}
+
+static void
+test_oauth1_authorizer_request_authentication_uri_async_cancellation_cb (GDataOAuth1Authorizer *authorizer, GAsyncResult *async_result,
+                                                                         OAuth1AuthorizerAsyncData *data)
+{
+	/* Initialise token and token_secret so we check that gdata_oauth1_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_oauth1_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_oauth1_authorizer_request_authentication_uri_async_cancellation (OAuth1AuthorizerAsyncData *data, gconstpointer user_data)
+{
+	GCancellable *cancellable;
+
+	/* Set up the cancellable */
+	cancellable = g_cancellable_new ();
+
+	/* Create a main loop and request an authentication URI */
+	gdata_oauth1_authorizer_request_authentication_uri_async (data->parent.authorizer, cancellable,
+	                                                          (GAsyncReadyCallback)
+	                                                              test_oauth1_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 {
+	OAuth1AuthorizerData parent;
+	gchar *token;
+	gchar *token_secret;
+	gchar *verifier;
+} OAuth1AuthorizerInteractiveData;
+
+/* NOTE: Any consumer of this data has to check for (data->verifier == NULL) and skip the test in that case */
+static void
+set_up_oauth1_authorizer_interactive_data (OAuth1AuthorizerInteractiveData *data, gconstpointer user_data)
+{
+	gchar *authentication_uri;
+
+	/* Chain up */
+	set_up_oauth1_authorizer_data ((OAuth1AuthorizerData*) data, user_data);
+
+	/* Get an authentication URI */
+	authentication_uri = gdata_oauth1_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_oauth1_authorizer_interactive_data_bad_credentials (OAuth1AuthorizerInteractiveData *data, gconstpointer user_data)
+{
+	gchar *authentication_uri;
+
+	/* Chain up */
+	set_up_oauth1_authorizer_data ((OAuth1AuthorizerData*) data, user_data);
+
+	/* Get an authentication URI */
+	authentication_uri = gdata_oauth1_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_oauth1_authorizer_interactive_data (OAuth1AuthorizerInteractiveData *data, gconstpointer user_data)
+{
+	g_free (data->token);
+	g_free (data->token_secret);
+	g_free (data->verifier);
+
+	/* Chain up */
+	tear_down_oauth1_authorizer_data ((OAuth1AuthorizerData*) 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_oauth1_authorizer_request_authorization_sync (OAuth1AuthorizerInteractiveData *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_oauth1_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_oauth1_authorizer_request_authorization_sync_bad_credentials (OAuth1AuthorizerInteractiveData *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_oauth1_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_oauth1_authorizer_request_authorization_sync_cancellation (OAuth1AuthorizerInteractiveData *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_oauth1_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 {
+	OAuth1AuthorizerInteractiveData parent;
+	GMainLoop *main_loop;
+} OAuth1AuthorizerInteractiveAsyncData;
+
+/* NOTE: Any consumer of this data has to check for (data->verifier == NULL) and skip the test in that case */
+static void
+set_up_oauth1_authorizer_interactive_async_data (OAuth1AuthorizerInteractiveAsyncData *data, gconstpointer user_data)
+{
+	/* Chain up */
+	set_up_oauth1_authorizer_interactive_data ((OAuth1AuthorizerInteractiveData*) data, user_data);
+
+	/* Set up the main loop */
+	data->main_loop = g_main_loop_new (NULL, FALSE);
+}
+
+static void
+set_up_oauth1_authorizer_interactive_async_data_bad_credentials (OAuth1AuthorizerInteractiveAsyncData *data, gconstpointer user_data)
+{
+	/* Chain up */
+	set_up_oauth1_authorizer_interactive_data_bad_credentials ((OAuth1AuthorizerInteractiveData*) data, user_data);
+
+	/* Set up the main loop */
+	data->main_loop = g_main_loop_new (NULL, FALSE);
+}
+
+static void
+tear_down_oauth1_authorizer_interactive_async_data (OAuth1AuthorizerInteractiveAsyncData *data, gconstpointer user_data)
+{
+	g_main_loop_unref (data->main_loop);
+
+	/* Chain up */
+	tear_down_oauth1_authorizer_interactive_data ((OAuth1AuthorizerInteractiveData*) data, user_data);
+}
+
+static void
+test_oauth1_authorizer_request_authorization_async_cb (GDataOAuth1Authorizer *authorizer, GAsyncResult *async_result,
+                                                       OAuth1AuthorizerInteractiveAsyncData *data)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	success = gdata_oauth1_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_oauth1_authorizer_request_authorization_async (OAuth1AuthorizerInteractiveAsyncData *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_oauth1_authorizer_request_authorization_async (data->parent.parent.authorizer, data->parent.token, data->parent.token_secret,
+	                                                     data->parent.verifier, NULL,
+	                                                     (GAsyncReadyCallback) test_oauth1_authorizer_request_authorization_async_cb, data);
+
+	g_main_loop_run (data->main_loop);
+}
+
+static void
+test_oauth1_authorizer_request_authorization_async_bad_credentials_cb (GDataOAuth1Authorizer *authorizer, GAsyncResult *async_result,
+                                                                       OAuth1AuthorizerInteractiveAsyncData *data)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	success = gdata_oauth1_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_oauth1_authorizer_request_authorization_async_bad_credentials (OAuth1AuthorizerInteractiveAsyncData *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_oauth1_authorizer_request_authorization_async (data->parent.parent.authorizer, data->parent.token, data->parent.token_secret,
+	                                                     data->parent.verifier, NULL,
+	                                                     (GAsyncReadyCallback)
+	                                                         test_oauth1_authorizer_request_authorization_async_bad_credentials_cb,
+	                                                     data);
+
+	g_main_loop_run (data->main_loop);
+}
+
+static void
+test_oauth1_authorizer_request_authorization_async_cancellation_cb (GDataOAuth1Authorizer *authorizer, GAsyncResult *async_result,
+                                                                    OAuth1AuthorizerInteractiveAsyncData *data)
+{
+	gboolean success;
+	GError *error = NULL;
+
+	success = gdata_oauth1_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_oauth1_authorizer_request_authorization_async_cancellation (OAuth1AuthorizerInteractiveAsyncData *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_oauth1_authorizer_request_authorization_async (data->parent.parent.authorizer, data->parent.token, data->parent.token_secret,
+	                                                     data->parent.verifier, cancellable,
+	                                                     (GAsyncReadyCallback) test_oauth1_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 ("/oauth1-authorizer/constructor", test_oauth1_authorizer_constructor);
+	g_test_add_func ("/oauth1-authorizer/constructor/for-domains", test_oauth1_authorizer_constructor_for_domains);
+
+	g_test_add ("/oauth1-authorizer/properties/application-name", OAuth1AuthorizerData, NULL, set_up_oauth1_authorizer_data,
+	            test_oauth1_authorizer_properties_application_name, tear_down_oauth1_authorizer_data);
+	g_test_add ("/oauth1-authorizer/properties/application-name/fallback", OAuth1AuthorizerData, NULL,
+	            set_up_oauth1_authorizer_data_fallback_application_name, test_oauth1_authorizer_properties_application_name_fallback,
+	            tear_down_oauth1_authorizer_data);
+	g_test_add ("/oauth1-authorizer/properties/locale", OAuth1AuthorizerData, NULL, set_up_oauth1_authorizer_data,
+	            test_oauth1_authorizer_properties_locale, tear_down_oauth1_authorizer_data);
+	g_test_add ("/oauth1-authorizer/properties/proxy-uri", OAuth1AuthorizerData, NULL, set_up_oauth1_authorizer_data,
+	            test_oauth1_authorizer_properties_proxy_uri, tear_down_oauth1_authorizer_data);
+	g_test_add ("/oauth1-authorizer/properties/timeout", OAuth1AuthorizerData, NULL, set_up_oauth1_authorizer_data,
+	            test_oauth1_authorizer_properties_timeout, tear_down_oauth1_authorizer_data);
+
+	g_test_add ("/oauth1-authorizer/refresh-authorization/unauthenticated", OAuth1AuthorizerData, NULL,
+	            set_up_oauth1_authorizer_data, test_oauth1_authorizer_refresh_authorization, tear_down_oauth1_authorizer_data);
+
+	g_test_add ("/oauth1-authorizer/process-request/null", OAuth1AuthorizerData, NULL,
+	            set_up_oauth1_authorizer_data, test_oauth1_authorizer_process_request_null, tear_down_oauth1_authorizer_data);
+	g_test_add ("/oauth1-authorizer/process-request/unauthenticated", OAuth1AuthorizerData, NULL,
+	            set_up_oauth1_authorizer_data, test_oauth1_authorizer_process_request_unauthenticated, tear_down_oauth1_authorizer_data);
+
+	if (gdata_test_internet () == TRUE) {
+		/* Sync request-authentication-uri tests */
+		g_test_add ("/oauth1-authorizer/request-authentication-uri/sync", OAuth1AuthorizerData, NULL, set_up_oauth1_authorizer_data,
+		            test_oauth1_authorizer_request_authentication_uri_sync, tear_down_oauth1_authorizer_data);
+		g_test_add ("/oauth1-authorizer/request-authentication-uri/sync/multiple-domains", OAuth1AuthorizerData, NULL,
+		            set_up_oauth1_authorizer_data_multiple_domains, test_oauth1_authorizer_request_authentication_uri_sync,
+		            tear_down_oauth1_authorizer_data);
+		g_test_add ("/oauth1-authorizer/request-authentication-uri/sync/multiple-domains", OAuth1AuthorizerData, NULL,
+		            set_up_oauth1_authorizer_data_locale, test_oauth1_authorizer_request_authentication_uri_sync,
+		            tear_down_oauth1_authorizer_data);
+		g_test_add ("/oauth1-authorizer/request-authentication-uri/sync/cancellation", OAuth1AuthorizerData, NULL,
+		            set_up_oauth1_authorizer_data, test_oauth1_authorizer_request_authentication_uri_sync_cancellation,
+		            tear_down_oauth1_authorizer_data);
+
+		/* Async request-authentication-uri tests */
+		g_test_add ("/oauth1-authorizer/request-authentication-uri/async", OAuth1AuthorizerAsyncData, NULL,
+		            set_up_oauth1_authorizer_async_data, test_oauth1_authorizer_request_authentication_uri_async,
+		            tear_down_oauth1_authorizer_async_data);
+		g_test_add ("/oauth1-authorizer/request-authentication-uri/async/multiple-domains", OAuth1AuthorizerAsyncData, NULL,
+		            set_up_oauth1_authorizer_async_data_multiple_domains, test_oauth1_authorizer_request_authentication_uri_async,
+		            tear_down_oauth1_authorizer_async_data);
+		g_test_add ("/oauth1-authorizer/request-authentication-uri/async/cancellation", OAuth1AuthorizerAsyncData, NULL,
+		            set_up_oauth1_authorizer_async_data, test_oauth1_authorizer_request_authentication_uri_async_cancellation,
+		            tear_down_oauth1_authorizer_async_data);
+
+		/* Sync request-authorization tests */
+		if (gdata_test_interactive () == TRUE) {
+			g_test_add ("/oauth1-authorizer/request-authorization/sync", OAuth1AuthorizerInteractiveData, NULL,
+			            set_up_oauth1_authorizer_interactive_data, test_oauth1_authorizer_request_authorization_sync,
+			            tear_down_oauth1_authorizer_interactive_data);
+			g_test_add ("/oauth1-authorizer/request-authorization/sync/cancellation", OAuth1AuthorizerInteractiveData, NULL,
+			            set_up_oauth1_authorizer_interactive_data, test_oauth1_authorizer_request_authorization_sync_cancellation,
+			            tear_down_oauth1_authorizer_interactive_data);
+		}
+
+		g_test_add ("/oauth1-authorizer/request-authorization/sync/bad-credentials", OAuth1AuthorizerInteractiveData, NULL,
+		            set_up_oauth1_authorizer_interactive_data_bad_credentials,
+		            test_oauth1_authorizer_request_authorization_sync_bad_credentials, tear_down_oauth1_authorizer_interactive_data);
+
+		/* Async request-authorization tests */
+		if (gdata_test_interactive () == TRUE) {
+			g_test_add ("/oauth1-authorizer/request-authorization/async", OAuth1AuthorizerInteractiveAsyncData, NULL,
+			            set_up_oauth1_authorizer_interactive_async_data, test_oauth1_authorizer_request_authorization_async,
+			            tear_down_oauth1_authorizer_interactive_async_data);
+			g_test_add ("/oauth1-authorizer/request-authorization/async/cancellation", OAuth1AuthorizerInteractiveAsyncData, NULL,
+			            set_up_oauth1_authorizer_interactive_async_data, test_oauth1_authorizer_request_authorization_async_cancellation,
+			            tear_down_oauth1_authorizer_interactive_async_data);
+		}
+
+		g_test_add ("/oauth1-authorizer/request-authorization/async/bad-credentials", OAuth1AuthorizerInteractiveAsyncData, NULL,
+		            set_up_oauth1_authorizer_interactive_async_data_bad_credentials,
+		            test_oauth1_authorizer_request_authorization_async_bad_credentials, tear_down_oauth1_authorizer_interactive_async_data);
+
+		/* Miscellaneous tests */
+		if (gdata_test_interactive () == TRUE) {
+			gboolean skip_test = FALSE;
+
+			g_test_add ("/oauth1-authorizer/refresh-authorization/authenticated", OAuth1AuthorizerData, &skip_test,
+			            set_up_oauth1_authorizer_data_authenticated, test_oauth1_authorizer_refresh_authorization,
+			            tear_down_oauth1_authorizer_data);
+
+			g_test_add ("/oauth1-authorizer/process-request/authenticated", OAuth1AuthorizerData, &skip_test,
+			            set_up_oauth1_authorizer_data_authenticated, test_oauth1_authorizer_process_request_authenticated,
+			            tear_down_oauth1_authorizer_data);
+		}
+	}
+
+	return g_test_run ();
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 5c7efd8..1d58e0f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -6,6 +6,7 @@ gdata/gdata-client-login-authorizer.c
 gdata/gdata-download-stream.c
 gdata/gdata-entry.c
 gdata/gdata-feed.c
+gdata/gdata-oauth1-authorizer.c
 gdata/gdata-parsable.c
 gdata/gdata-parser.c
 gdata/gdata-service.c



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