Christian Grothoff 1 gadu atpakaļ
vecāks
revīzija
9f6dcfb19f

+ 6 - 0
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 = \

+ 57 - 0
src/tests/client_server/libtest.h

@@ -375,6 +375,34 @@ MHDT_client_do_post (
   struct MHDT_PhaseContext *pc);
 
 
+/**
+ * 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);
+
+
 /**
  * Returns the text from @a cls as the response to any
  * request.
@@ -602,6 +630,35 @@ MHDT_server_reply_check_post (
   uint_fast64_t upload_size);
 
 
+/**
+ * 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);
+
+
 /**
  * Initialize options for an MHD daemon for a test.
  *

+ 112 - 1
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,114 @@ 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 - user);
+  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,
+                          "password")) )
+  {
+    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;
+}

+ 66 - 0
src/tests/client_server/libtest_convenience_server_reply.c

@@ -723,3 +723,69 @@ 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;
+  struct MHD_BasicAuthInfo *ba;
+  struct MHD_Connection *connection; // FIXME - suboptimal API
+  union MHD_RequestInfoFixedData rif;
+
+#define FIXME 1
+#if FIXME
+  sc = MHD_request_get_info_fixed (request,
+                                   MHD_REQUEST_INFO_FIXED_CONNECTION,
+                                   &rif);
+  if (MHD_SC_OK != sc)
+    return NULL;
+  connection = rif.v_connection;
+#endif
+  /* 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_BAUTH_REQ_INFO,
+                                     &dd);
+  if (MHD_SC_OK != sc)
+    return MHD_action_basic_auth_required_response (
+      connection, // FIXME: I'd like to see 'request' here.
+      "test-realm",
+      MHD_YES,
+      MHD_response_from_empty (
+        MHD_HTTP_STATUS_FORBIDDEN));
+  ba = dd.v_bauth_info;
+  if (NULL == ba)
+    return MHD_action_basic_auth_required_response (
+      connection, // FIXME: I'd like to see 'request' here.
+      "test-realm",
+      MHD_YES,
+      MHD_response_from_empty (
+        MHD_HTTP_STATUS_FORBIDDEN));
+  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])) )
+    return MHD_action_basic_auth_required_response (
+      connection, // FIXME: I'd like to see 'request' here.
+      "test-realm",
+      MHD_YES,
+      MHD_response_from_empty (
+        MHD_HTTP_STATUS_FORBIDDEN));
+  return MHD_action_from_response (
+    request,
+    MHD_response_from_empty (
+      MHD_HTTP_STATUS_NO_CONTENT));
+}

+ 104 - 0
src/tests/client_server/test_authentication.c

@@ -0,0 +1,104 @@
+/*
+  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[])
+{
+  struct MHD_DaemonOptionAndValue thread1auto[] = {
+    MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_AUTO),
+    MHD_D_OPTION_WM_WORKER_THREADS (1),
+    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[] = {
+    {
+      .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 = 200,
+    },
+    {
+      .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 = 200,
+    },
+    // TODO: digest auth
+    {
+      .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;
+}