[librest/gwagner/oauth2] oauth2: reworked oauth2
- From: Günther Wagner <gwagner src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [librest/gwagner/oauth2] oauth2: reworked oauth2
- Date: Sun, 14 Nov 2021 18:49:18 +0000 (UTC)
commit 1a7e8dba52a8d4cc39f963ea6b8015eed9351efe
Author: Günther Wagner <info gunibert de>
Date: Sun Nov 14 17:41:22 2021 +0100
oauth2: reworked oauth2
.gitlab-ci.yml | 7 +-
docs/meson.build | 2 +-
examples/gitlab-oauth2-example.c | 150 ++++++++
examples/meson.build | 1 +
meson.build | 1 +
rest/meson.build | 16 +-
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 | 67 ++++
rest/rest-oauth2-proxy-call.h | 41 +++
rest/rest-oauth2-proxy.c | 719 +++++++++++++++++++++++++++++++++++++++
rest/rest-oauth2-proxy.h | 93 +++++
rest/rest-pkce-code-challenge.c | 109 ++++++
rest/rest-pkce-code-challenge.h | 40 +++
rest/rest-utils.c | 38 +++
rest/rest-utils.h | 29 ++
rest/rest.h | 35 ++
tests/meson.build | 1 -
tests/oauth2.c | 69 ----
tests/proxy.c | 39 ++-
23 files changed, 1368 insertions(+), 732 deletions(-)
---
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e897e44..d87248b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -13,7 +13,7 @@ build-librest:
- dnf update -y --nogpgcheck
- dnf -y install --nogpgcheck redhat-rpm-config
glib2-devel gobject-introspection-devel libxml2-devel meson ninja-build
- libsoup-devel vala
+ libsoup-devel vala json-glib-devel
script:
- meson _build
- ninja -C _build
@@ -64,9 +64,10 @@ reference:
libxslt
libsoup-devel
gtk-doc
+ json-glib-devel
MESON_VERSION: "0.55.3"
- MESON_EXTRA_FLAGS: "-Dintrospection=true"
- DOCS_FLAGS: -Dgtk_doc=true
+ MESON_EXTRA_FLAGS: "-Dintrospection=enabled"
+ DOCS_FLAGS: -Dgtk_doc=enabled
DOCS_PATH: docs/librest-1.0
pages:
diff --git a/docs/meson.build b/docs/meson.build
index 601e306..33f4c29 100644
--- a/docs/meson.build
+++ b/docs/meson.build
@@ -1,4 +1,4 @@
-if get_option('gtk_doc') and get_option('introspection')
+if get_option('gtk_doc').enabled() and get_option('introspection').enabled()
dependency('gi-docgen', version: '>= 2021.6',
fallback: ['gi-docgen', 'dummy_dep'],
)
diff --git a/examples/gitlab-oauth2-example.c b/examples/gitlab-oauth2-example.c
new file mode 100644
index 0000000..88fc3bd
--- /dev/null
+++ b/examples/gitlab-oauth2-example.c
@@ -0,0 +1,150 @@
+/* gitlab-oauth2-example.c
+ *
+ * Copyright 2021 Günther Wagner <info gunibert de>
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/**
+ * This example shows the recommended PKCE authorization flow for Gitlab.
+ */
+
+#include <glib.h>
+#include <rest/rest.h>
+#include <stdio.h>
+#include <libsoup/soup.h>
+
+GMainLoop *loop;
+
+static void
+load_projects (RestProxy *proxy)
+{
+ g_autoptr(GError) error = NULL;
+ RestProxyCall *call;
+
+ call = rest_proxy_new_call (proxy);
+ rest_proxy_call_set_method (call, "GET");
+ rest_proxy_call_set_function (call, "projects/426/issues");
+ rest_proxy_call_sync (call, &error);
+
+ g_print ("%s\n", rest_proxy_call_get_payload (call));
+ g_main_loop_quit (loop);
+}
+
+static void
+gitlab_oauth2_example_refreshed_access_token (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ RestOAuth2Proxy *oauth2_proxy = (RestOAuth2Proxy *)object;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (G_IS_OBJECT (object));
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ rest_oauth2_proxy_refresh_access_token_finish (oauth2_proxy, result, &error);
+ if (error)
+ g_error ("%s", error->message);
+
+ g_print ("Access Token: %s\n", rest_oauth2_proxy_get_access_token (oauth2_proxy));
+ g_print ("Refresh Token: %s\n", rest_oauth2_proxy_get_refresh_token (oauth2_proxy));
+ GDateTime *expiration_date = rest_oauth2_proxy_get_expiration_date (oauth2_proxy);
+ if (expiration_date)
+ g_print ("Expires in: %s\n", g_date_time_format (expiration_date, "%X %x"));
+
+ load_projects (REST_PROXY (oauth2_proxy));
+}
+
+static void
+gitlab_oauth2_example_fetched_access_token (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ RestOAuth2Proxy *oauth2_proxy = (RestOAuth2Proxy *)object;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (G_IS_OBJECT (object));
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ rest_oauth2_proxy_fetch_access_token_finish (oauth2_proxy, result, &error);
+ if (error)
+ g_error ("%s", error->message);
+
+ g_print ("Access Token: %s\n", rest_oauth2_proxy_get_access_token (oauth2_proxy));
+ g_print ("Refresh Token: %s\n", rest_oauth2_proxy_get_refresh_token (oauth2_proxy));
+ GDateTime *expiration_date = rest_oauth2_proxy_get_expiration_date (oauth2_proxy);
+ if (expiration_date)
+ g_print ("Expires in: %s\n", g_date_time_format (expiration_date, "%X %x"));
+
+ /* refresh token */
+ rest_oauth2_proxy_refresh_access_token_async (oauth2_proxy, NULL,
gitlab_oauth2_example_refreshed_access_token, user_data);
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ g_autofree gchar *line = NULL;
+ size_t len = 0;
+
+ gchar **environment = g_get_environ ();
+ gchar *authurl = "https://gitlab.gnome.org/oauth/authorize";
+ gchar *tokenurl = "https://gitlab.gnome.org/oauth/token";
+ gchar *redirecturl = "http://example.com";
+ gchar *baseurl = "https://gitlab.gnome.org/api/v4/";
+ const gchar *clientid = g_environ_getenv (environment, "REST_OAUTH2_CLIENT_ID");
+ if (!clientid)
+ {
+ g_print ("You have to define your Gitlab Client ID as REST_OAUTH2_CLIENT_ID environment variable\n");
+ return EXIT_SUCCESS;
+ }
+
+ const gchar *clientsecret = g_environ_getenv (environment, "REST_OAUTH2_CLIENT_SECRET");
+ if (!clientsecret)
+ {
+ g_print ("You have to define your Gitlab Client Secret as REST_OAUTH2_CLIENT_SECRET environment
variable\n");
+ return EXIT_SUCCESS;
+ }
+ RestPkceCodeChallenge *pkce = rest_pkce_code_challenge_new_random ();
+ gchar *state = NULL;
+
+#ifdef WITH_SOUP_2
+ SoupLogger *logger = soup_logger_new (SOUP_LOGGER_LOG_HEADERS, -1);
+#else
+ SoupLogger *logger = soup_logger_new (SOUP_LOGGER_LOG_HEADERS);
+#endif
+
+ RestOAuth2Proxy *oauth2_proxy = rest_oauth2_proxy_new (authurl, tokenurl, redirecturl, clientid,
clientsecret, baseurl);
+ rest_proxy_add_soup_feature (oauth2_proxy, logger);
+ const gchar *authorize_url = rest_oauth2_proxy_build_authorization_url (oauth2_proxy,
rest_pkce_code_challenge_get_challenge (pkce), NULL, &state);
+
+ g_print ("URL to authorize: %s\n", authorize_url);
+
+ ssize_t chars = getline (&line, &len, stdin);
+ if (line[chars - 1] == '\n') {
+ line[chars - 1] = '\0';
+ }
+
+ g_print ("Got Authorization Grant: %s\n", line);
+
+ /* fetch access token */
+ rest_oauth2_proxy_fetch_access_token_async (oauth2_proxy, line, rest_pkce_code_challenge_get_verifier
(pkce), NULL, gitlab_oauth2_example_fetched_access_token, NULL);
+
+ loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (loop);
+
+ return 0;
+}
diff --git a/examples/meson.build b/examples/meson.build
index 7d16dc7..375a72a 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -7,6 +7,7 @@ example_names = [
'get-flickr-favorites',
'lastfm-shout',
'continuous-twitter',
+ 'gitlab-oauth2-example',
]
example_deps = [
diff --git a/meson.build b/meson.build
index 6ea2f6d..2a04568 100644
--- a/meson.build
+++ b/meson.build
@@ -57,6 +57,7 @@ endif
glib_dep = dependency('glib-2.0', version: '>= 2.44')
gobject_dep = dependency('gobject-2.0', version: '>= 2.44')
libsoup_dep = dependency(libsoup_name, version: libsoup_req_version)
+libjson_glib_dep = dependency('json-glib-1.0')
libxml_dep = dependency('libxml-2.0')
# config.h
diff --git a/rest/meson.build b/rest/meson.build
index a153660..a71064b 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,
]
diff --git a/rest/rest-oauth2-proxy-call.c b/rest/rest-oauth2-proxy-call.c
new file mode 100644
index 0000000..302df3b
--- /dev/null
+++ b/rest/rest-oauth2-proxy-call.c
@@ -0,0 +1,67 @@
+/* rest-oauth2-proxy-call.c
+ *
+ * Copyright 2021 Günther Wagner <info gunibert de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#include "rest-oauth2-proxy-call.h"
+
+G_DEFINE_TYPE (RestOAuth2ProxyCall, rest_oauth2_proxy_call, REST_TYPE_PROXY_CALL)
+
+G_DEFINE_QUARK (rest-oauth2-error-quark, rest_oauth2_error)
+
+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_PROXY_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..f89d033
--- /dev/null
+++ b/rest/rest-oauth2-proxy-call.h
@@ -0,0 +1,41 @@
+/* rest-oauth2-proxy-call.h
+ *
+ * Copyright 2021 Günther Wagner <info gunibert de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <rest.h>
+
+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;
+};
+
+enum {
+ REST_OAUTH2_PROXY_ERROR_ACCESS_TOKEN_EXPIRED,
+};
+
+#define REST_OAUTH2_ERROR rest_oauth2_error_quark ()
+
+G_END_DECLS
diff --git a/rest/rest-oauth2-proxy.c b/rest/rest-oauth2-proxy.c
new file mode 100644
index 0000000..14ecaab
--- /dev/null
+++ b/rest/rest-oauth2-proxy.c
@@ -0,0 +1,719 @@
+/* rest-oauth2-proxy.c
+ *
+ * Copyright 2021 Günther Wagner <info gunibert de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#include "rest-oauth2-proxy.h"
+#include "rest-oauth2-proxy-call.h"
+#include "rest-utils.h"
+#include "rest-private.h"
+#include <json-glib/json-glib.h>
+
+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)
+
+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
+ */
+const 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);
+
+ *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);
+ 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_task_get_source_object (task);
+
+ g_return_if_fail (G_IS_TASK (task));
+ g_return_if_fail (REST_IS_OAUTH2_PROXY (self));
+
+ 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);
+}
+
+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;
+ GBytes *payload;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ g_return_if_fail (REST_IS_OAUTH2_PROXY (self));
+
+ 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, NULL);
+
+ REST_OAUTH2_PROXY_GET_CLASS (self)->parse_access_token (self, payload, 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..c41884f
--- /dev/null
+++ b/rest/rest-oauth2-proxy.h
@@ -0,0 +1,93 @@
+/* rest-oauth2-proxy.h
+ *
+ * Copyright 2021 Günther Wagner <info gunibert de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <rest/rest-proxy.h>
+
+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];
+};
+
+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);
+const 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);
+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..f9932ec
--- /dev/null
+++ b/rest/rest-pkce-code-challenge.c
@@ -0,0 +1,109 @@
+/* rest-pkce-code-challenge.c
+ *
+ * Copyright 2021 Günther Wagner <info gunibert de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#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);
+}
+
+const gchar *
+rest_pkce_code_challenge_get_challenge (RestPkceCodeChallenge *self)
+{
+ return self->code_challenge;
+}
+
+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..eba1ea6
--- /dev/null
+++ b/rest/rest-pkce-code-challenge.h
@@ -0,0 +1,40 @@
+/* rest-pkce-code-challenge.h
+ *
+ * Copyright 2021 Günther Wagner <info gunibert de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define REST_TYPE_PKCE_CODE_CHALLENGE (rest_pkce_code_challenge_get_type ())
+
+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-utils.c b/rest/rest-utils.c
new file mode 100644
index 0000000..bb5a877
--- /dev/null
+++ b/rest/rest-utils.c
@@ -0,0 +1,38 @@
+/* rest-utils.c
+ *
+ * Copyright 2021 Günther Wagner <info gunibert de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#include "rest-utils.h"
+
+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..4a74433
--- /dev/null
+++ b/rest/rest-utils.h
@@ -0,0 +1,29 @@
+/* rest-utils.h
+ *
+ * Copyright 2021 Günther Wagner <info gunibert de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib.h>
+
+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 <info gunibert de>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib.h>
+
+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
diff --git a/tests/meson.build b/tests/meson.build
index 0fe80e8..8a0b3d6 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -3,7 +3,6 @@ test_suites = {
'proxy',
'proxy-continuous',
'threaded',
- 'oauth2',
'xml',
'custom-serialize',
],
diff --git a/tests/proxy.c b/tests/proxy.c
index c80c362..3abbc1c 100644
--- a/tests/proxy.c
+++ b/tests/proxy.c
@@ -50,16 +50,19 @@ GMainLoop *server_loop;
#ifdef WITH_SOUP_2
static void
-server_callback_soup (SoupServer *server,
- SoupMessage *msg,
- const gchar *path,
- GHashTable *query,
- SoupClientContext *client,
- gpointer user_data)
+server_callback (SoupServer *server,
+ SoupMessage *msg,
+ const gchar *path,
+ GHashTable *query,
+ SoupClientContext *client,
+ gpointer user_data)
{
- if (g_str_equal (path, "/ping") && g_strcmp0 ("GET", soup_server_message_get_method (msg)) == 0) {
+ if (g_str_equal (path, "/ping") && g_strcmp0 ("GET", msg->method) == 0) {
soup_message_set_status (msg, SOUP_STATUS_OK);
}
+ else if (g_str_equal (path, "/ping") && g_strcmp0 ("POST", msg->method) == 0) {
+ soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
+ }
else if (g_str_equal (path, "/echo")) {
const char *value;
@@ -120,6 +123,9 @@ server_callback (SoupServer *server,
if (g_str_equal (path, "/ping") && g_strcmp0 ("GET", soup_server_message_get_method (msg)) == 0) {
soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL);
}
+ else if (g_str_equal (path, "/ping") && g_strcmp0 ("POST", soup_server_message_get_method (msg)) == 0) {
+ soup_server_message_set_status (msg, SOUP_STATUS_NOT_FOUND, NULL);
+ }
else if (g_str_equal (path, "/echo")) {
const char *value;
@@ -205,6 +211,21 @@ ping_test (RestProxy *proxy)
}
g_object_unref (call);
+
+ call = rest_proxy_new_call (proxy);
+ rest_proxy_call_set_function (call, "ping");
+ rest_proxy_call_set_method (call, "POST");
+
+ rest_proxy_call_sync (call, &error);
+ /* g_assert_nonnull(error); */
+
+ if (rest_proxy_call_get_status_code (call) != SOUP_STATUS_NOT_FOUND) {
+ g_printerr ("wrong response code\n");
+ errors++;
+ return;
+ }
+
+ g_object_unref (call);
}
static void
@@ -379,11 +400,7 @@ static void *
server_thread_func (gpointer data)
{
server_loop = g_main_loop_new (NULL, TRUE);
-#ifdef WITH_SOUP_2
soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
-#else
- soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
-#endif
soup_server_listen_local (server, PORT, 0, NULL);
g_main_loop_run (server_loop);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]