summaryrefslogtreecommitdiff
path: root/rest/oauth2-proxy.c
diff options
context:
space:
mode:
authorJonathon Jongsma <jonathon@quotidian.org>2010-05-07 17:15:20 -0500
committerRoss Burton <ross@linux.intel.com>2010-05-26 15:15:38 +0100
commit44c991200bd5e8bc8be11e9fb8af9937abc1ae7c (patch)
treee856f4cd9a93962a1b5550d5251b15bbc7cc844f /rest/oauth2-proxy.c
parenta85b16a89d97199beabe283d5f0bdd9d1956490e (diff)
Add a oauth2 proxy
This new class provides a proxy class for oauth2-based web services, such as the new facebook graph API. The basic idea is as follows: - Construct a OAuth2Proxy object - Build a login url with _build_login_url(_full)() - Display said url in a browser widget (e.g. webkit) and listen for redirects to the specified 'redirect_uri' - Extract the access token from the uri that you are redirected to - Set the access token for the proxy with _set_access_token() http://bugs.meego.com/show_bug.cgi?id=2265
Diffstat (limited to 'rest/oauth2-proxy.c')
-rw-r--r--rest/oauth2-proxy.c399
1 files changed, 399 insertions, 0 deletions
diff --git a/rest/oauth2-proxy.c b/rest/oauth2-proxy.c
new file mode 100644
index 0000000..1a15bb6
--- /dev/null
+++ b/rest/oauth2-proxy.c
@@ -0,0 +1,399 @@
+/*
+ * librest - RESTful web services access
+ * Copyright (c) 2008, 2009, 2010 Intel Corporation.
+ *
+ * Authors: Rob Bradford <rob@linux.intel.com>
+ * Ross Burton <ross@linux.intel.com>
+ * Jonathon Jongsma <jonathon.jongsma@collabora.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <rest/rest-proxy.h>
+#include <libsoup/soup.h>
+#include "oauth2-proxy.h"
+#include "oauth2-proxy-private.h"
+#include "oauth2-proxy-call.h"
+
+G_DEFINE_TYPE (OAuth2Proxy, oauth2_proxy, REST_TYPE_PROXY)
+
+#define OAUTH2_PROXY_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), OAUTH2_TYPE_PROXY, OAuth2ProxyPrivate))
+
+GQuark
+oauth2_proxy_error_quark (void)
+{
+ return g_quark_from_static_string ("rest-oauth2-proxy");
+}
+
+#define EXTRA_CHARS_ENCODE "!$&'()*+,;=@"
+
+enum {
+ PROP_0,
+ PROP_CLIENT_ID,
+ PROP_AUTH_ENDPOINT,
+ PROP_ACCESS_TOKEN
+};
+
+static RestProxyCall *
+_new_call (RestProxy *proxy)
+{
+ RestProxyCall *call;
+
+ call = g_object_new (OAUTH2_TYPE_PROXY_CALL,
+ "proxy", proxy,
+ NULL);
+
+ return call;
+}
+
+static void
+oauth2_proxy_get_property (GObject *object, guint property_id,
+ GValue *value, GParamSpec *pspec)
+{
+ OAuth2ProxyPrivate *priv = ((OAuth2Proxy*)object)->priv;
+
+ switch (property_id) {
+ case PROP_CLIENT_ID:
+ g_value_set_string (value, priv->client_id);
+ break;
+ case PROP_AUTH_ENDPOINT:
+ g_value_set_string (value, priv->auth_endpoint);
+ break;
+ case PROP_ACCESS_TOKEN:
+ g_value_set_string (value, priv->access_token);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+oauth2_proxy_set_property (GObject *object, guint property_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ OAuth2ProxyPrivate *priv = ((OAuth2Proxy*)object)->priv;
+
+ switch (property_id) {
+ case PROP_CLIENT_ID:
+ if (priv->client_id)
+ g_free (priv->client_id);
+ priv->client_id = g_value_dup_string (value);
+ break;
+ case PROP_AUTH_ENDPOINT:
+ if (priv->auth_endpoint)
+ g_free (priv->auth_endpoint);
+ priv->auth_endpoint = g_value_dup_string (value);
+ break;
+ case PROP_ACCESS_TOKEN:
+ if (priv->access_token)
+ g_free (priv->access_token);
+ priv->access_token = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+oauth2_proxy_finalize (GObject *object)
+{
+ OAuth2ProxyPrivate *priv = ((OAuth2Proxy*)object)->priv;
+
+ g_free (priv->client_id);
+ g_free (priv->auth_endpoint);
+ g_free (priv->access_token);
+
+ G_OBJECT_CLASS (oauth2_proxy_parent_class)->finalize (object);
+}
+
+static void
+oauth2_proxy_class_init (OAuth2ProxyClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ RestProxyClass *proxy_class = REST_PROXY_CLASS (klass);
+ GParamSpec *pspec;
+
+ g_type_class_add_private (klass, sizeof (OAuth2ProxyPrivate));
+
+ object_class->get_property = oauth2_proxy_get_property;
+ object_class->set_property = oauth2_proxy_set_property;
+ object_class->finalize = oauth2_proxy_finalize;
+
+ proxy_class->new_call = _new_call;
+
+ pspec = g_param_spec_string ("client-id", "client-id",
+ "The client (application) id", NULL,
+ G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY|G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class,
+ PROP_CLIENT_ID,
+ pspec);
+
+ pspec = g_param_spec_string ("auth-endpoint", "auth-endpoint",
+ "The authentication endpoint url", NULL,
+ G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY|G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class,
+ PROP_AUTH_ENDPOINT,
+ pspec);
+
+ pspec = g_param_spec_string ("access-token", "access-token",
+ "The request or access token", NULL,
+ G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class,
+ PROP_ACCESS_TOKEN,
+ pspec);
+}
+
+static void
+oauth2_proxy_init (OAuth2Proxy *proxy)
+{
+ proxy->priv = OAUTH2_PROXY_GET_PRIVATE (proxy);
+}
+
+/**
+ * oauth2_proxy_new:
+ * @client_id: the client (application) id
+ * @auth_endpoint: the authentication endpoint URL
+ * @url_format: the endpoint URL
+ * @binding_required: whether the URL needs to be bound before calling
+ *
+ * Create a new #OAuth2Proxy for the specified endpoint @url_format, using the
+ * specified API key and secret.
+ *
+ * This proxy won't have the Token set so will be unauthorised. If the token is
+ * unknown then the following steps should be taken to acquire an access token:
+ * - Get the authentication url with oauth2_proxy_build_login_url()
+ * - Display this url in an embedded browser widget
+ * - wait for the browser widget to be redirected to the specified redirect_uri
+ * - extract the token from the fragment of the redirected uri (using
+ * convenience function oauth2_proxy_extract_access_token())
+ * - set the token with oauth2_proxy_set_access_token()
+ *
+ * Set @binding_required to %TRUE if the URL contains string formatting
+ * operations (for example "http://foo.com/%<!-- -->s". These must be expanded
+ * using rest_proxy_bind() before invoking the proxy.
+ *
+ * Returns: A new #OAuth2Proxy.
+ */
+RestProxy *
+oauth2_proxy_new (const char *client_id,
+ const char *auth_endpoint,
+ const gchar *url_format,
+ gboolean binding_required)
+{
+ return g_object_new (OAUTH2_TYPE_PROXY,
+ "client-id", client_id,
+ "auth-endpoint", auth_endpoint,
+ "url-format", url_format,
+ "binding-required", binding_required,
+ NULL);
+}
+
+/**
+ * oauth2_proxy_new_with_token:
+ * @client_id: the client (application) id
+ * @access_token: the Access Token
+ * @auth_endpoint: the authentication endpoint URL
+ * @url_format: the endpoint URL
+ * @binding_required: whether the URL needs to be bound before calling
+ *
+ * Create a new #OAuth2Proxy for the specified endpoint @url_format, using the
+ * specified client id
+ *
+ * @access_token is used for the Access Token, so if they are still valid then
+ * this proxy is authorised.
+ *
+ * Set @binding_required to %TRUE if the URL contains string formatting
+ * operations (for example "http://foo.com/%<!-- -->s". These must be expanded
+ * using rest_proxy_bind() before invoking the proxy.
+ *
+ * Returns: A new #OAuth2Proxy.
+ */
+RestProxy *
+oauth2_proxy_new_with_token (const char *client_id,
+ const char *access_token,
+ const char *auth_endpoint,
+ const gchar *url_format,
+ gboolean binding_required)
+{
+ return g_object_new (OAUTH2_TYPE_PROXY,
+ "client-id", client_id,
+ "access-token", access_token,
+ "auth-endpoint", auth_endpoint,
+ "url-format", url_format,
+ "binding-required", binding_required,
+ NULL);
+}
+
+/* allocates a new string of the form "key=value" */
+static void
+append_query_param (gpointer key, gpointer value, gpointer user_data)
+{
+ GString *params = (GString*) user_data;
+ char *encoded_val, *encoded_key;
+ char *param;
+
+ encoded_val = soup_uri_encode (value, EXTRA_CHARS_ENCODE);
+ encoded_key = soup_uri_encode (key, EXTRA_CHARS_ENCODE);
+
+ param = g_strdup_printf ("%s=%s", encoded_key, encoded_val);
+ g_free (encoded_key);
+ g_free (encoded_val);
+
+ // if there's already a parameter in the string, we need to add a '&'
+ // separator before adding the new param
+ if (params->len)
+ g_string_append_c (params, '&');
+ g_string_append (params, param);
+}
+
+/**
+ * oauth2_proxy_build_login_url_full:
+ * @proxy: a OAuth2Proxy object
+ * @redirect_uri: the uri to redirect to after the user authenticates
+ * @extra_params: any extra parameters to add to the login url (e.g. facebook
+ * uses 'scope=foo,bar' to request extended permissions).
+ *
+ * Builds a url at which the user can log in to the specified OAuth2-based web
+ * service. In general, this url should be displayed in an embedded browser
+ * widget, and you should then intercept the browser's redirect to @redirect_uri
+ * and extract the access token from the url fragment. After the access token
+ * has been retrieved, call oauth2_proxy_set_access_token(). This must be done
+ * before making any API calls to the service.
+ *
+ * See the oauth2 spec for more details about the "user-agent" authentication
+ * flow.
+ *
+ * The @extra_params and @redirect_uri should not be uri-encoded, that will be
+ * done automatically
+ *
+ * Returns: a newly allocated uri string
+ */
+char *
+oauth2_proxy_build_login_url_full (OAuth2Proxy *proxy,
+ const char* redirect_uri,
+ GHashTable* extra_params)
+{
+ char *url;
+ GString *params = 0;
+ char *encoded_uri, *encoded_id;
+
+ g_return_val_if_fail (proxy, NULL);
+ g_return_val_if_fail (redirect_uri, NULL);
+
+ if (extra_params && g_hash_table_size (extra_params) > 0) {
+ params = g_string_new (NULL);
+ g_hash_table_foreach (extra_params, append_query_param, params);
+ }
+
+ encoded_uri = soup_uri_encode (redirect_uri, EXTRA_CHARS_ENCODE);
+ encoded_id = soup_uri_encode (proxy->priv->client_id, EXTRA_CHARS_ENCODE);
+
+ url = g_strdup_printf ("%s?client_id=%s&redirect_uri=%s&type=user_agent",
+ proxy->priv->auth_endpoint, encoded_id,
+ encoded_uri);
+
+ g_free (encoded_uri);
+ g_free (encoded_id);
+
+ if (params) {
+ char * full_url = g_strdup_printf ("%s&%s", url, params->str);
+ g_free (url);
+ url = full_url;
+ g_string_free (params, TRUE);
+ }
+
+ return url;
+}
+
+/**
+ * oauth2_proxy_build_login_url:
+ * @proxy: an OAuth2Proxy object
+ * @redirect_uri: the uri to redirect to after the user authenticates
+ *
+ * Builds a url at which the user can log in to the specified OAuth2-based web
+ * service. See the documentation for oauth2_proxy_build_login_url_full() for
+ * detailed information.
+ *
+ * Returns: a newly allocated uri string
+ */
+char *
+oauth2_proxy_build_login_url (OAuth2Proxy *proxy,
+ const char* redirect_uri)
+{
+ return oauth2_proxy_build_login_url_full (proxy, redirect_uri, NULL);
+}
+
+/**
+ * oauth2_proxy_get_access_token:
+ * @proxy: an #OAuth2Proxy
+ *
+ * Get the current request or access token.
+ *
+ * Returns: the token, or %NULL if there is no token yet. This string is owned
+ * by #OAuth2Proxy and should not be freed.
+ */
+const char *
+oauth2_proxy_get_access_token (OAuth2Proxy *proxy)
+{
+ return proxy->priv->access_token;
+}
+
+/**
+ * oauth2_proxy_set_access_token:
+ * @proxy: an #OAuth2Proxy
+ * @token: the access token
+ *
+ * Set the access token.
+ */
+void
+oauth2_proxy_set_access_token (OAuth2Proxy *proxy, const char *access_token)
+{
+ g_return_if_fail (OAUTH2_IS_PROXY (proxy));
+
+ if (proxy->priv->access_token)
+ g_free (proxy->priv->access_token);
+
+ proxy->priv->access_token = g_strdup (access_token);
+}
+
+/**
+ * oauth2_proxy_extract_access_token:
+ * @url: the url which contains an access token in its fragment
+ *
+ * A utility function to extract the access token from the url that results from
+ * the redirection after the user authenticates
+ */
+char *
+oauth2_proxy_extract_access_token (const char *url)
+{
+ GHashTable *params;
+ char *token = NULL;
+ SoupURI *soupuri = soup_uri_new (url);
+
+ if (soupuri->fragment != NULL) {
+ params = soup_form_decode (soupuri->fragment);
+
+ if (params) {
+ char *encoded = g_hash_table_lookup (params, "access_token");
+ if (encoded)
+ token = soup_uri_decode (encoded);
+
+ g_hash_table_destroy (params);
+ }
+ }
+
+ return token;
+}