[gupnp: 21/25] tests: Add tests for ACL




commit cd93be9559983e2d0445dd78d400c3112584025a
Author: Jens Georg <mail jensge org>
Date:   Tue Dec 28 13:51:13 2021 +0100

    tests: Add tests for ACL

 tests/meson.build |   2 +-
 tests/test-acl.c  | 458 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 459 insertions(+), 1 deletion(-)
---
diff --git a/tests/meson.build b/tests/meson.build
index a6f0b4b..b976e8a 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1,4 +1,4 @@
-foreach program : ['context', 'bugs', 'service']
+foreach program : ['context', 'bugs', 'service', 'acl']
     test(
         program,
         executable(
diff --git a/tests/test-acl.c b/tests/test-acl.c
new file mode 100644
index 0000000..1c96a37
--- /dev/null
+++ b/tests/test-acl.c
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2021 Jens Georg.
+ *
+ * Author: Jens Georg <mail jensge org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <config.h>
+
+#include "libgupnp/gupnp.h"
+
+#include <string.h>
+#include <libsoup/soup.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+static GUPnPContext *
+create_context (const char *localhost, guint16 port, GError **error)
+{
+        return GUPNP_CONTEXT (g_initable_new (GUPNP_TYPE_CONTEXT,
+                                              NULL,
+                                              error,
+                                              "host-ip",
+                                              localhost,
+                                              "port",
+                                              port,
+                                              NULL));
+}
+
+
+typedef struct {
+        GMainLoop *loop;
+        GUPnPContext *context;
+        SoupSession *session;
+        char *base_uri;
+} ContextTestFixture;
+
+static void
+test_fixture_setup (ContextTestFixture *tf, gconstpointer user_data)
+{
+        GError *error = NULL;
+
+        tf->context = create_context ((const char *) user_data, 0, &error);
+
+        g_assert_nonnull (tf->context);
+        g_assert_no_error (error);
+
+        tf->loop = g_main_loop_new (NULL, FALSE);
+        tf->session = soup_session_new ();
+
+        GSList *uris =
+                soup_server_get_uris (gupnp_context_get_server (tf->context));
+        g_assert_nonnull (uris);
+        g_assert_cmpint (g_slist_length (uris), ==, 1);
+
+        tf->base_uri = g_uri_to_string (uris->data);
+        g_slist_free_full (uris, (GDestroyNotify) g_uri_unref);
+}
+
+static void
+test_fixture_teardown (ContextTestFixture *tf, gconstpointer user_data)
+{
+        g_free (tf->base_uri);
+        g_object_unref (tf->context);
+
+        // Make sure the source teardown handlers get run so we don't confuse valgrind
+        g_timeout_add (500, (GSourceFunc) g_main_loop_quit, tf->loop);
+        g_main_loop_run (tf->loop);
+        g_main_loop_unref (tf->loop);
+        g_object_unref (tf->session);
+}
+
+typedef struct _DefaultCallbackData {
+        GBytes *bytes;
+        GMainLoop *loop;
+} DefaultCallbackData;
+
+void
+soup_message_default_callback (GObject *source,
+                               GAsyncResult *res,
+                               gpointer user_data)
+{
+        DefaultCallbackData *d = user_data;
+        GError *error = NULL;
+        d->bytes = soup_session_send_and_read_finish (SOUP_SESSION (source),
+                                                      res,
+                                                      &error);
+        g_assert_no_error (error);
+        g_assert_nonnull (d->bytes);
+        g_bytes_unref (d->bytes);
+        g_main_loop_quit (d->loop);
+}
+
+static GUPnPAclInterface *acl_parent_iface = NULL;
+
+static gboolean
+test_acl_can_sync (GUPnPAcl *acl);
+
+static gboolean
+test_acl_is_allowed (GUPnPAcl *self,
+                     struct _GUPnPDevice *device,
+                     struct _GUPnPService *service,
+                     const char *path,
+                     const char *address,
+                     const char *agent);
+
+static void
+test_acl_is_allowed_async (GUPnPAcl *acl,
+                           struct _GUPnPDevice *device,
+                           struct _GUPnPService *service,
+                           const char *path,
+                           const char *address,
+                           const char *agent,
+                           GCancellable *cancellable,
+                           GAsyncReadyCallback callback,
+                           gpointer user_data);
+
+static gboolean
+test_acl_is_allowed_finish (GUPnPAcl *acl, GAsyncResult *res, GError **error);
+
+static void
+test_acl_interface_init (gpointer g_iface, gpointer iface_data)
+{
+        GUPnPAclInterface *iface = (GUPnPAclInterface *) g_iface;
+        acl_parent_iface = g_type_interface_peek_parent (g_iface);
+        iface->can_sync = test_acl_can_sync;
+        iface->is_allowed = test_acl_is_allowed;
+        iface->is_allowed_async = test_acl_is_allowed_async;
+        iface->is_allowed_finish = test_acl_is_allowed_finish;
+}
+
+struct _TestAcl {
+        GObject parent;
+
+        gboolean can_sync;
+        gboolean is_allowed;
+        gboolean finish_should_fail;
+
+        int can_sync_called;
+        int is_allowed_called;
+        int is_allowed_async_called;
+        int is_allowed_finish_called;
+};
+
+
+G_DECLARE_FINAL_TYPE (TestAcl, test_acl, TEST, ACL, GObject)
+
+static void
+test_acl_init (TestAcl *instance)
+{}
+
+G_DEFINE_TYPE_EXTENDED (TestAcl,
+                        test_acl,
+                        G_TYPE_OBJECT,
+                        0,
+                        G_IMPLEMENT_INTERFACE (GUPNP_TYPE_ACL,
+                                               test_acl_interface_init))
+
+static void
+test_acl_class_init (TestAclClass *klass)
+{}
+
+
+static gboolean
+test_acl_can_sync (GUPnPAcl *acl)
+{
+        TestAcl *self = TEST_ACL (acl);
+
+        self->can_sync_called++;
+
+        return self->can_sync;
+}
+
+static gboolean
+test_acl_is_allowed (GUPnPAcl *acl,
+                     struct _GUPnPDevice *device,
+                     struct _GUPnPService *service,
+                     const char *path,
+                     const char *address,
+                     const char *agent)
+{
+        TestAcl *self = TEST_ACL (acl);
+
+        self->is_allowed_called++;
+
+        return self->is_allowed;
+}
+
+static gboolean
+on_timeout (gpointer user_data)
+{
+        TestAcl *self =
+                TEST_ACL (g_task_get_source_object (G_TASK (user_data)));
+        g_task_return_boolean (G_TASK (user_data), self->is_allowed);
+        g_object_unref (G_OBJECT (user_data));
+
+        return G_SOURCE_REMOVE;
+}
+
+static void
+test_acl_is_allowed_async (GUPnPAcl *acl,
+                           struct _GUPnPDevice *device,
+                           struct _GUPnPService *service,
+                           const char *path,
+                           const char *address,
+                           const char *agent,
+                           GCancellable *cancellable,
+                           GAsyncReadyCallback callback,
+                           gpointer user_data)
+{
+        TestAcl *self = TEST_ACL (acl);
+        GTask *task = g_task_new (self, cancellable, callback, user_data);
+
+        self->is_allowed_async_called++;
+
+        g_timeout_add (100, on_timeout, task);
+}
+
+static gboolean
+test_acl_is_allowed_finish (GUPnPAcl *acl, GAsyncResult *res, GError **error)
+{
+
+        TestAcl *self = TEST_ACL (acl);
+
+        self->is_allowed_finish_called++;
+
+        if (g_task_is_valid (res, acl)) {
+                return self->is_allowed;
+        }
+
+        return FALSE;
+}
+
+static void
+acl_test_handler (SoupServer *server,
+                  SoupServerMessage *msg,
+                  const char *path,
+                  GHashTable *query,
+                  gpointer user_data)
+{
+        soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL);
+}
+
+void
+destroy_server_handler_data (gpointer data)
+{
+        (*(int *) (data))++;
+}
+
+static void
+test_gupnp_context_acl (ContextTestFixture *tf, gconstpointer user_data)
+{
+        GError *error = NULL;
+        DefaultCallbackData d = { .loop = tf->loop };
+        int destroy_called = 0;
+
+        // There should be no ACL
+        g_assert_null (gupnp_context_get_acl (tf->context));
+
+        TestAcl *acl = g_object_new (test_acl_get_type (), NULL);
+        acl->can_sync = TRUE;
+        acl->is_allowed = TRUE;
+
+        gupnp_context_set_acl (tf->context, GUPNP_ACL (acl));
+        g_object_unref (acl);
+
+        g_assert (acl == (TestAcl *) gupnp_context_get_acl (tf->context));
+
+        gupnp_context_add_server_handler (tf->context,
+                                          FALSE,
+                                          "/foo",
+                                          acl_test_handler,
+                                          &destroy_called,
+                                          destroy_server_handler_data);
+
+        char *uri = g_uri_resolve_relative (tf->base_uri, "/foo", 0, &error);
+        g_assert_nonnull (uri);
+        g_assert_no_error (error);
+
+        char *request_uri = gupnp_context_rewrite_uri (tf->context, uri);
+        g_free (uri);
+
+        SoupMessage *msg = soup_message_new (SOUP_METHOD_HEAD, request_uri);
+        soup_session_send_and_read_async (tf->session,
+                                          msg,
+                                          G_PRIORITY_DEFAULT,
+                                          NULL,
+                                          soup_message_default_callback,
+                                          &d);
+        g_main_loop_run (tf->loop);
+
+        g_assert_cmpint (soup_message_get_status (msg), ==, SOUP_STATUS_OK);
+        g_assert_cmpint (acl->can_sync_called, ==, 0);
+        g_assert_cmpint (acl->is_allowed_called, ==, 0);
+        g_assert_cmpint (acl->is_allowed_async_called, ==, 0);
+        g_assert_cmpint (acl->is_allowed_finish_called, ==, 0);
+
+        soup_server_remove_handler (gupnp_context_get_server (tf->context),
+                                    "/foo");
+
+        gupnp_context_add_server_handler (tf->context,
+                                          TRUE,
+                                          "/foo",
+                                          acl_test_handler,
+                                          NULL,
+                                          NULL);
+
+        g_object_unref (msg);
+        msg = soup_message_new (SOUP_METHOD_HEAD, request_uri);
+        soup_session_send_and_read_async (tf->session,
+                                          msg,
+                                          G_PRIORITY_DEFAULT,
+                                          NULL,
+                                          soup_message_default_callback,
+                                          &d);
+        g_main_loop_run (tf->loop);
+
+        g_assert_cmpint (soup_message_get_status (msg), ==, SOUP_STATUS_OK);
+        g_assert_cmpint (acl->can_sync_called, ==, 1);
+        g_assert_cmpint (acl->is_allowed_called, ==, 1);
+        g_assert_cmpint (acl->is_allowed_async_called, ==, 0);
+        g_assert_cmpint (acl->is_allowed_finish_called, ==, 0);
+
+
+        acl->is_allowed_called = 0;
+        acl->can_sync_called = 0;
+        acl->can_sync = FALSE;
+        g_object_unref (msg);
+        msg = soup_message_new (SOUP_METHOD_HEAD, request_uri);
+        soup_session_send_and_read_async (tf->session,
+                                          msg,
+                                          G_PRIORITY_DEFAULT,
+                                          NULL,
+                                          soup_message_default_callback,
+                                          &d);
+        g_main_loop_run (tf->loop);
+
+        g_assert_cmpint (soup_message_get_status (msg), ==, SOUP_STATUS_OK);
+        g_assert_cmpint (acl->can_sync_called, ==, 1);
+        g_assert_cmpint (acl->is_allowed_called, ==, 0);
+        g_assert_cmpint (acl->is_allowed_async_called, ==, 1);
+        g_assert_cmpint (acl->is_allowed_finish_called, ==, 1);
+
+        acl->is_allowed = FALSE;
+        g_object_unref (msg);
+        msg = soup_message_new (SOUP_METHOD_HEAD, request_uri);
+        soup_session_send_and_read_async (tf->session,
+                                          msg,
+                                          G_PRIORITY_DEFAULT,
+                                          NULL,
+                                          soup_message_default_callback,
+                                          &d);
+        g_main_loop_run (tf->loop);
+
+        // This should yield forbidden, even if the handler only returns OK
+        g_assert_cmpint (soup_message_get_status (msg),
+                         ==,
+                         SOUP_STATUS_FORBIDDEN);
+
+        acl->is_allowed = FALSE;
+        acl->can_sync = TRUE;
+        g_object_unref (msg);
+        msg = soup_message_new (SOUP_METHOD_HEAD, request_uri);
+        soup_session_send_and_read_async (tf->session,
+                                          msg,
+                                          G_PRIORITY_DEFAULT,
+                                          NULL,
+                                          soup_message_default_callback,
+                                          &d);
+        g_main_loop_run (tf->loop);
+
+        // This should yield forbidden, even if the handler only returns OK
+        g_assert_cmpint (soup_message_get_status (msg),
+                         ==,
+                         SOUP_STATUS_FORBIDDEN);
+
+        soup_server_remove_handler (gupnp_context_get_server (tf->context),
+                                    "/foo");
+
+        g_assert_cmpint (destroy_called, ==, 1);
+
+        g_object_unref (msg);
+        g_free (request_uri);
+}
+
+int
+main (int argc, char *argv[])
+{
+        GError *error;
+
+        g_test_init (&argc, &argv, NULL);
+
+        GPtrArray *addresses = g_ptr_array_sized_new (2);
+        g_ptr_array_add (addresses, g_strdup ("127.0.0.1"));
+        g_ptr_array_add (addresses, g_strdup ("::1"));
+
+        // Detect if there is a special network device with "proper" ip addresses to check also link-local 
addresses
+        GUPnPContext *c = g_initable_new (GUPNP_TYPE_CONTEXT,
+                                          NULL,
+                                          &error,
+                                          "interface",
+                                          "gupnp0",
+                                          "address-family",
+                                          G_SOCKET_FAMILY_IPV4,
+                                          NULL);
+
+        if (c != NULL) {
+                g_ptr_array_add (
+                        addresses,
+                        g_strdup (gssdp_client_get_host_ip (GSSDP_CLIENT (c))));
+                g_object_unref (c);
+        }
+
+        c = g_initable_new (GUPNP_TYPE_CONTEXT,
+                            NULL,
+                            &error,
+                            "interface",
+                            "gupnp0",
+                            "address-family",
+                            G_SOCKET_FAMILY_IPV6,
+                            NULL);
+
+        if (c != NULL) {
+                g_ptr_array_add (
+                        addresses,
+                        g_strdup (gssdp_client_get_host_ip (GSSDP_CLIENT (c))));
+                g_object_unref (c);
+        }
+
+        g_ptr_array_add (addresses, NULL);
+
+        char **data = (char **) g_ptr_array_free (addresses, FALSE);
+        char **it = data;
+        while (*it != NULL) {
+                char *name = NULL;
+
+                name = g_strdup_printf ("/context/http/acl/%s",
+                                        *it);
+                g_test_add (name,
+                            ContextTestFixture,
+                            *it,
+                            test_fixture_setup,
+                            test_gupnp_context_acl,
+                            test_fixture_teardown);
+                g_free (name);
+                it++;
+        }
+
+        int result = g_test_run ();
+
+        g_strfreev (data);
+
+        return result;
+}
+


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