[libsoup/pgriffis/multiple-digest-algorithms] auth: Add support for multiple authentication challenges at once This allows a server sending two di




commit 1bdb2df54eee04bb53437b45e6676961377bf866
Author: Patrick Griffis <pgriffis igalia com>
Date:   Thu Oct 7 11:57:43 2021 -0500

    auth: Add support for multiple authentication challenges at once
    This allows a server sending two different Digest headers with
    different algorithms. libsoup will now parse both and use one
    that is supported.

 libsoup/auth/soup-auth-manager.c | 111 ++++++++++++++++++++++++---------------
 tests/auth-test.c                |  73 +++++++++++++++++++++++++
 2 files changed, 141 insertions(+), 43 deletions(-)
---
diff --git a/libsoup/auth/soup-auth-manager.c b/libsoup/auth/soup-auth-manager.c
index 015c7c74..5737ca0c 100644
--- a/libsoup/auth/soup-auth-manager.c
+++ b/libsoup/auth/soup-auth-manager.c
@@ -311,41 +311,49 @@ next_challenge_start (GSList *items)
        return NULL;
 }
 
-static char *
-soup_auth_manager_extract_challenge (const char *challenges, const char *scheme)
+static GStrv
+soup_auth_manager_extract_challenges (const char *challenges, const char *scheme)
 {
+        GPtrArray *challenge_list = g_ptr_array_new ();
        GSList *items, *i, *next;
        int schemelen = strlen (scheme);
        char *item;
        GString *challenge;
 
-       items = soup_header_parse_list (challenges);
-
-       /* First item will start with the scheme name, followed by
-        * either nothing, or else a space and then the first
-        * auth-param.
-        */
-       for (i = items; i; i = next_challenge_start (i->next)) {
-               item = i->data;
-               if (!g_ascii_strncasecmp (item, scheme, schemelen) &&
-                   (!item[schemelen] || g_ascii_isspace (item[schemelen])))
-                       break;
-       }
-       if (!i) {
-               soup_header_free_list (items);
-               return NULL;
-       }
-
-       next = next_challenge_start (i->next);
-       challenge = g_string_new (item);
-       for (i = i->next; i != next; i = i->next) {
-               item = i->data;
-               g_string_append (challenge, ", ");
-               g_string_append (challenge, item);
-       }
+       i = items = soup_header_parse_list (challenges);
+
+        /* We need to split this list into individual challenges. */
+        while (i) {
+                /* First item will start with the scheme name, followed by
+                * either nothing, or else a space and then the first
+                * auth-param.
+                */
+                for (; i; i = next_challenge_start (i->next)) {
+                        item = i->data;
+                        if (!g_ascii_strncasecmp (item, scheme, schemelen) &&
+                        (!item[schemelen] || g_ascii_isspace (item[schemelen])))
+                                break;
+                }
+                if (!i)
+                        break;
+
+                next = next_challenge_start (i->next);
+                challenge = g_string_new (item);
+                for (i = i->next; i != next; i = i->next) {
+                        item = i->data;
+                        g_string_append (challenge, ", ");
+                        g_string_append (challenge, item);
+                }
+
+                i = next;
+                g_ptr_array_add (challenge_list, g_string_free (challenge, FALSE));
+        };
 
        soup_header_free_list (items);
-       return g_string_free (challenge, FALSE);
+
+        if (challenge_list->len)
+                g_ptr_array_add (challenge_list, NULL); /* Trailing NULL for GStrv. */
+        return (GStrv)g_ptr_array_free (challenge_list, FALSE);
 }
 
 static SoupAuth *
@@ -353,7 +361,7 @@ create_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
 {
        const char *header;
        SoupAuthClass *auth_class;
-       char *challenge = NULL;
+        GStrv challenges;
        SoupAuth *auth = NULL;
        int i;
 
@@ -363,13 +371,19 @@ create_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
 
        for (i = priv->auth_types->len - 1; i >= 0; i--) {
                auth_class = priv->auth_types->pdata[i];
-               challenge = soup_auth_manager_extract_challenge (header, auth_class->scheme_name);
-               if (!challenge)
-                       continue;
-               auth = soup_auth_new (G_TYPE_FROM_CLASS (auth_class), msg, challenge);
-               g_free (challenge);
-               if (auth)
-                       break;
+                challenges = soup_auth_manager_extract_challenges (header, auth_class->scheme_name);
+                if (!challenges)
+                        continue;
+
+                for (int j = 0; challenges[j]; j++) {
+                        /* We use the first successfully parsed auth, in the future this should
+                         * prioritise more secure ones when they are supported. */
+                        auth = soup_auth_new (G_TYPE_FROM_CLASS (auth_class), msg, challenges[j]);
+                        if (auth)
+                                break;
+                }
+
+               g_strfreev (challenges);
        }
 
        return auth;
@@ -379,22 +393,33 @@ static gboolean
 check_auth (SoupMessage *msg, SoupAuth *auth)
 {
        const char *header, *scheme;
-       char *challenge = NULL;
+        GStrv challenges = NULL;
        gboolean ok = TRUE;
+        gboolean a_challenge_was_ok = FALSE;
 
        scheme = soup_auth_get_scheme_name (auth);
 
        header = auth_header_for_message (msg);
        if (header)
-               challenge = soup_auth_manager_extract_challenge (header, scheme);
-       if (!challenge) {
-               ok = FALSE;
-               challenge = g_strdup (scheme);
+                challenges = soup_auth_manager_extract_challenges (header, scheme);
+       if (!challenges) {
+                GStrvBuilder *builder = g_strv_builder_new ();
+               g_strv_builder_add (builder, scheme);
+                challenges = g_strv_builder_end (builder);
+                ok = FALSE;
        }
 
-       if (!soup_auth_update (auth, msg, challenge))
-               ok = FALSE;
-       g_free (challenge);
+        for (int i = 0; challenges[i]; i++) {
+                if (soup_auth_update (auth, msg, challenges[i])) {
+                        a_challenge_was_ok = TRUE;
+                        break;
+                }
+        }
+
+        if (!a_challenge_was_ok)
+                ok = FALSE;
+
+        g_strfreev (challenges);
        return ok;
 }
 
diff --git a/tests/auth-test.c b/tests/auth-test.c
index c3bef39c..48067c7a 100644
--- a/tests/auth-test.c
+++ b/tests/auth-test.c
@@ -1760,6 +1760,78 @@ do_auth_uri_test (void)
        soup_test_session_abort_unref (session);
 }
 
+static void
+on_request_read (SoupServer        *server,
+                 SoupServerMessage *msg,
+                 gpointer           user_data)
+{
+        SoupMessageHeaders *response_headers = soup_server_message_get_response_headers (msg);
+        char *old_header = g_strdup (soup_message_headers_get_one (response_headers, "WWW-Authenticate"));
+        if (old_header) {
+                /* These must be in order to ensure libsoup passes over the invalid one. */
+                soup_message_headers_replace (response_headers, "WWW-Authenticate",
+                                "Digest realm=\"auth-test\", nonce=\"0000000000001\", qop=\"auth\", 
algorithm=FAKE");
+                soup_message_headers_append (response_headers, "WWW-Authenticate", old_header);
+                g_free (old_header);
+        }
+}
+
+static gboolean
+on_digest_authenticate (SoupMessage *msg,
+                        SoupAuth    *auth,
+                        gboolean     retrying,
+                        gpointer     user_data)
+{
+        g_assert_false (retrying);
+        soup_auth_authenticate (auth, "user", "good");
+        return TRUE;
+}
+
+static void
+do_multiple_digest_algorithms (void)
+{
+        SoupSession *session;
+        SoupMessage *msg;
+        SoupServer *server;
+        SoupAuthDomain *digest_auth_domain;
+        gint status;
+        GUri *uri;
+
+               server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
+       soup_server_add_handler (server, NULL,
+                                server_callback, NULL, NULL);
+       uri = soup_test_server_get_uri (server, "http", NULL);
+
+        /* Add one real authentication option, later we will add
+           a fake one with an unsupported algorithm. */
+       digest_auth_domain = soup_auth_domain_digest_new (
+               "realm", "auth-test",
+               "auth-callback", server_digest_auth_callback,
+               NULL);
+        soup_auth_domain_add_path (digest_auth_domain, "/");
+       soup_server_add_auth_domain (server, digest_auth_domain);
+        g_object_unref (digest_auth_domain);
+
+        /* We wait for the message to come in and will add a header. */
+        g_signal_connect (server, "request-read",
+                          G_CALLBACK (on_request_read),
+                          NULL);
+
+        session = soup_test_session_new (NULL);
+        loop = g_main_loop_new (NULL, FALSE);
+
+        msg = soup_message_new_from_uri ("GET", uri);
+        g_signal_connect (msg, "authenticate",
+                          G_CALLBACK (on_digest_authenticate),
+                          NULL);
+
+        status = soup_test_session_send_message (session, msg);
+
+        g_assert_cmpint (status, ==, SOUP_STATUS_OK);
+       g_uri_unref (uri);
+       soup_test_server_quit_unref (server);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -1791,6 +1863,7 @@ main (int argc, char **argv)
        g_test_add_func ("/auth/cancel-on-authenticate", do_cancel_on_authenticate);
        g_test_add_func ("/auth/auth-uri", do_auth_uri_test);
         g_test_add_func ("/auth/cancel-request-on-authenticate", do_cancel_request_on_authenticate);
+        g_test_add_func ("/auth/multiple-algorithms", do_multiple_digest_algorithms);
 
        ret = g_test_run ();
 


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