commit ed09a257495bc667f5ca043ffbe96800f418ff9c
parent bcb00ec1a1dacd1caebcc7303125558a4f36fa4b
Author: Christian Grothoff <grothoff@gnunet.org>
Date: Mon, 6 Jan 2025 15:00:00 +0100
work on test
Diffstat:
5 files changed, 632 insertions(+), 2 deletions(-)
diff --git a/src/tests/client_server/Makefile.am b/src/tests/client_server/Makefile.am
@@ -26,6 +26,7 @@ $(top_builddir)/src/mhd2/libmicrohttpd2.la: $(top_builddir)/src/mhd2/Makefile
check_PROGRAMS = \
test_client_server \
+ test_authentication \
test_postprocessor
if MHD_ENABLE_HTTPS
@@ -56,6 +57,11 @@ test_client_server_SOURCES = \
test_client_server_LDADD = \
libmhdt.la
+test_authentication_SOURCES = \
+ test_authentication.c
+test_authentication_LDADD = \
+ libmhdt.la
+
test_tls_SOURCES = \
test_tls.c
test_tls_LDADD = \
diff --git a/src/tests/client_server/libtest.h b/src/tests/client_server/libtest.h
@@ -376,6 +376,62 @@ MHDT_client_do_post (
/**
+ * Perform GET request and send some HTTP basic authentication header
+ * to authorize the request.
+ *
+ * @param cls a string with "$USERNAME:$PASSWORD"
+ * @param pc context for the client
+ * @return error message, NULL on success
+ */
+const char *
+MHDT_client_send_basic_auth (
+ const void *cls,
+ struct MHDT_PhaseContext *pc);
+
+
+/**
+ * Perform GET request and send some HTTP basic authentication header
+ * to authorize the request. Expect authentication to fail.
+ *
+ * @param cls a string with "$USERNAME:$PASSWORD"
+ * @param pc context for the client
+ * @return error message, NULL on success
+ */
+const char *
+MHDT_client_fail_basic_auth (
+ const void *cls,
+ struct MHDT_PhaseContext *pc);
+
+
+/**
+ * Perform GET request and send some HTTP digest authentication header
+ * to authorize the request.
+ *
+ * @param cls a string with "$USERNAME:$PASSWORD"
+ * @param pc context for the client
+ * @return error message, NULL on success
+ */
+const char *
+MHDT_client_send_digest_auth (
+ const void *cls,
+ struct MHDT_PhaseContext *pc);
+
+
+/**
+ * Perform GET request and send some HTTP digest authentication header
+ * to authorize the request. Expect authentication to fail.
+ *
+ * @param cls a string with "$USERNAME:$PASSWORD"
+ * @param pc context for the client
+ * @return error message, NULL on success
+ */
+const char *
+MHDT_client_fail_digest_auth (
+ const void *cls,
+ struct MHDT_PhaseContext *pc);
+
+
+/**
* Returns the text from @a cls as the response to any
* request.
*
@@ -603,6 +659,64 @@ MHDT_server_reply_check_post (
/**
+ * Checks that the client request includes the given
+ * username and password in HTTP basic authetnication.
+ * If so, returns #MHD_HTTP_STATUS_NO_CONTENT, otherwise
+ * an #MHD_HTTP_STATUS_UNAUTHORIZED.
+ *
+ * @param cls expected upload data as a 0-terminated string.
+ * @param request the request object
+ * @param path the requested uri (without arguments after "?")
+ * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
+ * #MHD_HTTP_METHOD_PUT, etc.)
+ * @param upload_size the size of the message upload content payload,
+ * #MHD_SIZE_UNKNOWN for chunked uploads (if the
+ * final chunk has not been processed yet)
+ * @return action how to proceed, NULL
+ * if the request must be aborted due to a serious
+ * error while handling the request (implies closure
+ * of underling data stream, for HTTP/1.1 it means
+ * socket closure).
+ */
+const struct MHD_Action *
+MHDT_server_reply_check_basic_auth (
+ void *cls,
+ struct MHD_Request *MHD_RESTRICT request,
+ const struct MHD_String *MHD_RESTRICT path,
+ enum MHD_HTTP_Method method,
+ uint_fast64_t upload_size);
+
+
+/**
+ * Checks that the client request includes the given
+ * username and password in HTTP digest authetnication.
+ * If so, returns #MHD_HTTP_STATUS_NO_CONTENT, otherwise
+ * an #MHD_HTTP_STATUS_UNAUTHORIZED.
+ *
+ * @param cls expected upload data as a 0-terminated string.
+ * @param request the request object
+ * @param path the requested uri (without arguments after "?")
+ * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
+ * #MHD_HTTP_METHOD_PUT, etc.)
+ * @param upload_size the size of the message upload content payload,
+ * #MHD_SIZE_UNKNOWN for chunked uploads (if the
+ * final chunk has not been processed yet)
+ * @return action how to proceed, NULL
+ * if the request must be aborted due to a serious
+ * error while handling the request (implies closure
+ * of underling data stream, for HTTP/1.1 it means
+ * socket closure).
+ */
+const struct MHD_Action *
+MHDT_server_reply_check_digest_auth (
+ void *cls,
+ struct MHD_Request *MHD_RESTRICT request,
+ const struct MHD_String *MHD_RESTRICT path,
+ enum MHD_HTTP_Method method,
+ uint_fast64_t upload_size);
+
+
+/**
* Initialize options for an MHD daemon for a test.
*
* @param cls closure
diff --git a/src/tests/client_server/libtest_convenience_client_request.c b/src/tests/client_server/libtest_convenience_client_request.c
@@ -19,7 +19,7 @@
*/
/**
- * @file libtest_convenience.c
+ * @file libtest_convenience_client_request.c
* @brief convenience functions implementing clients making requests for libtest users
* @author Christian Grothoff
*/
@@ -814,3 +814,225 @@ MHDT_client_do_post (
}
return NULL;
}
+
+
+/**
+ * Send HTTP request with basic authentication.
+ *
+ * @param cred $USERNAME:$PASSWORD to use
+ * @param[in,out] phase context
+ * @param[out] http_status set to HTTP status
+ * @return error message, NULL on success
+ */
+static const char *
+send_basic_auth (const char *cred,
+ struct MHDT_PhaseContext *pc,
+ unsigned int *http_status)
+{
+ CURL *c;
+ const char *err;
+ long status;
+ char *pass = strchr (cred, ':');
+ char *user;
+
+ if (NULL == pass)
+ return "invalid credential given";
+ user = strndup (cred,
+ pass - cred);
+ pass++;
+ c = curl_easy_init ();
+ if (NULL == c)
+ {
+ free (user);
+ return "Failed to initialize Curl handle";
+ }
+ err = set_url (c,
+ pc->base_url,
+ pc);
+ if (NULL != err)
+ {
+ free (user);
+ curl_easy_cleanup (c);
+ return err;
+ }
+ if ( (CURLE_OK !=
+ curl_easy_setopt (c,
+ CURLOPT_HTTPAUTH,
+ (long) CURLAUTH_BASIC)) ||
+ (CURLE_OK !=
+ curl_easy_setopt (c,
+ CURLOPT_USERNAME,
+ user)) ||
+ (CURLE_OK !=
+ curl_easy_setopt (c,
+ CURLOPT_PASSWORD,
+ pass)) )
+ {
+ curl_easy_cleanup (c);
+ free (user);
+ return "Failed to set basic authentication header for curl request";
+ }
+ free (user);
+ PERFORM_REQUEST (c);
+ if (CURLE_OK !=
+ curl_easy_getinfo (c,
+ CURLINFO_RESPONSE_CODE,
+ &status))
+ {
+ return "Failed to get HTTP status";
+ }
+ *http_status = (unsigned int) status;
+ curl_easy_cleanup (c);
+ return NULL;
+}
+
+
+const char *
+MHDT_client_send_basic_auth (
+ const void *cls,
+ struct MHDT_PhaseContext *pc)
+{
+ const char *cred = cls;
+ const char *ret;
+ unsigned int status;
+
+ ret = send_basic_auth (cred,
+ pc,
+ &status);
+ if (NULL != ret)
+ return ret;
+ if (MHD_HTTP_STATUS_NO_CONTENT != status)
+ return "invalid HTTP response code";
+ return NULL;
+}
+
+
+const char *
+MHDT_client_fail_basic_auth (
+ const void *cls,
+ struct MHDT_PhaseContext *pc)
+{
+ const char *cred = cls;
+ const char *ret;
+ unsigned int status;
+
+ ret = send_basic_auth (cred,
+ pc,
+ &status);
+ if (NULL != ret)
+ return ret;
+ if (MHD_HTTP_STATUS_UNAUTHORIZED != status)
+ return "invalid HTTP response code";
+ return NULL;
+}
+
+
+/**
+ * Send HTTP request with digest authentication.
+ *
+ * @param cred $USERNAME:$PASSWORD to use
+ * @param[in,out] phase context
+ * @param[out] http_status set to HTTP status
+ * @return error message, NULL on success
+ */
+static const char *
+send_digest_auth (const char *cred,
+ struct MHDT_PhaseContext *pc,
+ unsigned int *http_status)
+{
+ CURL *c;
+ const char *err;
+ long status;
+ char *pass = strchr (cred, ':');
+ char *user;
+
+ if (NULL == pass)
+ return "invalid credential given";
+ user = strndup (cred,
+ pass - cred);
+ pass++;
+ c = curl_easy_init ();
+ if (NULL == c)
+ {
+ free (user);
+ return "Failed to initialize Curl handle";
+ }
+ err = set_url (c,
+ pc->base_url,
+ pc);
+ if (NULL != err)
+ {
+ free (user);
+ curl_easy_cleanup (c);
+ return err;
+ }
+ if ( (CURLE_OK !=
+ curl_easy_setopt (c,
+ CURLOPT_HTTPAUTH,
+ (long) CURLAUTH_DIGEST)) ||
+ (CURLE_OK !=
+ curl_easy_setopt (c,
+ CURLOPT_USERNAME,
+ user)) ||
+ (CURLE_OK !=
+ curl_easy_setopt (c,
+ CURLOPT_PASSWORD,
+ pass)) )
+ {
+ curl_easy_cleanup (c);
+ free (user);
+ return "Failed to set digest authentication header for curl request";
+ }
+ free (user);
+ PERFORM_REQUEST (c);
+ if (CURLE_OK !=
+ curl_easy_getinfo (c,
+ CURLINFO_RESPONSE_CODE,
+ &status))
+ {
+ return "Failed to get HTTP status";
+ }
+ *http_status = (unsigned int) status;
+ curl_easy_cleanup (c);
+ return NULL;
+}
+
+
+const char *
+MHDT_client_send_digest_auth (
+ const void *cls,
+ struct MHDT_PhaseContext *pc)
+{
+ const char *cred = cls;
+ const char *ret;
+ unsigned int status;
+
+ ret = send_digest_auth (cred,
+ pc,
+ &status);
+ if (NULL != ret)
+ return ret;
+ if (MHD_HTTP_STATUS_NO_CONTENT != status)
+ return "invalid HTTP response code";
+ return NULL;
+}
+
+
+const char *
+MHDT_client_fail_digest_auth (
+ const void *cls,
+ struct MHDT_PhaseContext *pc)
+{
+ const char *cred = cls;
+ const char *ret;
+ unsigned int status;
+
+ ret = send_digest_auth (cred,
+ pc,
+ &status);
+ if (NULL != ret)
+ return ret;
+ if (MHD_HTTP_STATUS_FORBIDDEN != status)
+ return "invalid HTTP response code";
+ return NULL;
+}
diff --git a/src/tests/client_server/libtest_convenience_server_reply.c b/src/tests/client_server/libtest_convenience_server_reply.c
@@ -33,7 +33,7 @@
#include <unistd.h>
#include <errno.h>
#include <curl/curl.h>
-
+#include <assert.h>
const struct MHD_Action *
MHDT_server_reply_text (
@@ -723,3 +723,167 @@ MHDT_server_reply_check_post (
&post_stream_done,
pi);
}
+
+
+const struct MHD_Action *
+MHDT_server_reply_check_basic_auth (
+ void *cls,
+ struct MHD_Request *MHD_RESTRICT request,
+ const struct MHD_String *MHD_RESTRICT path,
+ enum MHD_HTTP_Method method,
+ uint_fast64_t upload_size)
+{
+ const char *cred = cls;
+ union MHD_RequestInfoDynamicData dd;
+ enum MHD_StatusCode sc;
+ const struct MHD_AuthBasicCreds *ba;
+
+ /* should not be needed, except to make gcc happy */
+ memset (&dd,
+ 0,
+ sizeof (dd));
+ sc = MHD_request_get_info_dynamic (request,
+ MHD_REQUEST_INFO_DYNAMIC_AUTH_BASIC_CREDS,
+ &dd);
+ if (MHD_SC_OK != sc)
+ {
+ fprintf (stderr,
+ "No credentials?\n");
+ return MHD_action_basic_auth_challenge_p (
+ request,
+ "test-realm",
+ MHD_YES,
+ MHD_response_from_empty (
+ MHD_HTTP_STATUS_UNAUTHORIZED));
+ }
+ ba = dd.v_auth_basic_creds;
+ assert (NULL != ba);
+ if ( (0 != strncmp (ba->username.cstr,
+ cred,
+ ba->username.len)) ||
+ (':' != cred[ba->username.len]) ||
+ (NULL == ba->password.cstr) ||
+ (0 != strcmp (ba->password.cstr,
+ &cred[ba->username.len + 1])) )
+ {
+ fprintf (stderr,
+ "Wrong credentials (Got: %s/%s Want: %s)!\n",
+ ba->username.cstr,
+ ba->password.cstr,
+ cred);
+ return MHD_action_basic_auth_challenge_p (
+ request,
+ "test-realm",
+ MHD_YES,
+ MHD_response_from_empty (
+ MHD_HTTP_STATUS_UNAUTHORIZED));
+ }
+ return MHD_action_from_response (
+ request,
+ MHD_response_from_empty (
+ MHD_HTTP_STATUS_NO_CONTENT));
+}
+
+
+const struct MHD_Action *
+MHDT_server_reply_check_digest_auth (
+ void *cls,
+ struct MHD_Request *MHD_RESTRICT request,
+ const struct MHD_String *MHD_RESTRICT path,
+ enum MHD_HTTP_Method method,
+ uint_fast64_t upload_size)
+{
+ const char *cred = cls;
+ const char *colon = strchr (cred, ':');
+ char *username;
+ const char *password;
+ enum MHD_DigestAuthResult dar;
+ const char *realm = "test-realm";
+ enum MHD_DigestAuthAlgo algo = MHD_DIGEST_AUTH_ALGO_SHA256;
+ size_t digest_len = MHD_digest_get_hash_size (algo);
+
+ if (0 == digest_len)
+ return NULL;
+ assert (NULL != colon);
+ password = colon + 1;
+ username = strndup (cred,
+ colon - cred);
+ assert (NULL != username);
+ {
+ enum MHD_StatusCode sc;
+ char digest[digest_len];
+
+ // FIXME: why is this needed? We should not get a warning
+ // even without this memset!
+ memset (digest, 0, sizeof (digest));
+ sc = MHD_digest_auth_calc_userdigest (algo,
+ username,
+ realm,
+ password,
+ sizeof (digest),
+ digest);
+ if (MHD_SC_OK != sc)
+ {
+ fprintf (stderr,
+ "MHD_digest_auth_calc_userdigest: %d\n",
+ (int) sc);
+ return NULL;
+ }
+ dar = MHD_digest_auth_check_digest (request,
+ realm,
+ username,
+ sizeof (digest),
+ digest,
+ 0, /* maximum nonce counter; 0: default */
+ MHD_DIGEST_AUTH_MULT_QOP_AUTH,
+ (enum MHD_DigestAuthMultiAlgo) algo);
+ }
+ free (username);
+ if ((MHD_DAUTH_HEADER_MISSING == dar)
+ || (MHD_DAUTH_NONCE_STALE == dar))
+ {
+ struct MHD_Response *resp;
+ enum MHD_StatusCode sc;
+
+ resp = MHD_response_from_empty (
+ MHD_HTTP_STATUS_UNAUTHORIZED);
+ if (NULL == resp)
+ {
+ fprintf (stderr,
+ "Failed to create response body\n");
+ return NULL;
+ }
+ sc = MHD_response_add_auth_digest_challenge (
+ resp,
+ "test-realm",
+ "opaque",
+ NULL, /* domain */
+ (MHD_DAUTH_NONCE_STALE == dar) ? MHD_YES : MHD_NO, /* indicate stale */
+ MHD_DIGEST_AUTH_MULT_QOP_AUTH,
+ MHD_DIGEST_AUTH_MULT_ALGO_SHA256,
+ MHD_NO /* userhash_support */,
+ MHD_YES /* prefer UTF8 */);
+ if (MHD_SC_OK != sc)
+ {
+ fprintf (stderr,
+ "MHD_response_add_auth_digest_challenge failed: %d\n",
+ (int) sc);
+ return NULL;
+ }
+ return MHD_action_from_response (
+ request,
+ resp);
+ }
+ if (MHD_DAUTH_RESPONSE_WRONG == dar)
+ return MHD_action_from_response (
+ request,
+ MHD_response_from_empty (MHD_HTTP_STATUS_FORBIDDEN));
+
+ if (MHD_DAUTH_OK == dar)
+ return MHD_action_from_response (
+ request,
+ MHD_response_from_empty (
+ MHD_HTTP_STATUS_NO_CONTENT));
+
+ return MHD_action_abort_request (request);
+}
diff --git a/src/tests/client_server/test_authentication.c b/src/tests/client_server/test_authentication.c
@@ -0,0 +1,124 @@
+/*
+ This file is part of GNU libmicrohttpd
+ Copyright (C) 2016, 2024 Christian Grothoff & Evgeny Grin (Karlson2k)
+
+ GNU libmicrohttpd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GNU libmicrohttpd 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 library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+*/
+
+/**
+ * @file test_authentication.c
+ * @brief test for HTTP authentication
+ * @author Christian Grothoff
+ */
+#include "libtest.h"
+
+
+int
+main (int argc, char *argv[])
+{
+ unsigned char pseudo_entropy[] = { 0, 1, 2, 3 };
+ struct MHD_DaemonOptionAndValue thread1auto[] = {
+ MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_AUTO),
+ MHD_D_OPTION_WM_WORKER_THREADS (1),
+ MHD_D_OPTION_RANDOM_ENTROPY (sizeof(pseudo_entropy),
+ pseudo_entropy),
+ MHD_D_OPTION_TERMINATE ()
+ };
+ struct ServerType
+ {
+ const char *label;
+ MHDT_ServerSetup server_setup;
+ void *server_setup_cls;
+ MHDT_ServerRunner server_runner;
+ void *server_runner_cls;
+ } configs[] = {
+ {
+ .label = "auto-selected mode, single threaded",
+ .server_setup = &MHDT_server_setup_minimal,
+ .server_setup_cls = thread1auto,
+ .server_runner = &MHDT_server_run_minimal,
+ },
+ {
+ .label = "END"
+ },
+ };
+ struct MHDT_Phase phases[] = {
+#if FIXME
+ {
+ .label = "simple basic authentication",
+ .server_cb = &MHDT_server_reply_check_basic_auth,
+ .server_cb_cls = (void *) "username:password",
+ .client_cb = &MHDT_client_send_basic_auth,
+ .client_cb_cls = (void *) "username:password",
+ .timeout_ms = 2500,
+ },
+ {
+ .label = "failing basic authentication",
+ .server_cb = &MHDT_server_reply_check_basic_auth,
+ .server_cb_cls = (void *) "username:password",
+ .client_cb = &MHDT_client_fail_basic_auth,
+ .client_cb_cls = (void *) "username:word", /* incorrect on purpose */
+ .timeout_ms = 2500,
+ },
+#endif
+ {
+ .label = "simple digest authentication",
+ .server_cb = &MHDT_server_reply_check_digest_auth,
+ .server_cb_cls = (void *) "username:password",
+ .client_cb = &MHDT_client_send_digest_auth,
+ .client_cb_cls = (void *) "username:password",
+ .timeout_ms = 2500,
+ },
+ {
+ .label = "failing digest authentication",
+ .server_cb = &MHDT_server_reply_check_digest_auth,
+ .server_cb_cls = (void *) "username:password",
+ .client_cb = &MHDT_client_fail_digest_auth,
+ .client_cb_cls = (void *) "username:word", /* incorrect on purpose */
+ .timeout_ms = 2500,
+ },
+ {
+ .label = NULL,
+ },
+ };
+ unsigned int i;
+
+ (void) argc; /* Unused. Silence compiler warning. */
+ (void) argv; /* Unused. Silence compiler warning. */
+
+ for (i = 0; NULL != configs[i].server_setup; i++)
+ {
+ int ret;
+
+ fprintf (stderr,
+ "Running tests with server setup '%s'\n",
+ configs[i].label);
+ ret = MHDT_test (configs[i].server_setup,
+ configs[i].server_setup_cls,
+ configs[i].server_runner,
+ configs[i].server_runner_cls,
+ phases);
+ if (0 != ret)
+ {
+ fprintf (stderr,
+ "Test failed with server of type '%s' (%u)\n",
+ configs[i].label,
+ i);
+ return ret;
+ }
+ }
+ return 0;
+}