From 6d65d8862ad7d39e55be0adb868c05f194250e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Wagner?= Date: Wed, 12 Jan 2022 18:26:38 +0000 Subject: OAuth2 Pkce Workflow --- rest/Makefile.am | 139 ------- rest/meson.build | 28 +- rest/oauth2-proxy-call.c | 68 ---- rest/oauth2-proxy-call.h | 49 --- rest/oauth2-proxy-private.h | 34 -- rest/oauth2-proxy.c | 397 -------------------- rest/oauth2-proxy.h | 95 ----- rest/rest-oauth2-proxy-call.c | 63 ++++ rest/rest-oauth2-proxy-call.h | 33 ++ rest/rest-oauth2-proxy.c | 802 ++++++++++++++++++++++++++++++++++++++++ rest/rest-oauth2-proxy.h | 101 +++++ rest/rest-pkce-code-challenge.c | 121 ++++++ rest/rest-pkce-code-challenge.h | 44 +++ rest/rest-proxy-call.c | 18 +- rest/rest-proxy.c | 4 +- rest/rest-utils.c | 44 +++ rest/rest-utils.h | 27 ++ rest/rest.h | 35 ++ 18 files changed, 1306 insertions(+), 796 deletions(-) delete mode 100644 rest/Makefile.am delete mode 100644 rest/oauth2-proxy-call.c delete mode 100644 rest/oauth2-proxy-call.h delete mode 100644 rest/oauth2-proxy-private.h delete mode 100644 rest/oauth2-proxy.c delete mode 100644 rest/oauth2-proxy.h create mode 100644 rest/rest-oauth2-proxy-call.c create mode 100644 rest/rest-oauth2-proxy-call.h create mode 100644 rest/rest-oauth2-proxy.c create mode 100644 rest/rest-oauth2-proxy.h create mode 100644 rest/rest-pkce-code-challenge.c create mode 100644 rest/rest-pkce-code-challenge.h create mode 100644 rest/rest-utils.c create mode 100644 rest/rest-utils.h create mode 100644 rest/rest.h (limited to 'rest') diff --git a/rest/Makefile.am b/rest/Makefile.am deleted file mode 100644 index 64205d4..0000000 --- a/rest/Makefile.am +++ /dev/null @@ -1,139 +0,0 @@ -CLEANFILES = - -# For some reason I can't use $(librest_@API_VERSION@_la_SOURCES) in -# test_runner_SOURCES, so we have to do this -lib_sources = \ - rest-param.c \ - rest-params.c \ - rest-proxy.c \ - rest-proxy-auth.c \ - rest-proxy-auth-private.h \ - rest-proxy-call.c \ - rest-proxy-call-private.h \ - rest-xml-node.c \ - rest-xml-parser.c \ - rest-main.c \ - rest-private.h \ - rest-enum-types.c \ - oauth-proxy.c \ - oauth-proxy-call.c \ - oauth-proxy-private.h \ - oauth2-proxy.c \ - oauth2-proxy-call.c \ - oauth2-proxy-private.h \ - sha1.c \ - sha1.h -nodist_lib_sources = \ - rest-marshal.h \ - rest-marshal.c -lib_headers = \ - rest-param.h \ - rest-params.h \ - rest-proxy.h \ - rest-proxy-auth.h \ - rest-proxy-call.h \ - rest-enum-types.h \ - oauth-proxy.h \ - oauth-proxy-call.h \ - oauth2-proxy.h \ - oauth2-proxy-call.h \ - rest-xml-node.h \ - rest-xml-parser.h - -EXTRA_DIST = \ - rest-marshal.txt - -lib_LTLIBRARIES = librest-@API_VERSION@.la -librest_@API_VERSION@_la_CFLAGS = $(GLIB_CFLAGS) $(GTHREAD_CFLAGS) \ - $(SOUP_CFLAGS) $(SOUP_GNOME_CFLAGS) \ - $(XML_CFLAGS) $(GCOV_CFLAGS) \ - -I$(top_srcdir) -Wall -DG_LOG_DOMAIN=\"Rest\" -librest_@API_VERSION@_la_LDFLAGS = -no-undefined -librest_@API_VERSION@_la_LIBADD = $(GLIB_LIBS) $(GTHREAD_LIBS) \ - $(SOUP_LIBS) $(SOUP_GNOME_LIBS) $(XML_LIBS) \ - $(GCOV_LDFLAGS) -librest_@API_VERSION@_la_SOURCES = $(lib_sources) $(lib_headers) -nodist_librest_@API_VERSION@_la_SOURCES = $(nodist_lib_sources) -librest_@API_VERSION@_la_HEADERS = $(lib_headers) -librest_@API_VERSION@_ladir = $(includedir)/rest-@API_VERSION@/rest - -rest-enum-types.h: stamp-rest-enum-types.h - @true - -stamp-rest-enum-types.h: rest-proxy.h rest-proxy-call.h Makefile - $(AM_V_GEN) (cd $(srcdir) && $(GLIB_MKENUMS) \ - --fhead "#ifndef __REST_ENUM_TYPES_H__\n#define __REST_ENUM_TYPES_H__\n\n#include \n\nG_BEGIN_DECLS\n" \ - --fprod "/* enumerations from \"@filename@\" */\n" \ - --vhead "GType @enum_name@_get_type (void) G_GNUC_CONST;\n#define REST_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n" \ - --ftail "G_END_DECLS\n\n#endif /* __REST_ENUM_TYPES_H__ */" rest-proxy.h rest-proxy-call.h) >> xgen-gtbh \ - && (cmp -s xgen-gtbh rest-enum-types.h || cp xgen-gtbh rest-enum-types.h ) \ - && rm -f xgen-gtbh \ - && echo timestamp > $(@F) - -rest-enum-types.c: rest-proxy.h rest-proxy-call.h Makefile rest-enum-types.h - $(AM_V_GEN) (cd $(srcdir) && $(GLIB_MKENUMS) \ - --fhead "#include \"rest-proxy.h\"\n#include \"rest-proxy-call.h\"\n#include \"rest-enum-types.h\"" \ - --fprod "\n/* enumerations from \"@filename@\" */" \ - --vhead "GType\n@enum_name@_get_type (void)\n{\n static GType etype = 0;\n if (etype == 0) {\n static const G@Type@Value values[] = {" \ - --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \ - --vtail " { 0, NULL, NULL }\n };\n etype = g_@type@_register_static (\"@EnumName@\", values);\n }\n return etype;\n}\n" \ - rest-proxy.h rest-proxy-call.h) > xgen-gtbc \ - && cp xgen-gtbc rest-enum-types.c \ - && rm -f xgen-gtbc - -BUILT_SOURCES = rest-marshal.c rest-marshal.h - -rest-marshal.c: rest-marshal.txt - $(AM_V_GEN)echo "#include \"rest-marshal.h\"" > $@ && \ - glib-genmarshal --body $< >> $@ || (rm -f $@ && exit 1) - -rest-marshal.h: rest-marshal.txt - $(AM_V_GEN)glib-genmarshal --header $< > $@ || (rm -f $@ && exit 1) - - -# Test suite -TESTS = test-runner -check_PROGRAMS = test-runner - -test_runner_SOURCES = test-runner.c $(lib_sources) $(nodist_lib_sources) $(lib_headers) -test_runner_CFLAGS = -DBUILD_TESTS $(librest_@API_VERSION@_la_CFLAGS) $(GCOV_CFLAGS) -test_runner_LDFLAGS = $(librest_@API_VERSION@_la_LIBADD) $(GCOV_LDFLAGS) - -# TODO: use gtester - -# introspection --include $(INTROSPECTION_MAKEFILE) - -if HAVE_INTROSPECTION -INTROSPECTION_GIRS = Rest-@API_VERSION@.gir - -Rest-@API_VERSION@.gir: librest-@API_VERSION@.la Makefile - -Rest_@API_VERSION_AM@_gir_NAMESPACE = Rest -Rest_@API_VERSION_AM@_gir_VERSION = @API_VERSION@ -Rest_@API_VERSION_AM@_gir_LIBS = librest-@API_VERSION@.la -Rest_@API_VERSION_AM@_gir_FILES = \ - $(lib_headers) \ - $(filter-out %private.h, $(lib_sources)) -Rest_@API_VERSION_AM@_gir_CFLAGS = -I$(top_srcdir) -Rest_@API_VERSION_AM@_gir_INCLUDES = GObject-2.0 Gio-2.0 Soup-2.4 -Rest_@API_VERSION_AM@_gir_PACKAGES = gobject-2.0 libsoup-2.4 libxml-2.0 gio-2.0 -Rest_@API_VERSION_AM@_gir_SCANNERFLAGS = --accept-unprefixed --warn-all -Rest_@API_VERSION_AM@_gir_EXPORT_PACKAGES = rest-@API_VERSION@ -REST_CINCLUDES=$(patsubst %,--c-include='rest/%',$(shell echo $(lib_headers))) -INTROSPECTION_SCANNER_ARGS = $(REST_CINCLUDES) - -girdir = $(datadir)/gir-1.0 -dist_gir_DATA = $(INTROSPECTION_GIRS) - -typelibsdir = $(libdir)/girepository-1.0/ -typelibs_DATA = $(INTROSPECTION_GIRS:.gir=.typelib) - -CLEANFILES += $(dist_gir_DATA) \ - $(typelibs_DATA) \ - rest-enum-types.h \ - rest-enum-types.c \ - stamp-rest-enum-types.h \ - $(NULL) - -endif # HAVE_INTROSPECTION diff --git a/rest/meson.build b/rest/meson.build index 396ec83..a22742e 100644 --- a/rest/meson.build +++ b/rest/meson.build @@ -19,10 +19,13 @@ librest_sources = [ 'rest-main.c', 'oauth-proxy.c', 'oauth-proxy-call.c', - 'oauth2-proxy.c', - 'oauth2-proxy-call.c', 'sha1.c', + 'rest-oauth2-proxy.c', + 'rest-oauth2-proxy-call.c', + 'rest-pkce-code-challenge.c', + 'rest-utils.c', + librest_enums, librest_marshal, ] @@ -30,8 +33,6 @@ librest_sources = [ librest_headers = [ 'oauth-proxy-call.h', 'oauth-proxy.h', - 'oauth2-proxy-call.h', - 'oauth2-proxy.h', 'rest-param.h', 'rest-params.h', 'rest-proxy-auth.h', @@ -39,11 +40,18 @@ librest_headers = [ 'rest-proxy.h', 'rest-xml-node.h', 'rest-xml-parser.h', + + 'rest-oauth2-proxy.h', + 'rest-oauth2-proxy-call.h', + 'rest-pkce-code-challenge.h', + 'rest-utils.h', + 'rest.h', ] librest_deps = [ glib_dep, libsoup_dep, + libjson_glib_dep, libxml_dep, ] @@ -65,6 +73,8 @@ install_headers(librest_headers, subdir: librest_pkg_string / 'rest', ) +rest_dep_sources = [] + # GObject Introspection if get_option('introspection') librest_gir_extra_args = [ @@ -77,17 +87,25 @@ if get_option('introspection') endforeach librest_gir = gnome.generate_gir(librest_lib, - sources: [ librest_headers, librest_enums[1] ], + sources: [ librest_headers, librest_sources, librest_enums[1] ], + dependencies: librest_deps, namespace: 'Rest', + identifier_prefix: 'Rest', + symbol_prefix: 'rest', nsversion: librest_api_version, includes: [ 'GObject-2.0', 'Gio-2.0', 'Soup-@0@'.format(libsoup_api_version) ], extra_args: librest_gir_extra_args, install: true, ) + + rest_dep_sources += librest_gir endif librest_dep = declare_dependency( + sources: rest_dep_sources, link_with: librest_lib, + include_directories: include_directories('..'), + dependencies: librest_deps, ) # Test suite diff --git a/rest/oauth2-proxy-call.c b/rest/oauth2-proxy-call.c deleted file mode 100644 index 2d71b45..0000000 --- a/rest/oauth2-proxy-call.c +++ /dev/null @@ -1,68 +0,0 @@ -/* - * librest - RESTful web services access - * Copyright (c) 2008, 2009, Intel Corporation. - * - * Authors: Rob Bradford - * Ross Burton - * Jonathon Jongsma - * - * 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 -#include -#include -#include "oauth2-proxy-call.h" -#include "oauth2-proxy-private.h" -#include "sha1.h" - -G_DEFINE_TYPE (OAuth2ProxyCall, oauth2_proxy_call, REST_TYPE_PROXY_CALL) - -static gboolean -_prepare (RestProxyCall *call, GError **error) -{ - OAuth2Proxy *proxy = NULL; - gboolean result = TRUE; - - g_object_get (call, "proxy", &proxy, NULL); - - if (!proxy->priv->access_token) { - g_set_error (error, - REST_PROXY_CALL_ERROR, - REST_PROXY_CALL_FAILED, - "Missing access token, web service not properly authenticated"); - result = FALSE; - } else { - rest_proxy_call_add_param (call, "access_token", proxy->priv->access_token); - } - - g_object_unref (proxy); - - return result; -} - -static void -oauth2_proxy_call_class_init (OAuth2ProxyCallClass *klass) -{ - RestProxyCallClass *call_class = REST_PROXY_CALL_CLASS (klass); - - call_class->prepare = _prepare; -} - -static void -oauth2_proxy_call_init (OAuth2ProxyCall *self) -{ -} - diff --git a/rest/oauth2-proxy-call.h b/rest/oauth2-proxy-call.h deleted file mode 100644 index 805a501..0000000 --- a/rest/oauth2-proxy-call.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * librest - RESTful web services access - * Copyright (c) 2008, 2009, Intel Corporation. - * - * Authors: Rob Bradford - * Ross Burton - * Jonathon Jongsma - * - * 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. - * - */ - -#ifndef _OAUTH2_PROXY_CALL -#define _OAUTH2_PROXY_CALL - -#include - -G_BEGIN_DECLS - -#define OAUTH2_TYPE_PROXY_CALL oauth2_proxy_call_get_type() -G_DECLARE_DERIVABLE_TYPE (OAuth2ProxyCall, oauth2_proxy_call, OAUTH2, PROXY_CALL, RestProxyCall) - -/** - * OAuth2ProxyCall: - * - * #OAuth2ProxyCall has no publicly available members. - */ - -struct _OAuth2ProxyCallClass { - RestProxyCallClass parent_class; - /*< private >*/ - /* padding for future expansion */ - gpointer _padding_dummy[8]; -}; - -G_END_DECLS - -#endif /* _OAUTH2_PROXY_CALL */ diff --git a/rest/oauth2-proxy-private.h b/rest/oauth2-proxy-private.h deleted file mode 100644 index 565c1c3..0000000 --- a/rest/oauth2-proxy-private.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * librest - RESTful web services access - * Copyright (c) 2008, 2009, 2010 Intel Corporation. - * - * Authors: Rob Bradford - * Ross Burton - * Jonathon Jongsma - * - * 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. - * - */ -#ifndef _OAUTH2_PROXY_PRIVATE -#define _OAUTH2_PROXY_PRIVATE - -#include "oauth2-proxy.h" - -struct _OAuth2ProxyPrivate { - char *client_id; - char *auth_endpoint; - char *access_token; -}; - -#endif /* _OAUTH2_PROXY_PRIVATE */ diff --git a/rest/oauth2-proxy.c b/rest/oauth2-proxy.c deleted file mode 100644 index 3382f8b..0000000 --- a/rest/oauth2-proxy.c +++ /dev/null @@ -1,397 +0,0 @@ -/* - * librest - RESTful web services access - * Copyright (c) 2008, 2009, 2010 Intel Corporation. - * - * Authors: Rob Bradford - * Ross Burton - * Jonathon Jongsma - * - * 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 -#include -#include "oauth2-proxy.h" -#include "oauth2-proxy-private.h" -#include "oauth2-proxy-call.h" - -#define GET_PRIVATE(o) oauth2_proxy_get_instance_private(OAUTH2_PROXY(o)) - -G_DEFINE_TYPE_WITH_PRIVATE (OAuth2Proxy, oauth2_proxy, REST_TYPE_PROXY) - -GQuark -oauth2_proxy_error_quark (void) -{ - return g_quark_from_static_string ("rest-oauth2-proxy"); -} - -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; - - 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 = 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 = g_uri_escape_string (value, NULL, TRUE); - encoded_key = g_uri_escape_string (key, NULL, TRUE); - - 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 = g_uri_escape_string (redirect_uri, NULL, TRUE); - encoded_id = g_uri_escape_string (proxy->priv->client_id, NULL, TRUE); - - 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 - * @access_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; - const char *fragment; - GUri *uri = g_uri_parse (url, G_URI_FLAGS_ENCODED, NULL); - - fragment = g_uri_get_fragment (uri); - if (fragment != NULL) { - params = soup_form_decode (fragment); - - if (params) { - char *encoded = g_hash_table_lookup (params, "access_token"); - if (encoded) - token = g_uri_unescape_string (encoded, NULL); - - g_hash_table_destroy (params); - } - } - g_uri_unref (uri); - - return token; -} diff --git a/rest/oauth2-proxy.h b/rest/oauth2-proxy.h deleted file mode 100644 index 0473c62..0000000 --- a/rest/oauth2-proxy.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * librest - RESTful web services access - * Copyright (c) 2008, 2009, 2010 Intel Corporation. - * - * Authors: Rob Bradford - * Ross Burton - * Jonathon Jongsma - * - * 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. - * - */ - -#ifndef _OAUTH2_PROXY -#define _OAUTH2_PROXY - -#include - -G_BEGIN_DECLS - -#define OAUTH2_TYPE_PROXY oauth2_proxy_get_type() - -#define OAUTH2_PROXY(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST ((obj), OAUTH2_TYPE_PROXY, OAuth2Proxy)) - -#define OAUTH2_PROXY_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST ((klass), OAUTH2_TYPE_PROXY, OAuth2ProxyClass)) - -#define OAUTH2_IS_PROXY(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE ((obj), OAUTH2_TYPE_PROXY)) - -#define OAUTH2_IS_PROXY_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE ((klass), OAUTH2_TYPE_PROXY)) - -#define OAUTH2_PROXY_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS ((obj), OAUTH2_TYPE_PROXY, OAuth2ProxyClass)) - -typedef struct _OAuth2ProxyPrivate OAuth2ProxyPrivate; - -/** - * OAuth2Proxy: - * - * #OAuth2Proxy has no publicly available members. - */ -typedef struct { - RestProxy parent; - OAuth2ProxyPrivate *priv; -} OAuth2Proxy; - -typedef struct { - RestProxyClass parent_class; - /*< private >*/ - /* padding for future expansion */ - gpointer _padding_dummy[8]; -} OAuth2ProxyClass; - -GType oauth2_proxy_get_type (void); - -RestProxy* oauth2_proxy_new (const char *client_id, - const char *auth_endpoint, - const gchar *url_format, - gboolean binding_required); - -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); - -char * oauth2_proxy_build_login_url_full (OAuth2Proxy *proxy, - const char* redirect_uri, - GHashTable* extra_params); - -char * oauth2_proxy_build_login_url (OAuth2Proxy *proxy, - const char* redirect_uri); - -const char * oauth2_proxy_get_access_token (OAuth2Proxy *proxy); - -void oauth2_proxy_set_access_token (OAuth2Proxy *proxy, const char *access_token); - -char * oauth2_proxy_extract_access_token (const char *url); - -G_END_DECLS - -#endif /* _OAUTH2_PROXY */ diff --git a/rest/rest-oauth2-proxy-call.c b/rest/rest-oauth2-proxy-call.c new file mode 100644 index 0000000..cbea6f5 --- /dev/null +++ b/rest/rest-oauth2-proxy-call.c @@ -0,0 +1,63 @@ +/* rest-oauth2-proxy-call.c + * + * Copyright 2021 Günther Wagner + * + * 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-oauth2-proxy-call.h" + +G_DEFINE_TYPE (RestOAuth2ProxyCall, rest_oauth2_proxy_call, REST_TYPE_PROXY_CALL) + +static gboolean +rest_oauth2_proxy_call_prepare (RestProxyCall *call, + GError **error) +{ + RestOAuth2ProxyCall *self = (RestOAuth2ProxyCall *)call; + RestOAuth2Proxy *proxy = NULL; + g_autoptr(GDateTime) now = NULL; + GDateTime *expiration_date = NULL; + + g_return_val_if_fail (REST_IS_OAUTH2_PROXY_CALL (call), FALSE); + + g_object_get (call, "proxy", &proxy, NULL); + + now = g_date_time_new_now_local (); + expiration_date = rest_oauth2_proxy_get_expiration_date (proxy); + + // access token expired -> refresh + if (g_date_time_compare (now, expiration_date) > 0) + { + g_set_error (error, + REST_OAUTH2_ERROR, + REST_OAUTH2_ERROR_ACCESS_TOKEN_EXPIRED, + "Access token is expired"); + return FALSE; + } + + return TRUE; +} + +static void +rest_oauth2_proxy_call_class_init (RestOAuth2ProxyCallClass *klass) +{ + RestProxyCallClass *call_class = REST_PROXY_CALL_CLASS (klass); + + call_class->prepare = rest_oauth2_proxy_call_prepare; +} + +static void +rest_oauth2_proxy_call_init (RestOAuth2ProxyCall *self) +{ +} diff --git a/rest/rest-oauth2-proxy-call.h b/rest/rest-oauth2-proxy-call.h new file mode 100644 index 0000000..fafc61f --- /dev/null +++ b/rest/rest-oauth2-proxy-call.h @@ -0,0 +1,33 @@ +/* rest-oauth2-proxy-call.h + * + * Copyright 2021 Günther Wagner + * + * 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. + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define REST_TYPE_OAUTH2_PROXY_CALL (rest_oauth2_proxy_call_get_type()) + +G_DECLARE_DERIVABLE_TYPE (RestOAuth2ProxyCall, rest_oauth2_proxy_call, REST, OAUTH2_PROXY_CALL, RestProxyCall) + +struct _RestOAuth2ProxyCallClass { + RestProxyCallClass parent_class; +}; + +G_END_DECLS diff --git a/rest/rest-oauth2-proxy.c b/rest/rest-oauth2-proxy.c new file mode 100644 index 0000000..f15b589 --- /dev/null +++ b/rest/rest-oauth2-proxy.c @@ -0,0 +1,802 @@ +/* rest-oauth2-proxy.c + * + * Copyright 2021 Günther Wagner + * + * 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-oauth2-proxy.h" +#include "rest-oauth2-proxy-call.h" +#include "rest-utils.h" +#include "rest-private.h" +#include + +typedef struct +{ + gchar *authurl; + gchar *tokenurl; + gchar *redirect_uri; + gchar *client_id; + gchar *client_secret; + + gchar *access_token; + gchar *refresh_token; + + GDateTime *expiration_date; +} RestOAuth2ProxyPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (RestOAuth2Proxy, rest_oauth2_proxy, REST_TYPE_PROXY) + +G_DEFINE_QUARK (rest-oauth2-error-quark, rest_oauth2_error) + +enum { + PROP_0, + PROP_AUTH_URL, + PROP_TOKEN_URL, + PROP_REDIRECT_URI, + PROP_CLIENT_ID, + PROP_CLIENT_SECRET, + PROP_ACCESS_TOKEN, + PROP_REFRESH_TOKEN, + PROP_EXPIRATION_DATE, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +static void +rest_oauth2_proxy_parse_access_token (RestOAuth2Proxy *self, + GBytes *payload, + GTask *task) +{ + g_autoptr(JsonParser) parser = NULL; + g_autoptr(GError) error = NULL; + JsonNode *root; + JsonObject *root_object; + const gchar *data; + gsize size; + gint expires_in; + gint created_at; + + g_return_if_fail (REST_IS_OAUTH2_PROXY (self)); + + data = g_bytes_get_data (payload, &size); + + parser = json_parser_new (); + json_parser_load_from_data (parser, data, size, &error); + if (error != NULL) + { + g_task_return_error (task, error); + return; + } + + root = json_parser_get_root (parser); + root_object = json_node_get_object (root); + + if (json_object_has_member (root_object, "access_token")) + rest_oauth2_proxy_set_access_token (self, json_object_get_string_member (root_object, "access_token")); + if (json_object_has_member (root_object, "refresh_token")) + rest_oauth2_proxy_set_refresh_token (self, json_object_get_string_member (root_object, "refresh_token")); + + if (json_object_has_member (root_object, "expires_in") && json_object_has_member (root_object, "created_at")) + { + expires_in = json_object_get_int_member (root_object, "expires_in"); + created_at = json_object_get_int_member (root_object, "created_at"); + + rest_oauth2_proxy_set_expiration_date (self, g_date_time_new_from_unix_local (created_at+expires_in)); + } + else if (json_object_has_member (root_object, "expires_in")) + { + g_autoptr(GDateTime) now = g_date_time_new_now_utc (); + expires_in = json_object_get_int_member (root_object, "expires_in"); + rest_oauth2_proxy_set_expiration_date (self, g_date_time_add_seconds (now, expires_in)); + } + + g_task_return_boolean (task, TRUE); +} + +RestProxyCall * +rest_oauth2_proxy_new_call (RestProxy *proxy) +{ + RestOAuth2Proxy *self = (RestOAuth2Proxy *)proxy; + RestProxyCall *call; + g_autofree gchar *auth; + + g_return_val_if_fail (REST_IS_OAUTH2_PROXY (self), NULL); + + auth = g_strdup_printf ("Bearer %s", rest_oauth2_proxy_get_access_token (self)); + + call = g_object_new (REST_TYPE_OAUTH2_PROXY_CALL, "proxy", proxy, NULL); + rest_proxy_call_add_header (call, "Authorization", auth); + + return call; +} + +/** + * rest_oauth2_proxy_new: + * + * Create a new #RestOAuth2Proxy. + * + * Returns: (transfer full): a newly created #RestOAuth2Proxy + */ +RestOAuth2Proxy * +rest_oauth2_proxy_new (const gchar *authurl, + const gchar *tokenurl, + const gchar *redirecturl, + const gchar *client_id, + const gchar *client_secret, + const gchar *baseurl) +{ + return g_object_new (REST_TYPE_OAUTH2_PROXY, + "url-format", baseurl, + "auth-url", authurl, + "token-url", tokenurl, + "redirect-uri", redirecturl, + "client-id", client_id, + "client-secret", client_secret, + NULL); +} + +static void +rest_oauth2_proxy_finalize (GObject *object) +{ + RestOAuth2Proxy *self = (RestOAuth2Proxy *)object; + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + + g_clear_pointer (&priv->authurl, g_free); + g_clear_pointer (&priv->tokenurl, g_free); + g_clear_pointer (&priv->redirect_uri, g_free); + g_clear_pointer (&priv->client_id, g_free); + g_clear_pointer (&priv->client_secret, g_free); + g_clear_pointer (&priv->access_token, g_free); + g_clear_pointer (&priv->refresh_token, g_free); + g_clear_pointer (&priv->expiration_date, g_date_time_unref); + + G_OBJECT_CLASS (rest_oauth2_proxy_parent_class)->finalize (object); +} + +static void +rest_oauth2_proxy_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + RestOAuth2Proxy *self = REST_OAUTH2_PROXY (object); + + switch (prop_id) + { + case PROP_AUTH_URL: + g_value_set_string (value, rest_oauth2_proxy_get_auth_url (self)); + break; + case PROP_TOKEN_URL: + g_value_set_string (value, rest_oauth2_proxy_get_token_url (self)); + break; + case PROP_REDIRECT_URI: + g_value_set_string (value, rest_oauth2_proxy_get_redirect_uri (self)); + break; + case PROP_CLIENT_ID: + g_value_set_string (value, rest_oauth2_proxy_get_client_id (self)); + break; + case PROP_CLIENT_SECRET: + g_value_set_string (value, rest_oauth2_proxy_get_client_secret (self)); + break; + case PROP_ACCESS_TOKEN: + g_value_set_string (value, rest_oauth2_proxy_get_access_token (self)); + break; + case PROP_REFRESH_TOKEN: + g_value_set_string (value, rest_oauth2_proxy_get_refresh_token (self)); + break; + case PROP_EXPIRATION_DATE: + g_value_set_boxed (value, rest_oauth2_proxy_get_expiration_date (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +rest_oauth2_proxy_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + RestOAuth2Proxy *self = REST_OAUTH2_PROXY (object); + + switch (prop_id) + { + case PROP_AUTH_URL: + rest_oauth2_proxy_set_auth_url (self, g_value_get_string (value)); + break; + case PROP_TOKEN_URL: + rest_oauth2_proxy_set_token_url (self, g_value_get_string (value)); + break; + case PROP_REDIRECT_URI: + rest_oauth2_proxy_set_redirect_uri (self, g_value_get_string (value)); + break; + case PROP_CLIENT_ID: + rest_oauth2_proxy_set_client_id (self, g_value_get_string (value)); + break; + case PROP_CLIENT_SECRET: + rest_oauth2_proxy_set_client_secret (self, g_value_get_string (value)); + break; + case PROP_ACCESS_TOKEN: + rest_oauth2_proxy_set_access_token (self, g_value_get_string (value)); + break; + case PROP_REFRESH_TOKEN: + rest_oauth2_proxy_set_refresh_token (self, g_value_get_string (value)); + break; + case PROP_EXPIRATION_DATE: + rest_oauth2_proxy_set_expiration_date (self, g_value_get_boxed (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +rest_oauth2_proxy_class_init (RestOAuth2ProxyClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + RestOAuth2ProxyClass *oauth2_class = REST_OAUTH2_PROXY_CLASS (klass); + RestProxyClass *proxy_class = REST_PROXY_CLASS (klass); + + object_class->finalize = rest_oauth2_proxy_finalize; + object_class->get_property = rest_oauth2_proxy_get_property; + object_class->set_property = rest_oauth2_proxy_set_property; + oauth2_class->parse_access_token = rest_oauth2_proxy_parse_access_token; + proxy_class->new_call = rest_oauth2_proxy_new_call; + + properties [PROP_AUTH_URL] = + g_param_spec_string ("auth-url", + "AuthUrl", + "AuthUrl", + "", + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_TOKEN_URL] = + g_param_spec_string ("token-url", + "TokenUrl", + "TokenUrl", + "", + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_REDIRECT_URI] = + g_param_spec_string ("redirect-uri", + "RedirectUri", + "RedirectUri", + "", + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_CLIENT_ID] = + g_param_spec_string ("client-id", + "ClientId", + "ClientId", + "", + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_CLIENT_SECRET] = + g_param_spec_string ("client-secret", + "ClientSecret", + "ClientSecret", + "", + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_ACCESS_TOKEN] = + g_param_spec_string ("access-token", + "AccessToken", + "AccessToken", + NULL, + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_REFRESH_TOKEN] = + g_param_spec_string ("refresh-token", + "RefreshToken", + "RefreshToken", + NULL, + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_EXPIRATION_DATE] = + g_param_spec_boxed ("expiration-date", + "ExpirationDate", + "ExpirationDate", + G_TYPE_DATE_TIME, + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +rest_oauth2_proxy_init (RestOAuth2Proxy *self) +{ +} + +/** + * rest_oauth2_proxy_build_authorization_url: + * @self: a #RestOAuth2Proxy + * @code_challenge: the code challenge (see #RestPkceCodeChallenge) + * @scope: (nullable): the requesting scope of the resource + * @state: (out): a CRSF token which should be verified from the redirect_uri + * + * + * Returns: (transfer full): the authorization url which should be shown in a WebView in order to accept/decline the request + * to authorize the application + * + * Since: 0.8 + */ +gchar * +rest_oauth2_proxy_build_authorization_url (RestOAuth2Proxy *self, + const gchar *code_challenge, + const gchar *scope, + gchar **state) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + g_autoptr(GHashTable) params = NULL; + g_autoptr(GUri) auth = NULL; + g_autoptr(GUri) authorization_url = NULL; + g_autofree gchar *params_string; + + g_return_val_if_fail (REST_IS_OAUTH2_PROXY (self), NULL); + + if (state != NULL) + *state = random_string (10); + params = g_hash_table_new (g_str_hash, g_str_equal); + + g_hash_table_insert (params, "response_type", "code"); + g_hash_table_insert (params, "client_id", priv->client_id); + g_hash_table_insert (params, "redirect_uri", priv->redirect_uri); + if (state != NULL) + g_hash_table_insert (params, "state", *state); + g_hash_table_insert (params, "code_challenge", (gchar *)code_challenge); + g_hash_table_insert (params, "code_challenge_method", "S256"); + if (scope) + g_hash_table_insert (params, "scope", (gchar *)scope); + + params_string = soup_form_encode_hash (params); + auth = g_uri_parse (priv->authurl, G_URI_FLAGS_NONE, NULL); + authorization_url = g_uri_build (G_URI_FLAGS_ENCODED, + g_uri_get_scheme (auth), + NULL, + g_uri_get_host (auth), + g_uri_get_port (auth), + g_uri_get_path (auth), + params_string, + NULL); + return g_uri_to_string (authorization_url); +} + +static void +rest_oauth2_proxy_fetch_access_token_cb (SoupMessage *msg, + GBytes *body, + GError *error, + gpointer user_data) +{ + g_autoptr(GTask) task = user_data; + RestOAuth2Proxy *self; + + g_assert (G_IS_TASK (task)); + + self = g_task_get_source_object (task); + + if (error) + { + g_task_return_error (task, error); + return; + } + + REST_OAUTH2_PROXY_GET_CLASS (self)->parse_access_token (self, body, g_steal_pointer (&task)); +} + +void +rest_oauth2_proxy_fetch_access_token_async (RestOAuth2Proxy *self, + const gchar *authorization_code, + const gchar *code_verifier, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + g_autoptr(SoupMessage) msg = NULL; + g_autoptr(GTask) task = NULL; + g_autoptr(GHashTable) params = NULL; + + g_return_if_fail (REST_IS_OAUTH2_PROXY (self)); + g_return_if_fail (authorization_code != NULL); + + task = g_task_new (self, cancellable, callback, user_data); + params = g_hash_table_new (g_str_hash, g_str_equal); + + g_hash_table_insert (params, "client_id", priv->client_id); + g_hash_table_insert (params, "grant_type", "authorization_code"); + g_hash_table_insert (params, "code", (gchar *)authorization_code); + g_hash_table_insert (params, "redirect_uri", priv->redirect_uri); + g_hash_table_insert (params, "code_verifier", (gchar *)code_verifier); + +#if WITH_SOUP_2 + msg = soup_form_request_new_from_hash (SOUP_METHOD_POST, priv->tokenurl, params); +#else + msg = soup_message_new_from_encoded_form (SOUP_METHOD_POST, priv->tokenurl, soup_form_encode_hash (params)); +#endif + + _rest_proxy_queue_message (REST_PROXY (self), +#if WITH_SOUP_2 + g_steal_pointer (&msg), +#else + msg, +#endif + cancellable, rest_oauth2_proxy_fetch_access_token_cb, g_steal_pointer (&task)); + +} + +/** + * rest_oauth2_proxy_fetch_access_token_finish: + * @self: an #RestOauth2Proxy + * @result: a #GAsyncResult provided to callback + * @error: a location for a #GError, or %NULL + * + * Returns: + */ +gboolean +rest_oauth2_proxy_fetch_access_token_finish (RestOAuth2Proxy *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (REST_IS_OAUTH2_PROXY (self), FALSE); + g_return_val_if_fail (g_task_is_valid (result, self), FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +gboolean +rest_oauth2_proxy_refresh_access_token (RestOAuth2Proxy *self, + GError **error) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + g_autoptr(SoupMessage) msg = NULL; + g_autoptr(GHashTable) params = NULL; + g_autoptr(GTask) task = NULL; + GBytes *payload; + + task = g_task_new (self, NULL, NULL, NULL); + + g_return_val_if_fail (REST_IS_OAUTH2_PROXY (self), FALSE); + + if (priv->refresh_token == NULL) + { + *error = g_error_new (REST_OAUTH2_ERROR, + REST_OAUTH2_ERROR_NO_REFRESH_TOKEN, + "No refresh token available"); + return FALSE; + } + + params = g_hash_table_new (g_str_hash, g_str_equal); + + g_hash_table_insert (params, "client_id", priv->client_id); + g_hash_table_insert (params, "refresh_token", priv->refresh_token); + g_hash_table_insert (params, "redirect_uri", priv->redirect_uri); + g_hash_table_insert (params, "grant_type", "refresh_token"); + +#if WITH_SOUP_2 + msg = soup_form_request_new_from_hash (SOUP_METHOD_POST, priv->tokenurl, params); +#else + msg = soup_message_new_from_encoded_form (SOUP_METHOD_POST, priv->tokenurl, soup_form_encode_hash (params)); +#endif + payload = _rest_proxy_send_message (REST_PROXY (self), msg, NULL, error); + if (error && *error) + { + return FALSE; + } + + REST_OAUTH2_PROXY_GET_CLASS (self)->parse_access_token (self, payload, g_steal_pointer (&task)); + return TRUE; +} + +static void +rest_oauth2_proxy_refresh_access_token_cb (SoupMessage *msg, + GBytes *payload, + GError *error, + gpointer user_data) +{ + g_autoptr(GTask) task = user_data; + RestOAuth2Proxy *self; + + g_assert (G_IS_TASK (task)); + + self = g_task_get_source_object (task); + + if (error) + { + g_task_return_error (task, error); + return; + } + + REST_OAUTH2_PROXY_GET_CLASS (self)->parse_access_token (self, payload, g_steal_pointer (&task)); +} + +void +rest_oauth2_proxy_refresh_access_token_async (RestOAuth2Proxy *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + g_autoptr(SoupMessage) msg = NULL; + g_autoptr(GHashTable) params = NULL; + g_autoptr(GTask) task = NULL; + + task = g_task_new (self, cancellable, callback, user_data); + + g_return_if_fail (REST_IS_OAUTH2_PROXY (self)); + + if (priv->refresh_token == NULL) + { + g_task_return_new_error (task, + REST_OAUTH2_ERROR, + REST_OAUTH2_ERROR_NO_REFRESH_TOKEN, + "No refresh token available"); + return; + } + + params = g_hash_table_new (g_str_hash, g_str_equal); + + g_hash_table_insert (params, "client_id", priv->client_id); + g_hash_table_insert (params, "refresh_token", priv->refresh_token); + g_hash_table_insert (params, "redirect_uri", priv->redirect_uri); + g_hash_table_insert (params, "grant_type", "refresh_token"); + +#if WITH_SOUP_2 + msg = soup_form_request_new_from_hash (SOUP_METHOD_POST, priv->tokenurl, params); +#else + msg = soup_message_new_from_encoded_form (SOUP_METHOD_POST, priv->tokenurl, soup_form_encode_hash (params)); +#endif + _rest_proxy_queue_message (REST_PROXY (self), +#if WITH_SOUP_2 + g_steal_pointer (&msg), +#else + msg, +#endif + cancellable, + rest_oauth2_proxy_refresh_access_token_cb, + g_steal_pointer (&task)); +} + +/** + * rest_oauth2_proxy_refresh_access_token_finish: + * @self: an #RestOauth2Proxy + * @result: a #GAsyncResult provided to callback + * @error: a location for a #GError, or %NULL + * + * Returns: + */ +gboolean +rest_oauth2_proxy_refresh_access_token_finish (RestOAuth2Proxy *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (REST_IS_OAUTH2_PROXY (self), FALSE); + g_return_val_if_fail (g_task_is_valid (result, self), FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +const gchar * +rest_oauth2_proxy_get_auth_url (RestOAuth2Proxy *self) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + + g_return_val_if_fail (REST_IS_OAUTH2_PROXY (self), NULL); + + return priv->authurl; +} + +void +rest_oauth2_proxy_set_auth_url (RestOAuth2Proxy *self, + const gchar *authurl) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + + g_return_if_fail (REST_IS_OAUTH2_PROXY (self)); + + if (g_strcmp0 (priv->authurl, authurl) != 0) + { + g_clear_pointer (&priv->authurl, g_free); + priv->authurl = g_strdup (authurl); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_AUTH_URL]); + } +} + +const gchar * +rest_oauth2_proxy_get_token_url (RestOAuth2Proxy *self) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + + g_return_val_if_fail (REST_IS_OAUTH2_PROXY (self), NULL); + + return priv->tokenurl; +} + +void +rest_oauth2_proxy_set_token_url (RestOAuth2Proxy *self, + const gchar *tokenurl) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + + g_return_if_fail (REST_IS_OAUTH2_PROXY (self)); + + if (g_strcmp0 (priv->tokenurl, tokenurl) != 0) + { + g_clear_pointer (&priv->tokenurl, g_free); + priv->tokenurl = g_strdup (tokenurl); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TOKEN_URL]); + } +} + +const gchar * +rest_oauth2_proxy_get_redirect_uri (RestOAuth2Proxy *self) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + + g_return_val_if_fail (REST_IS_OAUTH2_PROXY (self), NULL); + + return priv->redirect_uri; +} + +void +rest_oauth2_proxy_set_redirect_uri (RestOAuth2Proxy *self, + const gchar *redirect_uri) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + + g_return_if_fail (REST_IS_OAUTH2_PROXY (self)); + + if (g_strcmp0 (priv->redirect_uri, redirect_uri) != 0) + { + g_clear_pointer (&priv->redirect_uri, g_free); + priv->redirect_uri = g_strdup (redirect_uri); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_REDIRECT_URI]); + } +} + +const gchar * +rest_oauth2_proxy_get_client_id (RestOAuth2Proxy *self) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + + g_return_val_if_fail (REST_IS_OAUTH2_PROXY (self), NULL); + + return priv->client_id; +} + +void +rest_oauth2_proxy_set_client_id (RestOAuth2Proxy *self, + const gchar *client_id) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + + g_return_if_fail (REST_IS_OAUTH2_PROXY (self)); + + if (g_strcmp0 (priv->client_id, client_id) != 0) + { + g_clear_pointer (&priv->client_id, g_free); + priv->client_id = g_strdup (client_id); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CLIENT_ID]); + } +} + +const gchar * +rest_oauth2_proxy_get_client_secret (RestOAuth2Proxy *self) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + + g_return_val_if_fail (REST_IS_OAUTH2_PROXY (self), NULL); + + return priv->client_secret; +} + +void +rest_oauth2_proxy_set_client_secret (RestOAuth2Proxy *self, + const gchar *client_secret) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + + g_return_if_fail (REST_IS_OAUTH2_PROXY (self)); + + if (g_strcmp0 (priv->client_secret, client_secret) != 0) + { + g_clear_pointer (&priv->client_secret, g_free); + priv->client_secret = g_strdup (client_secret); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CLIENT_SECRET]); + } +} + +const gchar * +rest_oauth2_proxy_get_access_token (RestOAuth2Proxy *self) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + + g_return_val_if_fail (REST_IS_OAUTH2_PROXY (self), NULL); + + return priv->access_token; +} + +void +rest_oauth2_proxy_set_access_token (RestOAuth2Proxy *self, + const gchar *access_token) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + + g_return_if_fail (REST_IS_OAUTH2_PROXY (self)); + + if (g_strcmp0 (priv->access_token, access_token) != 0) + { + g_clear_pointer (&priv->access_token, g_free); + priv->access_token = g_strdup (access_token); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACCESS_TOKEN]); + } +} + +const gchar * +rest_oauth2_proxy_get_refresh_token (RestOAuth2Proxy *self) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + + g_return_val_if_fail (REST_IS_OAUTH2_PROXY (self), NULL); + + return priv->refresh_token; +} + +void +rest_oauth2_proxy_set_refresh_token (RestOAuth2Proxy *self, + const gchar *refresh_token) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + + g_return_if_fail (REST_IS_OAUTH2_PROXY (self)); + + if (g_strcmp0 (priv->refresh_token, refresh_token) != 0) + { + g_clear_pointer (&priv->refresh_token, g_free); + priv->refresh_token = g_strdup (refresh_token); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_REFRESH_TOKEN]); + } +} + +GDateTime * +rest_oauth2_proxy_get_expiration_date (RestOAuth2Proxy *self) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + g_return_val_if_fail (REST_IS_OAUTH2_PROXY (self), NULL); + + return priv->expiration_date; +} + +void +rest_oauth2_proxy_set_expiration_date (RestOAuth2Proxy *self, + GDateTime *expiration_date) +{ + RestOAuth2ProxyPrivate *priv = rest_oauth2_proxy_get_instance_private (self); + + g_return_if_fail (REST_IS_OAUTH2_PROXY (self)); + + g_clear_pointer (&priv->expiration_date, g_date_time_unref); + priv->expiration_date = g_date_time_ref (expiration_date); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXPIRATION_DATE]); +} diff --git a/rest/rest-oauth2-proxy.h b/rest/rest-oauth2-proxy.h new file mode 100644 index 0000000..dd4148e --- /dev/null +++ b/rest/rest-oauth2-proxy.h @@ -0,0 +1,101 @@ +/* rest-oauth2-proxy.h + * + * Copyright 2021 Günther Wagner + * + * 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. + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define REST_TYPE_OAUTH2_PROXY (rest_oauth2_proxy_get_type()) + +G_DECLARE_DERIVABLE_TYPE (RestOAuth2Proxy, rest_oauth2_proxy, REST, OAUTH2_PROXY, RestProxy) + +struct _RestOAuth2ProxyClass +{ + RestProxyClass parent_class; + + void (*parse_access_token) (RestOAuth2Proxy *self, + GBytes *payload, + GTask *task); + + gpointer padding[8]; +}; + +enum { + REST_OAUTH2_ERROR_NO_REFRESH_TOKEN, + REST_OAUTH2_ERROR_ACCESS_TOKEN_EXPIRED, +}; + +#define REST_OAUTH2_ERROR rest_oauth2_error_quark () +GQuark rest_oauth2_error_quark (); + +RestOAuth2Proxy *rest_oauth2_proxy_new (const gchar *authurl, + const gchar *tokenurl, + const gchar *redirecturl, + const gchar *client_id, + const gchar *client_secret, + const gchar *baseurl); +gchar *rest_oauth2_proxy_build_authorization_url (RestOAuth2Proxy *self, + const gchar *code_challenge, + const gchar *scope, + gchar **state); +void rest_oauth2_proxy_fetch_access_token_async (RestOAuth2Proxy *self, + const gchar *authorization_code, + const gchar *code_verifier, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean rest_oauth2_proxy_fetch_access_token_finish (RestOAuth2Proxy *self, + GAsyncResult *result, + GError **error); +gboolean rest_oauth2_proxy_refresh_access_token (RestOAuth2Proxy *self, + GError **error); +void rest_oauth2_proxy_refresh_access_token_async (RestOAuth2Proxy *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean rest_oauth2_proxy_refresh_access_token_finish (RestOAuth2Proxy *self, + GAsyncResult *result, + GError **error); +const gchar *rest_oauth2_proxy_get_auth_url (RestOAuth2Proxy *self); +void rest_oauth2_proxy_set_auth_url (RestOAuth2Proxy *self, + const gchar *tokenurl); +const gchar *rest_oauth2_proxy_get_token_url (RestOAuth2Proxy *self); +void rest_oauth2_proxy_set_token_url (RestOAuth2Proxy *self, + const gchar *tokenurl); +const gchar *rest_oauth2_proxy_get_redirect_uri (RestOAuth2Proxy *self); +void rest_oauth2_proxy_set_redirect_uri (RestOAuth2Proxy *self, + const gchar *redirect_uri); +const gchar *rest_oauth2_proxy_get_client_id (RestOAuth2Proxy *self); +void rest_oauth2_proxy_set_client_id (RestOAuth2Proxy *self, + const gchar *client_id); +const gchar *rest_oauth2_proxy_get_client_secret (RestOAuth2Proxy *self); +void rest_oauth2_proxy_set_client_secret (RestOAuth2Proxy *self, + const gchar *client_secret); +const gchar *rest_oauth2_proxy_get_access_token (RestOAuth2Proxy *self); +void rest_oauth2_proxy_set_access_token (RestOAuth2Proxy *self, + const gchar *access_token); +const gchar *rest_oauth2_proxy_get_refresh_token (RestOAuth2Proxy *self); +void rest_oauth2_proxy_set_refresh_token (RestOAuth2Proxy *self, + const gchar *refresh_token); +GDateTime *rest_oauth2_proxy_get_expiration_date (RestOAuth2Proxy *self); +void rest_oauth2_proxy_set_expiration_date (RestOAuth2Proxy *self, + GDateTime *expiration_date); + +G_END_DECLS diff --git a/rest/rest-pkce-code-challenge.c b/rest/rest-pkce-code-challenge.c new file mode 100644 index 0000000..0d936f8 --- /dev/null +++ b/rest/rest-pkce-code-challenge.c @@ -0,0 +1,121 @@ +/* rest-pkce-code-challenge.c + * + * Copyright 2021 Günther Wagner + * + * 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-pkce-code-challenge.h" +#include "rest-utils.h" + +G_DEFINE_BOXED_TYPE (RestPkceCodeChallenge, rest_pkce_code_challenge, rest_pkce_code_challenge_copy, rest_pkce_code_challenge_free) + +struct _RestPkceCodeChallenge +{ + gchar *code_verifier; + gchar *code_challenge; +}; + +/** + * rest_pkce_code_challenge_new_random: + * + * Creates a new #RestPkceCodeChallenge. + * + * Returns: (transfer full): A newly created #RestPkceCodeChallenge + */ +RestPkceCodeChallenge * +rest_pkce_code_challenge_new_random (void) +{ + RestPkceCodeChallenge *self; + gint length = g_random_int_range (43, 128); + gsize digest_len = 200; + guchar code_verifier_sha256[200]; + GChecksum *sha256 = g_checksum_new (G_CHECKSUM_SHA256); + + self = g_slice_new0 (RestPkceCodeChallenge); + self->code_verifier = random_string (length); + g_checksum_update (sha256, (guchar *)self->code_verifier, -1); + g_checksum_get_digest (sha256, (guchar *)&code_verifier_sha256, &digest_len); + + self->code_challenge = g_base64_encode (code_verifier_sha256, digest_len); + g_strdelimit (self->code_challenge, "=", '\0'); + g_strdelimit (self->code_challenge, "+", '-'); + g_strdelimit (self->code_challenge, "/", '_'); + + return self; +} + +/** + * rest_pkce_code_challenge_copy: + * @self: a #RestPkceCodeChallenge + * + * Makes a deep copy of a #RestPkceCodeChallenge. + * + * Returns: (transfer full): A newly created #RestPkceCodeChallenge with the same + * contents as @self + */ +RestPkceCodeChallenge * +rest_pkce_code_challenge_copy (RestPkceCodeChallenge *self) +{ + RestPkceCodeChallenge *copy; + + g_return_val_if_fail (self, NULL); + + copy = g_slice_new0 (RestPkceCodeChallenge); + copy->code_verifier = self->code_verifier; + copy->code_challenge = self->code_challenge; + + return copy; +} + +/** + * rest_pkce_code_challenge_free: + * @self: a #RestPkceCodeChallenge + * + * Frees a #RestPkceCodeChallenge allocated using rest_pkce_code_challenge_new() + * or rest_pkce_code_challenge_copy(). + */ +void +rest_pkce_code_challenge_free (RestPkceCodeChallenge *self) +{ + g_return_if_fail (self); + + g_slice_free (RestPkceCodeChallenge, self); +} + +/** + * rest_pkce_code_challenge_get_challenge: + * + * Returns the Code Challenge for the Pkce verification. + * + * Returns: the code_challenge + */ +const gchar * +rest_pkce_code_challenge_get_challenge (RestPkceCodeChallenge *self) +{ + return self->code_challenge; +} + +/** + * rest_pkce_code_challenge_get_verifier: + * + * Returns the Code Verifier for the Pkce verification. + * + * Returns: the code_verifier + */ +const gchar * +rest_pkce_code_challenge_get_verifier (RestPkceCodeChallenge *self) +{ + return self->code_verifier; +} diff --git a/rest/rest-pkce-code-challenge.h b/rest/rest-pkce-code-challenge.h new file mode 100644 index 0000000..9ce3907 --- /dev/null +++ b/rest/rest-pkce-code-challenge.h @@ -0,0 +1,44 @@ +/* rest-pkce-code-challenge.h + * + * Copyright 2021 Günther Wagner + * + * 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. + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define REST_TYPE_PKCE_CODE_CHALLENGE (rest_pkce_code_challenge_get_type ()) + +/** + * RestPkceCodeChallenge: + * + * In order to play a Pkce Code Verification during a OAuth2 authorization + * you need this structure which handles the algorithmic part. + */ +typedef struct _RestPkceCodeChallenge RestPkceCodeChallenge; + +GType rest_pkce_code_challenge_get_type (void) G_GNUC_CONST; +RestPkceCodeChallenge *rest_pkce_code_challenge_new_random (void); +RestPkceCodeChallenge *rest_pkce_code_challenge_copy (RestPkceCodeChallenge *self); +void rest_pkce_code_challenge_free (RestPkceCodeChallenge *self); +const gchar *rest_pkce_code_challenge_get_challenge (RestPkceCodeChallenge *self); +const gchar *rest_pkce_code_challenge_get_verifier (RestPkceCodeChallenge *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (RestPkceCodeChallenge, rest_pkce_code_challenge_free) + +G_END_DECLS diff --git a/rest/rest-proxy-call.c b/rest/rest-proxy-call.c index 62b00da..24d952d 100644 --- a/rest/rest-proxy-call.c +++ b/rest/rest-proxy-call.c @@ -238,6 +238,8 @@ rest_proxy_call_set_method (RestProxyCall *call, * @call: The #RestProxyCall * * Get the HTTP method to use when making the call, for example GET or POST. + * + * Returns: (transfer none): the HTTP method */ const char * rest_proxy_call_get_method (RestProxyCall *call) @@ -1005,19 +1007,23 @@ _call_message_call_completed_cb (SoupMessage *message, GError *error, gpointer user_data) { - GTask *task = user_data; + g_autoptr(GTask) task = user_data; RestProxyCall *call; call = REST_PROXY_CALL (g_task_get_source_object (task)); + if (error) + { + g_task_return_error (task, error); + return; + } + finish_call (call, message, payload, &error); if (error != NULL) g_task_return_error (task, error); else g_task_return_boolean (task, TRUE); - - g_object_unref (task); } /** @@ -1158,6 +1164,8 @@ _continuous_call_message_sent_cb (GObject *source, * * You may unref the call after calling this function since there is an * internal reference, or you may unref in the callback. + * + * Returns: %TRUE on success */ gboolean rest_proxy_call_continuous (RestProxyCall *call, @@ -1491,11 +1499,7 @@ rest_proxy_call_get_payload_length (RestProxyCall *call) g_return_val_if_fail (REST_IS_PROXY_CALL (call), 0); payload = GET_PRIVATE (call)->payload; -#ifdef WITH_SOUP_2 - return payload ? g_bytes_get_size (payload) - 1 : 0; -#else return payload ? g_bytes_get_size (payload) : 0; -#endif } /** diff --git a/rest/rest-proxy.c b/rest/rest-proxy.c index 171f6cb..8231b6f 100644 --- a/rest/rest-proxy.c +++ b/rest/rest-proxy.c @@ -763,7 +763,7 @@ message_finished_cb (SoupSession *session, GError *error = NULL; body = g_bytes_new (message->response_body->data, - message->response_body->length + 1); + message->response_body->length); data->callback (message, body, error, data->user_data); g_free (data); } @@ -894,7 +894,7 @@ _rest_proxy_send_message (RestProxy *proxy, #ifdef WITH_SOUP_2 soup_session_send_message (priv->session, message); body = g_bytes_new (message->response_body->data, - message->response_body->length + 1); + message->response_body->length); #else body = soup_session_send_and_read (priv->session, message, diff --git a/rest/rest-utils.c b/rest/rest-utils.c new file mode 100644 index 0000000..939d49e --- /dev/null +++ b/rest/rest-utils.c @@ -0,0 +1,44 @@ +/* rest-utils.c + * + * Copyright 2021 Günther Wagner + * + * 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-utils.h" + +/** + * random_string: + * @length: the length of the random string + * + * Creates a random string from a given alphabeth with length @length + * + * Returns: (transfer full): a random string + */ +gchar * +random_string (guint length) +{ + g_autoptr(GRand) rand = g_rand_new (); + gchar *buffer = g_slice_alloc0 (sizeof (gchar) * length + 1); + gchar alphabeth[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"; + + for (guint i = 0; i < length; i++) + { + buffer[i] = alphabeth[g_rand_int (rand) % (sizeof (alphabeth) - 1)]; + } + buffer[length] = '\0'; + + return buffer; +} + diff --git a/rest/rest-utils.h b/rest/rest-utils.h new file mode 100644 index 0000000..cd61145 --- /dev/null +++ b/rest/rest-utils.h @@ -0,0 +1,27 @@ +/* rest-utils.h + * + * Copyright 2021 Günther Wagner + * + * 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. + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +gchar *random_string (guint length); + +G_END_DECLS diff --git a/rest/rest.h b/rest/rest.h new file mode 100644 index 0000000..9bc964c --- /dev/null +++ b/rest/rest.h @@ -0,0 +1,35 @@ +/* rest.h + * + * Copyright 2021 Günther Wagner + * + * This file 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 3 of the + * License, or (at your option) any later version. + * + * This file 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 this program. If not, see . + * + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define REST_INSIDE +# include "rest-proxy.h" +# include "rest-proxy-call.h" +# include "rest-oauth2-proxy.h" +# include "rest-utils.h" +# include "rest-pkce-code-challenge.h" +#undef REST_INSIDE + +G_END_DECLS -- cgit v1.2.1