LCOV - code coverage report
Current view: top level - libmalcontent-timer/tests - extension-agent-polkit.c (source / functions) Coverage Total Hit
Test: 2 coverage DB files Lines: 98.4 % 547 538
Test Date: 2025-11-20 19:57:30 Functions: 96.4 % 28 27
Branches: 76.4 % 144 110

             Branch data     Line data    Source code
       1                 :             : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
       2                 :             :  *
       3                 :             :  * Copyright 2025 GNOME Foundation, Inc.
       4                 :             :  *
       5                 :             :  * SPDX-License-Identifier: LGPL-2.1-or-later
       6                 :             :  *
       7                 :             :  * This library is free software; you can redistribute it and/or
       8                 :             :  * modify it under the terms of the GNU Lesser General Public
       9                 :             :  * License as published by the Free Software Foundation; either
      10                 :             :  * version 2.1 of the License, or (at your option) any later version.
      11                 :             :  *
      12                 :             :  * This library is distributed in the hope that it will be useful,
      13                 :             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      14                 :             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      15                 :             :  * Lesser General Public License for more details.
      16                 :             :  *
      17                 :             :  * You should have received a copy of the GNU Lesser General Public
      18                 :             :  * License along with this library; if not, write to the Free Software
      19                 :             :  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
      20                 :             :  *
      21                 :             :  * Authors:
      22                 :             :  *  - Philip Withnall <pwithnall@gnome.org>
      23                 :             :  */
      24                 :             : 
      25                 :             : #include "config.h"
      26                 :             : 
      27                 :             : #include <glib.h>
      28                 :             : #include <gio/gio.h>
      29                 :             : #include <libmalcontent-timer/extension-agent-object.h>
      30                 :             : #include <libmalcontent-timer/extension-agent-object-polkit.h>
      31                 :             : #include <libglib-testing/dbus-queue.h>
      32                 :             : #include <locale.h>
      33                 :             : #include <pwd.h>
      34                 :             : #include <string.h>
      35                 :             : #include <sys/types.h>
      36                 :             : 
      37                 :             : #include "polkit-authority-iface.h"
      38                 :             : 
      39                 :             : 
      40                 :             : /* Fixture for tests which have an `MctExtensionAgentObjectPolkit` interacting
      41                 :             :  * with polkit over D-Bus. The polkit service is mocked up using @queue, which
      42                 :             :  * allows us to reply to D-Bus calls from the code under test from within the
      43                 :             :  * test process. The code under test is the `MctExtensionAgentObjectPolkit`. It
      44                 :             :  * only has D-Bus interface, so the tests need to trigger it by making D-Bus
      45                 :             :  * calls (rather than C function calls) via `timerd_connection`. In this way,
      46                 :             :  * the unit test pretends to be the `malcontent-timerd` process which would be
      47                 :             :  * interacting with the agent in production.
      48                 :             :  */
      49                 :             : typedef struct
      50                 :             : {
      51                 :             :   GtDBusQueue *queue;  /* (owned) */
      52                 :             :   MctExtensionAgentObjectPolkit *extension_agent;  /* (owned) */
      53                 :             :   unsigned int extension_agent_name_id;
      54                 :             :   GDBusConnection *timerd_connection;  /* (owned) */
      55                 :             : } BusFixture;
      56                 :             : 
      57                 :             : static void name_lost_cb (GDBusConnection *connection,
      58                 :             :                           const char      *name,
      59                 :             :                           void            *user_data);
      60                 :             : static GDBusConnection *dbus_queue_open_additional_client_connection (void);
      61                 :             : 
      62                 :             : static void
      63                 :          36 : bus_set_up (BusFixture *fixture,
      64                 :             :             const void *test_data)
      65                 :             : {
      66                 :          35 :   g_autoptr(GError) local_error = NULL;
      67                 :             : 
      68                 :          36 :   fixture->queue = gt_dbus_queue_new ();
      69                 :             : 
      70                 :          36 :   gt_dbus_queue_connect (fixture->queue, &local_error);
      71                 :          35 :   g_assert_no_error (local_error);
      72                 :             : 
      73                 :          35 :   gt_dbus_queue_own_name (fixture->queue, "org.freedesktop.PolicyKit1");
      74                 :             : 
      75                 :          35 :   gt_dbus_queue_export_object (fixture->queue,
      76                 :             :                                "/org/freedesktop/PolicyKit1/Authority",
      77                 :             :                                (GDBusInterfaceInfo *) &org_freedesktop_policy_kit1_authority_interface,
      78                 :             :                                &local_error);
      79                 :          35 :   g_assert_no_error (local_error);
      80                 :             : 
      81                 :          35 :   fixture->extension_agent_name_id =
      82                 :          35 :       g_bus_own_name_on_connection (gt_dbus_queue_get_client_connection (fixture->queue),
      83                 :             :                                     "org.freedesktop.MalcontentTimer1.ExtensionAgent",
      84                 :             :                                     G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE,
      85                 :             :                                     NULL,
      86                 :             :                                     name_lost_cb,
      87                 :             :                                     NULL,
      88                 :             :                                     NULL);
      89                 :             : 
      90                 :          35 :   fixture->extension_agent = mct_extension_agent_object_polkit_new (gt_dbus_queue_get_client_connection (fixture->queue),
      91                 :             :                                                                     "/org/freedesktop/MalcontentTimer1/ExtensionAgent");
      92                 :          35 :   mct_extension_agent_object_register (MCT_EXTENSION_AGENT_OBJECT (fixture->extension_agent), &local_error);
      93                 :          35 :   g_assert_no_error (local_error);
      94                 :             : 
      95                 :          35 :   fixture->timerd_connection = dbus_queue_open_additional_client_connection ();
      96                 :             : 
      97                 :             :   /* Create a mock app desktop file which we can refer to in the tests. It will
      98                 :             :    * be automatically deleted by G_TEST_OPTION_ISOLATE_DIRS on teardown. */
      99                 :          35 :   g_autofree char *applications_dir = NULL;
     100                 :          35 :   g_autofree char *desktop_file_path = NULL;
     101                 :             : 
     102                 :          35 :   applications_dir = g_build_filename (g_get_user_data_dir (), "applications", NULL);
     103                 :          35 :   g_assert_no_errno (g_mkdir_with_parents (applications_dir, 0755));
     104                 :             : 
     105                 :          35 :   desktop_file_path = g_build_filename (applications_dir, "org.freedesktop.Malcontent.TestApp.desktop", NULL);
     106                 :          35 :   g_file_set_contents (desktop_file_path,
     107                 :             :                        "[Desktop Entry]\n"
     108                 :             :                        "Name=Test Application\n"
     109                 :             :                        "Exec=true %U\n"
     110                 :             :                        "Type=Application\n",
     111                 :             :                        -1, &local_error);
     112                 :          35 :   g_assert_no_error (local_error);
     113                 :          35 : }
     114                 :             : 
     115                 :             : static void
     116                 :          35 : bus_tear_down (BusFixture *fixture,
     117                 :             :                const void *test_data)
     118                 :             : {
     119         [ +  + ]:          35 :   if (fixture->timerd_connection != NULL)
     120                 :          34 :     g_dbus_connection_close_sync (fixture->timerd_connection, NULL, NULL);
     121         [ +  + ]:          35 :   g_clear_object (&fixture->timerd_connection);
     122                 :          35 :   mct_extension_agent_object_unregister (MCT_EXTENSION_AGENT_OBJECT (fixture->extension_agent));
     123         [ +  - ]:          35 :   g_clear_object (&fixture->extension_agent);
     124         [ +  - ]:          35 :   g_clear_handle_id (&fixture->extension_agent_name_id, g_bus_unown_name);
     125                 :          35 :   gt_dbus_queue_disconnect (fixture->queue, TRUE);
     126         [ +  - ]:          35 :   g_clear_pointer (&fixture->queue, gt_dbus_queue_free);
     127                 :          35 : }
     128                 :             : 
     129                 :             : static void
     130                 :         173 : async_result_cb (GObject      *source_object,
     131                 :             :                  GAsyncResult *result,
     132                 :             :                  void         *user_data)
     133                 :             : {
     134                 :         173 :   GAsyncResult **result_out = user_data;
     135                 :             : 
     136                 :         173 :   g_assert (*result_out == NULL);
     137                 :         173 :   *result_out = g_object_ref (result);
     138                 :         173 :   g_main_context_wakeup (NULL);
     139                 :         173 : }
     140                 :             : 
     141                 :             : static void
     142                 :           0 : name_lost_cb (GDBusConnection *connection,
     143                 :             :               const char      *name,
     144                 :             :               void            *user_data)
     145                 :             : {
     146                 :             :   g_assert_not_reached ();
     147                 :             : }
     148                 :             : 
     149                 :             : static GDBusConnection *
     150                 :          37 : dbus_queue_open_additional_client_connection (void)
     151                 :             : {
     152                 :          37 :   g_autoptr(GAsyncResult) result = NULL;
     153                 :          37 :   g_autoptr(GDBusConnection) connection = NULL;
     154                 :          37 :   g_autoptr(GError) local_error = NULL;
     155                 :             : 
     156                 :             :   /* FIXME: Hackily get the bus address as set by the GTestDBus instance
     157                 :             :    * internally in the GtDBusQueue. We ideally need a new helper method on the
     158                 :             :    * GtDBusQueue to get another connection. */
     159                 :          37 :   g_dbus_connection_new_for_address (g_getenv ("DBUS_SESSION_BUS_ADDRESS"),
     160                 :             :                                      G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
     161                 :             :                                      G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
     162                 :             :                                      NULL,
     163                 :             :                                      NULL,
     164                 :             :                                      async_result_cb,
     165                 :             :                                      &result);
     166         [ +  + ]:         171 :   while (result == NULL)
     167                 :         134 :     g_main_context_iteration (NULL, TRUE);
     168                 :          37 :   connection = g_dbus_connection_new_for_address_finish (result, &local_error);
     169                 :          37 :   g_assert_no_error (local_error);
     170                 :          37 :   g_assert_nonnull (connection);
     171                 :             : 
     172                 :          37 :   return g_steal_pointer (&connection);
     173                 :             : }
     174                 :             : 
     175                 :             : #define assert_cmpvariant_parsed(v1, v2_string, ...) \
     176                 :             :   G_STMT_START \
     177                 :             :     { \
     178                 :             :       g_autoptr(GVariant) v2 = g_variant_new_parsed ((v2_string), ##__VA_ARGS__); \
     179                 :             :       g_assert_cmpvariant ((v1), v2); \
     180                 :             :     } \
     181                 :             :   G_STMT_END
     182                 :             : 
     183                 :             : /* Test that a D-Bus object exists, by assuming that it implements the
     184                 :             :  * o.fd.DBus.Introspectable interface, and querying that. We don’t care what the
     185                 :             :  * result is, we care whether it errors. */
     186                 :             : static gboolean
     187                 :          46 : dbus_object_exists (GDBusConnection *connection,
     188                 :             :                     const char      *bus_name,
     189                 :             :                     const char      *object_path)
     190                 :             : {
     191                 :          46 :   g_autoptr(GAsyncResult) result = NULL;
     192                 :          46 :   g_autoptr(GVariant) reply = NULL;
     193                 :             :   const char *introspection_data;
     194                 :          46 :   g_autoptr(GError) local_error = NULL;
     195                 :             : 
     196                 :          46 :   g_dbus_connection_call (connection,
     197                 :             :                           bus_name,
     198                 :             :                           object_path,
     199                 :             :                           "org.freedesktop.DBus.Introspectable",
     200                 :             :                           "Introspect",
     201                 :             :                           NULL,
     202                 :             :                           G_VARIANT_TYPE ("(s)"),
     203                 :             :                           G_DBUS_CALL_FLAGS_NONE,
     204                 :             :                           G_MAXINT,  /* timeout (ms) */
     205                 :             :                           NULL,
     206                 :             :                           async_result_cb,
     207                 :             :                           &result);
     208         [ +  + ]:         170 :   while (result == NULL)
     209                 :         124 :     g_main_context_iteration (NULL, TRUE);
     210                 :          46 :   reply = g_dbus_connection_call_finish (connection, result, &local_error);
     211                 :             : 
     212         [ -  + ]:          46 :   if (g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
     213                 :           0 :     return FALSE;
     214                 :             : 
     215                 :          46 :   g_assert_no_error (local_error);
     216                 :          46 :   g_assert_nonnull (reply);
     217                 :             : 
     218                 :             :   /* FIXME: GDBus can return `<node></node>` for an object which doesn’t exist,
     219                 :             :    * rather than returning a D-Bus error. This doesn’t seem right to me, but
     220                 :             :    * it’s the behaviour in stable releases so we have to deal with it. */
     221                 :          46 :   g_variant_get (reply, "(&s)", &introspection_data);
     222         [ +  + ]:          46 :   if (strstr (introspection_data, "<interface") == NULL)
     223                 :          26 :     return FALSE;
     224                 :             : 
     225                 :          20 :   return TRUE;
     226                 :             : }
     227                 :             : 
     228                 :             : /* Generic mock polkit implementation which expects a single
     229                 :             :  * `CheckAuthorization()` call, and returns the given `.result` for it. Intended
     230                 :             :  * to be used for writing tests for the extension agent process. */
     231                 :             : typedef struct
     232                 :             : {
     233                 :             :   uid_t expected_subject_uid;
     234                 :             :   const char *expected_polkit_message;
     235                 :             :   const char *expected_duration_str;
     236                 :             :   const char *expected_app_name;
     237                 :             :   gboolean expected_allow_user_interaction;
     238                 :             :   struct
     239                 :             :     {
     240                 :             :       /* Either the first three fields should be set, or the second two */
     241                 :             :       gboolean is_authorized;
     242                 :             :       gboolean is_challenge;
     243                 :             :       const char *details;  /* (nullable) */
     244                 :             :       const char *error_name;  /* (nullable) */
     245                 :             :       const char *error_message;  /* (nullable) */
     246                 :             :     } result;
     247                 :             : } PolkitData;
     248                 :             : 
     249                 :             : /* This is run in a worker thread. */
     250                 :             : static void
     251                 :          20 : polkit_server_cb (GtDBusQueue *queue,
     252                 :             :                   void        *user_data)
     253                 :             : {
     254                 :          20 :   const PolkitData *data = user_data;
     255                 :          20 :   g_autoptr(GDBusMethodInvocation) invocation1 = NULL;
     256                 :          20 :   g_autoptr(GVariant) subject_variant = NULL;
     257                 :             :   const char *action_id;
     258                 :          20 :   g_autoptr(GVariantIter) details_iter = NULL;
     259                 :             :   uint32_t flags;
     260                 :             :   const char *cancellation_id;
     261                 :             :   const char *details_key, *details_value;
     262                 :             :   size_t n_expected_entries;
     263                 :             : 
     264                 :             :   /* Handle the CheckAuthorization() call. */
     265                 :          20 :   invocation1 =
     266                 :          20 :       gt_dbus_queue_assert_pop_message (queue,
     267                 :             :                                         "/org/freedesktop/PolicyKit1/Authority",
     268                 :             :                                         "org.freedesktop.PolicyKit1.Authority",
     269                 :             :                                         "CheckAuthorization",
     270                 :             :                                         "(@r&sa{ss}u&s)",
     271                 :             :                                         &subject_variant,
     272                 :             :                                         &action_id,
     273                 :             :                                         &details_iter,
     274                 :             :                                         &flags,
     275                 :             :                                         &cancellation_id);
     276                 :             : 
     277         [ -  + ]:          20 :   assert_cmpvariant_parsed (subject_variant,
     278                 :             :                             "('unix-process', {'pidfd': <@h 0>, 'uid': <@i %i>})",
     279                 :             :                             data->expected_subject_uid);
     280                 :          20 :   g_assert_cmpstr (action_id, ==, "org.freedesktop.Malcontent.SessionLimits.Extend");
     281                 :             : 
     282         [ +  + ]:         141 :   while (g_variant_iter_loop (details_iter, "{&s&s}", &details_key, &details_value))
     283                 :             :     {
     284         [ +  + ]:         121 :       if (g_str_equal (details_key, "polkit.message"))
     285                 :          20 :         g_assert_cmpstr (details_value, ==, data->expected_polkit_message);
     286         [ +  + ]:         101 :       else if (g_str_equal (details_key, "polkit.gettext_domain"))
     287                 :          20 :         g_assert_cmpstr (details_value, ==, "malcontent");
     288         [ +  + ]:          81 :       else if (g_str_equal (details_key, "polkit.icon_name"))
     289                 :          20 :         g_assert_cmpstr (details_value, ==, "org.freedesktop.MalcontentControl");
     290         [ +  + ]:          61 :       else if (g_str_equal (details_key, "child_user_display_name"))
     291                 :          20 :         g_assert_cmpstr (details_value, !=, "");
     292         [ +  + ]:          41 :       else if (g_str_equal (details_key, "duration_str"))
     293                 :          18 :         g_assert_cmpstr (details_value, ==, data->expected_duration_str);
     294         [ +  + ]:          23 :       else if (g_str_equal (details_key, "time_str"))
     295                 :          20 :         g_assert_cmpstr (details_value, !=, "");  /* can’t check the actual time as it varies */
     296         [ +  - ]:           3 :       else if (g_str_equal (details_key, "app_name"))
     297                 :           3 :         g_assert_cmpstr (details_value, ==, data->expected_app_name);
     298                 :             :       else
     299                 :             :         g_assert_not_reached ();
     300                 :             :     }
     301                 :             : 
     302                 :          20 :   n_expected_entries = 5;
     303         [ +  + ]:          20 :   if (data->expected_duration_str != NULL)
     304                 :          18 :     n_expected_entries++;
     305         [ +  + ]:          20 :   if (data->expected_app_name != NULL)
     306                 :           3 :     n_expected_entries++;
     307                 :             : 
     308                 :          20 :   g_assert_cmpuint (g_variant_iter_n_children (details_iter), ==, n_expected_entries);
     309                 :             : 
     310                 :          20 :   g_assert_cmpuint (flags, ==, data->expected_allow_user_interaction ? 0x01 : 0x00);
     311                 :          20 :   g_assert_cmpstr (cancellation_id, !=, "");
     312                 :             : 
     313         [ +  + ]:          20 :   if (data->result.error_name == NULL)
     314                 :             :     {
     315                 :             :       /* Note: The extra struct wrapper is correct as per the polkit API */
     316                 :          15 :       g_dbus_method_invocation_return_value (invocation1,
     317                 :             :                                              g_variant_new ("((bb@a{ss}))",
     318                 :          15 :                                                             data->result.is_authorized,
     319                 :          15 :                                                             data->result.is_challenge,
     320                 :          15 :                                                             g_variant_new_parsed (data->result.details)));
     321                 :             :     }
     322                 :             :   else
     323                 :             :     {
     324                 :           5 :       g_dbus_method_invocation_return_dbus_error (invocation1,
     325                 :           5 :                                                   data->result.error_name,
     326                 :           5 :                                                   data->result.error_message);
     327                 :             :     }
     328                 :          20 : }
     329                 :             : 
     330                 :             : static void
     331                 :           1 : test_extension_agent_polkit_construction (BusFixture *fixture,
     332                 :             :                                           const void *test_data)
     333                 :             : {
     334                 :           1 :   g_autoptr(GDBusConnection) connection = NULL;
     335                 :           1 :   g_autofree char *object_path = NULL;
     336                 :             :   gboolean busy;
     337                 :             : 
     338                 :           1 :   g_test_summary ("Smoketest for constructing an MctExtensionAgentObjectPolkit");
     339                 :             : 
     340                 :             :   /* The extension agent object was created in bus_set_up(); we now just need
     341                 :             :    * to query its methods. */
     342                 :           1 :   g_assert_true (mct_extension_agent_object_get_connection (MCT_EXTENSION_AGENT_OBJECT (fixture->extension_agent)) ==
     343                 :             :                  gt_dbus_queue_get_client_connection (fixture->queue));
     344                 :           1 :   g_assert_false (mct_extension_agent_object_get_busy (MCT_EXTENSION_AGENT_OBJECT (fixture->extension_agent)));
     345                 :             : 
     346                 :           1 :   g_object_get (fixture->extension_agent,
     347                 :             :                 "connection", &connection,
     348                 :             :                 "object-path", &object_path,
     349                 :             :                 "busy", &busy,
     350                 :             :                 NULL);
     351                 :           1 :   g_assert_true (connection == gt_dbus_queue_get_client_connection (fixture->queue));
     352                 :           1 :   g_assert_cmpstr (object_path, ==, "/org/freedesktop/MalcontentTimer1/ExtensionAgent");
     353                 :           1 :   g_assert_false (busy);
     354                 :           1 : }
     355                 :             : 
     356                 :             : static void
     357                 :          20 : request_response_cb (GDBusConnection *connection,
     358                 :             :                      const char      *sender_name,
     359                 :             :                      const char      *object_path,
     360                 :             :                      const char      *interface_name,
     361                 :             :                      const char      *signal_name,
     362                 :             :                      GVariant        *parameters,
     363                 :             :                      void            *user_data)
     364                 :             : {
     365                 :          20 :   GVariant **parameters_out = user_data;
     366                 :             : 
     367                 :             :   /* This is the extension agent’s unique name, which we don’t easily have
     368                 :             :    * access to in this callback: */
     369                 :          20 :   g_assert_cmpstr (sender_name, !=, "");
     370                 :             : 
     371                 :          20 :   g_assert_true (g_str_has_prefix (object_path, "/org/freedesktop/MalcontentTimer1/ExtensionAgent/Request"));
     372                 :          20 :   g_assert_cmpstr (interface_name, ==, "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request");
     373                 :          20 :   g_assert_cmpstr (signal_name, ==, "Response");
     374                 :             : 
     375                 :          20 :   g_assert (*parameters_out == NULL);
     376                 :          20 :   *parameters_out = g_variant_ref (parameters);
     377                 :          20 :   g_main_context_wakeup (NULL);
     378                 :          20 : }
     379                 :             : 
     380                 :             : static unsigned int
     381                 :          21 : extension_agent_subscribe_responses (GDBusConnection  *connection,
     382                 :             :                                      GVariant        **response_variant_out)
     383                 :             : {
     384                 :          21 :   return g_dbus_connection_signal_subscribe (connection,
     385                 :             :                                              "org.freedesktop.MalcontentTimer1.ExtensionAgent",
     386                 :             :                                              "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request",
     387                 :             :                                              "Response",
     388                 :             :                                              NULL,  /* object path */
     389                 :             :                                              NULL,  /* arg0 */
     390                 :             :                                              G_DBUS_SIGNAL_FLAGS_NONE,
     391                 :             :                                              request_response_cb,
     392                 :             :                                              response_variant_out,
     393                 :             :                                              NULL);
     394                 :             : }
     395                 :             : 
     396                 :             : static char *
     397                 :          26 : extension_agent_request_extension (GDBusConnection  *connection,
     398                 :             :                                    uid_t             subject_uid,
     399                 :             :                                    const char       *record_type,
     400                 :             :                                    const char       *identifier,
     401                 :             :                                    uint64_t          duration_secs,
     402                 :             :                                    gboolean          allow_user_interaction,
     403                 :             :                                    GError          **error)
     404                 :             : {
     405                 :          26 :   g_autoptr(GAsyncResult) result = NULL;
     406                 :          26 :   g_autoptr(GVariant) reply = NULL;
     407                 :          26 :   g_autofree char *request_object_path = NULL;
     408                 :             : 
     409         [ +  + ]:          26 :   g_dbus_connection_call (connection,
     410                 :             :                           "org.freedesktop.MalcontentTimer1.ExtensionAgent",
     411                 :             :                           "/org/freedesktop/MalcontentTimer1/ExtensionAgent",
     412                 :             :                           "org.freedesktop.MalcontentTimer1.ExtensionAgent",
     413                 :             :                           "RequestExtension",
     414                 :             :                           g_variant_new ("(sst(s@a{sv})@a{sv})",
     415                 :             :                                          record_type,
     416                 :             :                                          identifier,
     417                 :             :                                          duration_secs,
     418                 :             :                                          "unix-process",
     419                 :             :                                          g_variant_new_parsed ("@a{sv} { 'pidfd': <@h 0>, 'uid': <@i %i> }", subject_uid),
     420                 :             :                                          g_variant_new_parsed ("@a{sv} {}")),
     421                 :             :                           G_VARIANT_TYPE ("(o)"),
     422                 :             :                           allow_user_interaction ? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION : G_DBUS_CALL_FLAGS_NONE,
     423                 :             :                           G_MAXINT,  /* timeout (ms) */
     424                 :             :                           NULL,
     425                 :             :                           async_result_cb,
     426                 :             :                           &result);
     427         [ +  + ]:         121 :   while (result == NULL)
     428                 :          95 :     g_main_context_iteration (NULL, TRUE);
     429                 :          26 :   reply = g_dbus_connection_call_finish (connection, result, error);
     430                 :             : 
     431         [ -  + ]:          26 :   if (reply == NULL)
     432                 :           0 :     return NULL;
     433                 :             : 
     434                 :          26 :   g_variant_get (reply, "(o)", &request_object_path);
     435                 :          26 :   g_assert_true (g_str_has_prefix (request_object_path, "/org/freedesktop/MalcontentTimer1/ExtensionAgent/Request"));
     436                 :             : 
     437                 :          26 :   return g_steal_pointer (&request_object_path);
     438                 :             : }
     439                 :             : 
     440                 :             : static gboolean
     441                 :          27 : extension_agent_request_close (GDBusConnection  *connection,
     442                 :             :                                const char       *request_object_path,
     443                 :             :                                GError          **error)
     444                 :             : {
     445                 :          27 :   g_autoptr(GAsyncResult) result = NULL;
     446                 :          27 :   g_autoptr(GVariant) reply = NULL;
     447                 :             : 
     448                 :          27 :   g_dbus_connection_call (connection,
     449                 :             :                           "org.freedesktop.MalcontentTimer1.ExtensionAgent",
     450                 :             :                           request_object_path,
     451                 :             :                           "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request",
     452                 :             :                           "Close",
     453                 :             :                           NULL,
     454                 :             :                           G_VARIANT_TYPE_UNIT,
     455                 :             :                           G_DBUS_CALL_FLAGS_NONE,
     456                 :             :                           G_MAXINT,  /* timeout (ms) */
     457                 :             :                           NULL,
     458                 :             :                           async_result_cb,
     459                 :             :                           &result);
     460         [ +  + ]:         123 :   while (result == NULL)
     461                 :          96 :     g_main_context_iteration (NULL, TRUE);
     462                 :          27 :   reply = g_dbus_connection_call_finish (connection, result, error);
     463                 :             : 
     464         [ +  + ]:          27 :   if (reply != NULL)
     465                 :             :     {
     466         [ -  + ]:          26 :       assert_cmpvariant_parsed (reply, "()");
     467                 :          26 :       return TRUE;
     468                 :             :     }
     469                 :             :   else
     470                 :             :     {
     471                 :           1 :       return FALSE;
     472                 :             :     }
     473                 :             : }
     474                 :             : 
     475                 :             : /* Test that making a time limit extension request works on the success path.
     476                 :             :  *
     477                 :             :  * The mock D-Bus replies are generated in polkit_server_cb().
     478                 :             :  *
     479                 :             :  * @test_data contains a `SuccessfulRequestData` with the request arguments and
     480                 :             :  * the expected results. */
     481                 :             : typedef struct
     482                 :             : {
     483                 :             :   const uid_t subject_uid;
     484                 :             :   const char *record_type;
     485                 :             :   const char *identifier;
     486                 :             :   uint64_t duration_secs;
     487                 :             :   const char *expected_polkit_message;
     488                 :             :   const char *expected_duration_str;
     489                 :             :   const char *expected_app_name;
     490                 :             : } SuccessfulRequestData;
     491                 :             : 
     492                 :             : static void
     493                 :          14 : test_extension_agent_polkit_request_successful (BusFixture *fixture,
     494                 :             :                                                 const void *test_data)
     495                 :             : {
     496                 :          14 :   g_autoptr(GError) local_error = NULL;
     497                 :          14 :   unsigned int signal_id = 0;
     498                 :          14 :   g_autoptr(GVariant) response_variant = NULL;
     499                 :          28 :   g_autofree char *request_object_path = NULL;
     500                 :          14 :   const SuccessfulRequestData *request_data = test_data;
     501                 :          14 :   const PolkitData polkit_data =
     502                 :             :     {
     503                 :          14 :       .expected_subject_uid = request_data->subject_uid,
     504                 :          14 :       .expected_polkit_message = request_data->expected_polkit_message,
     505                 :          14 :       .expected_duration_str = request_data->expected_duration_str,
     506                 :          14 :       .expected_app_name = request_data->expected_app_name,
     507                 :             :       .expected_allow_user_interaction = TRUE,
     508                 :             :       .result.is_authorized = TRUE,
     509                 :             :       .result.is_challenge = FALSE,
     510                 :             :       .result.details = "@a{ss} {}",
     511                 :             :     };
     512                 :             : 
     513                 :          14 :   gt_dbus_queue_set_server_func (fixture->queue, polkit_server_cb,
     514                 :             :                                  (void *) &polkit_data);
     515                 :             : 
     516                 :             :   /* Call the RequestExtension method on the agent. We need to use a separate
     517                 :             :    * D-Bus connection so we call from a different unique name from the one
     518                 :             :    * which registered the Agent object.
     519                 :             :    *
     520                 :             :    * Connect to signals from requests first, to avoid race conditions. */
     521                 :          14 :   signal_id = extension_agent_subscribe_responses (fixture->timerd_connection,
     522                 :             :                                                    &response_variant);
     523                 :             : 
     524                 :          28 :   request_object_path = extension_agent_request_extension (fixture->timerd_connection,
     525                 :          14 :                                                            request_data->subject_uid,
     526                 :          14 :                                                            request_data->record_type,
     527                 :          14 :                                                            request_data->identifier,
     528                 :          14 :                                                            request_data->duration_secs,
     529                 :             :                                                            TRUE,
     530                 :             :                                                            &local_error);
     531                 :          14 :   g_assert_no_error (local_error);
     532                 :             : 
     533                 :             :   /* Wait for the Response signal. @response_variant is set in request_response_cb(). */
     534         [ +  + ]:          53 :   while (response_variant == NULL)
     535                 :          39 :     g_main_context_iteration (NULL, TRUE);
     536                 :             : 
     537         [ -  + ]:          14 :   assert_cmpvariant_parsed (response_variant, "(true, @a{sv} {})");
     538                 :             : 
     539                 :             :   /* The agent should be marked as busy until the request object is closed. */
     540                 :          14 :   g_assert_true (mct_extension_agent_object_get_busy (MCT_EXTENSION_AGENT_OBJECT (fixture->extension_agent)));
     541                 :          14 :   g_assert_true (dbus_object_exists (fixture->timerd_connection, "org.freedesktop.MalcontentTimer1.ExtensionAgent", request_object_path));
     542                 :             : 
     543                 :             :   /* Call Close() on the request. */
     544                 :          14 :   extension_agent_request_close (fixture->timerd_connection, request_object_path, &local_error);
     545                 :          14 :   g_assert_no_error (local_error);
     546                 :             : 
     547                 :             :   /* The agent should no longer be busy, and the Request object should no longer exist.
     548                 :             :    * Check that by querying its properties. */
     549                 :          14 :   g_assert_false (mct_extension_agent_object_get_busy (MCT_EXTENSION_AGENT_OBJECT (fixture->extension_agent)));
     550                 :          14 :   g_assert_false (dbus_object_exists (fixture->timerd_connection, "org.freedesktop.MalcontentTimer1.ExtensionAgent", request_object_path));
     551                 :             : 
     552                 :             :   /* Clean up. */
     553                 :          14 :   g_dbus_connection_signal_unsubscribe (fixture->timerd_connection, signal_id);
     554                 :          14 :   signal_id = 0;
     555                 :          14 : }
     556                 :             : 
     557                 :             : /* Test that making a time limit extension request fails gracefully if polkit
     558                 :             :  * returns an error (which is different from returning that permission was
     559                 :             :  * denied).
     560                 :             :  *
     561                 :             :  * The mock D-Bus replies are generated in polkit_server_cb().
     562                 :             :  *
     563                 :             :  * @test_data contains an `PolkitErrorPair` with the polkit error and the
     564                 :             :  * corresponding expected error from the extension agent. */
     565                 :             : typedef struct
     566                 :             : {
     567                 :             :   const char *polkit_error_name;
     568                 :             :   const char *expected_mct_error_name;
     569                 :             : } PolkitErrorPair;
     570                 :             : 
     571                 :             : static void
     572                 :           5 : test_extension_agent_polkit_request_error_polkit (BusFixture *fixture,
     573                 :             :                                                   const void *test_data)
     574                 :             : {
     575                 :           5 :   g_autoptr(GError) local_error = NULL;
     576                 :           5 :   unsigned int signal_id = 0;
     577                 :           5 :   g_autoptr(GVariant) response_variant = NULL;
     578                 :          10 :   g_autofree char *request_object_path = NULL;
     579                 :           5 :   const uid_t subject_uid = getuid ();
     580                 :           5 :   const PolkitErrorPair *error_data = test_data;
     581                 :           5 :   const PolkitData polkit_data =
     582                 :             :     {
     583                 :             :       .expected_subject_uid = subject_uid,
     584                 :             :       .expected_polkit_message = "$(child_user_display_name) has requested a device screen time limit extension of $(duration_str) (until $(time_str))",
     585                 :             :       .expected_duration_str = "1 hour",
     586                 :             :       .expected_allow_user_interaction = TRUE,
     587                 :           5 :       .result.error_name = error_data->polkit_error_name,
     588                 :             :       .result.error_message = "Polkit gave some error",
     589                 :             :     };
     590                 :             : 
     591                 :           5 :   gt_dbus_queue_set_server_func (fixture->queue, polkit_server_cb,
     592                 :             :                                  (void *) &polkit_data);
     593                 :             : 
     594                 :             :   /* Call the RequestExtension method on the agent. We need to use a separate
     595                 :             :    * D-Bus connection so we call from a different unique name from the one
     596                 :             :    * which registered the Agent object.
     597                 :             :    *
     598                 :             :    * Connect to signals from requests first, to avoid race conditions. */
     599                 :           5 :   signal_id = extension_agent_subscribe_responses (fixture->timerd_connection,
     600                 :             :                                                    &response_variant);
     601                 :             : 
     602                 :           5 :   request_object_path = extension_agent_request_extension (fixture->timerd_connection,
     603                 :             :                                                            subject_uid,
     604                 :             :                                                            "login-session",
     605                 :             :                                                            "",
     606                 :             :                                                            3600,
     607                 :             :                                                            TRUE,
     608                 :             :                                                            &local_error);
     609                 :           5 :   g_assert_no_error (local_error);
     610                 :             : 
     611                 :             :   /* Wait for the Response signal. @response_variant is set in request_response_cb(). */
     612         [ +  + ]:          30 :   while (response_variant == NULL)
     613                 :          25 :     g_main_context_iteration (NULL, TRUE);
     614                 :             : 
     615         [ -  + ]:           5 :   assert_cmpvariant_parsed (response_variant, "(false, {'error-name': <%s>})", error_data->expected_mct_error_name);
     616                 :             : 
     617                 :             :   /* The agent should be marked as busy until the request object is closed. */
     618                 :           5 :   g_assert_true (mct_extension_agent_object_get_busy (MCT_EXTENSION_AGENT_OBJECT (fixture->extension_agent)));
     619                 :           5 :   g_assert_true (dbus_object_exists (fixture->timerd_connection, "org.freedesktop.MalcontentTimer1.ExtensionAgent", request_object_path));
     620                 :             : 
     621                 :             :   /* Call Close() on the request. */
     622                 :           5 :   extension_agent_request_close (fixture->timerd_connection, request_object_path, &local_error);
     623                 :           5 :   g_assert_no_error (local_error);
     624                 :             : 
     625                 :             :   /* The agent should no longer be busy, and the Request object should no longer exist.
     626                 :             :    * Check that by querying its properties. */
     627                 :           5 :   g_assert_false (mct_extension_agent_object_get_busy (MCT_EXTENSION_AGENT_OBJECT (fixture->extension_agent)));
     628                 :           5 :   g_assert_false (dbus_object_exists (fixture->timerd_connection, "org.freedesktop.MalcontentTimer1.ExtensionAgent", request_object_path));
     629                 :             : 
     630                 :             :   /* Clean up. */
     631                 :           5 :   g_dbus_connection_signal_unsubscribe (fixture->timerd_connection, signal_id);
     632                 :           5 :   signal_id = 0;
     633                 :           5 : }
     634                 :             : 
     635                 :             : /* Test that making a time limit extension request fails gracefully if the UID
     636                 :             :  * of the subject (the child user) doesn’t actually exist.
     637                 :             :  *
     638                 :             :  * No mock D-Bus replies are needed as the polkit daemon is never queried. */
     639                 :             : static void
     640                 :           1 : test_extension_agent_polkit_request_error_user_non_existent (BusFixture *fixture,
     641                 :             :                                                              const void *test_data G_GNUC_UNUSED)
     642                 :             : {
     643         [ +  - ]:           1 :   g_autoptr(GError) local_error = NULL;
     644                 :           1 :   unsigned int signal_id = 0;
     645         [ +  - ]:           1 :   g_autoptr(GVariant) response_variant = NULL;
     646         [ +  - ]:           1 :   g_autofree char *request_object_path = NULL;
     647                 :           1 :   const uid_t subject_uid = 123456;  /* arbitrarily chosen to hopefully not exist */
     648                 :             :   char buffer[4096];
     649                 :             :   struct passwd pwbuf;
     650                 :             :   struct passwd *result;
     651                 :             :   int pwuid_errno;
     652                 :             : 
     653                 :             :   /* Check that @subject_uid doesn’t actually exist on this system. */
     654                 :           1 :   pwuid_errno = getpwuid_r (subject_uid, &pwbuf, buffer, sizeof (buffer), &result);
     655   [ +  -  -  + ]:           1 :   if (pwuid_errno != 0 || result != NULL)
     656                 :             :     {
     657                 :           0 :       g_autofree char *message = g_strdup_printf ("User %u actually exists", subject_uid);
     658                 :           0 :       g_test_skip (message);
     659                 :           0 :       return;
     660                 :             :     }
     661                 :             : 
     662                 :             :   /* Call the RequestExtension method on the agent. We need to use a separate
     663                 :             :    * D-Bus connection so we call from a different unique name from the one
     664                 :             :    * which registered the Agent object.
     665                 :             :    *
     666                 :             :    * Connect to signals from requests first, to avoid race conditions. */
     667                 :           1 :   signal_id = extension_agent_subscribe_responses (fixture->timerd_connection,
     668                 :             :                                                    &response_variant);
     669                 :             : 
     670                 :           1 :   request_object_path = extension_agent_request_extension (fixture->timerd_connection,
     671                 :             :                                                            subject_uid,
     672                 :             :                                                            "login-session",
     673                 :             :                                                            "",
     674                 :             :                                                            3600,
     675                 :             :                                                            TRUE,
     676                 :             :                                                            &local_error);
     677                 :           1 :   g_assert_no_error (local_error);
     678                 :             : 
     679                 :             :   /* Wait for the Response signal. @response_variant is set in request_response_cb(). */
     680         [ +  + ]:           3 :   while (response_variant == NULL)
     681                 :           2 :     g_main_context_iteration (NULL, TRUE);
     682                 :             : 
     683         [ -  + ]:           1 :   assert_cmpvariant_parsed (response_variant, "(false, {'error-name': <'org.freedesktop.MalcontentTimer1.ExtensionAgent.Error.IdentifyingUser'>})");
     684                 :             : 
     685                 :             :   /* The agent should be marked as busy until the request object is closed. */
     686                 :           1 :   g_assert_true (mct_extension_agent_object_get_busy (MCT_EXTENSION_AGENT_OBJECT (fixture->extension_agent)));
     687                 :           1 :   g_assert_true (dbus_object_exists (fixture->timerd_connection, "org.freedesktop.MalcontentTimer1.ExtensionAgent", request_object_path));
     688                 :             : 
     689                 :             :   /* Call Close() on the request. */
     690                 :           1 :   extension_agent_request_close (fixture->timerd_connection, request_object_path, &local_error);
     691                 :           1 :   g_assert_no_error (local_error);
     692                 :             : 
     693                 :             :   /* The agent should no longer be busy, and the Request object should no longer exist.
     694                 :             :    * Check that by querying its properties. */
     695                 :           1 :   g_assert_false (mct_extension_agent_object_get_busy (MCT_EXTENSION_AGENT_OBJECT (fixture->extension_agent)));
     696                 :           1 :   g_assert_false (dbus_object_exists (fixture->timerd_connection, "org.freedesktop.MalcontentTimer1.ExtensionAgent", request_object_path));
     697                 :             : 
     698                 :             :   /* Clean up. */
     699                 :           1 :   g_dbus_connection_signal_unsubscribe (fixture->timerd_connection, signal_id);
     700                 :           1 :   signal_id = 0;
     701                 :             : }
     702                 :             : 
     703                 :             : /* Test the validation of arguments to RequestExtension().
     704                 :             :  *
     705                 :             :  * No mock D-Bus replies are needed as the polkit daemon is never queried.
     706                 :             :  *
     707                 :             :  * @test_data contains a `ValidationData` with the valid/invalid arguments to
     708                 :             :  * use. The same error is always expected as a result. */
     709                 :             : typedef struct
     710                 :             : {
     711                 :             :   const char *record_type;
     712                 :             :   const char *identifier;
     713                 :             :   const char *subject_kind;
     714                 :             :   const char *subject_details;
     715                 :             : } ValidationData;
     716                 :             : 
     717                 :             : static void
     718                 :           9 : test_extension_agent_polkit_request_error_validation (BusFixture *fixture,
     719                 :             :                                                       const void *test_data)
     720                 :             : {
     721                 :           9 :   g_autoptr(GError) local_error = NULL;
     722                 :           9 :   g_autoptr(GAsyncResult) result = NULL;
     723                 :           9 :   g_autoptr(GVariant) reply = NULL;
     724                 :           9 :   const ValidationData *validation_data = test_data;
     725                 :             : 
     726                 :          18 :   g_dbus_connection_call (fixture->timerd_connection,
     727                 :             :                           "org.freedesktop.MalcontentTimer1.ExtensionAgent",
     728                 :             :                           "/org/freedesktop/MalcontentTimer1/ExtensionAgent",
     729                 :             :                           "org.freedesktop.MalcontentTimer1.ExtensionAgent",
     730                 :             :                           "RequestExtension",
     731                 :             :                           g_variant_new ("(sst(s@a{sv})@a{sv})",
     732                 :           9 :                                          validation_data->record_type,
     733                 :           9 :                                          validation_data->identifier,
     734                 :             :                                          3600,
     735                 :           9 :                                          validation_data->subject_kind,
     736                 :           9 :                                          g_variant_new_parsed (validation_data->subject_details),
     737                 :             :                                          g_variant_new_parsed ("@a{sv} {}")),
     738                 :             :                           G_VARIANT_TYPE ("(o)"),
     739                 :             :                           G_DBUS_CALL_FLAGS_NONE,
     740                 :             :                           G_MAXINT,  /* timeout (ms) */
     741                 :             :                           NULL,
     742                 :             :                           async_result_cb,
     743                 :             :                           &result);
     744         [ +  + ]:          45 :   while (result == NULL)
     745                 :          36 :     g_main_context_iteration (NULL, TRUE);
     746                 :           9 :   reply = g_dbus_connection_call_finish (fixture->timerd_connection, result, &local_error);
     747                 :             : 
     748                 :           9 :   g_assert_error (local_error, MCT_EXTENSION_AGENT_OBJECT_ERROR, MCT_EXTENSION_AGENT_OBJECT_ERROR_INVALID_QUERY);
     749                 :           9 :   g_assert_null (reply);
     750                 :           9 : }
     751                 :             : 
     752                 :             : static GVariant *
     753                 :           9 : call_dbus_property_get (GDBusConnection  *connection,
     754                 :             :                         const char       *bus_name,
     755                 :             :                         const char       *object_path,
     756                 :             :                         const char       *interface_name,
     757                 :             :                         const char       *property_name,
     758                 :             :                         GError          **error)
     759                 :             : {
     760                 :           9 :   g_autoptr(GAsyncResult) result = NULL;
     761                 :           9 :   g_autoptr(GVariant) reply = NULL;
     762                 :             : 
     763                 :           9 :   g_dbus_connection_call (connection,
     764                 :             :                           bus_name,
     765                 :             :                           object_path,
     766                 :             :                           "org.freedesktop.DBus.Properties",
     767                 :             :                           "Get",
     768                 :             :                           g_variant_new ("(ss)",
     769                 :             :                                          interface_name,
     770                 :             :                                          property_name),
     771                 :             :                           G_VARIANT_TYPE ("(v)"),
     772                 :             :                           G_DBUS_CALL_FLAGS_NONE,
     773                 :             :                           G_MAXINT,  /* timeout (ms) */
     774                 :             :                           NULL,
     775                 :             :                           async_result_cb,
     776                 :             :                           &result);
     777         [ +  + ]:          44 :   while (result == NULL)
     778                 :          35 :     g_main_context_iteration (NULL, TRUE);
     779                 :           9 :   reply = g_dbus_connection_call_finish (connection, result, error);
     780                 :             : 
     781         [ +  - ]:           9 :   if (reply == NULL)
     782                 :           9 :     return NULL;
     783                 :             : 
     784                 :           0 :   return g_variant_get_child_value (reply, 0);
     785                 :             : }
     786                 :             : 
     787                 :             : static gboolean
     788                 :           9 : call_dbus_property_set (GDBusConnection  *connection,
     789                 :             :                         const char       *bus_name,
     790                 :             :                         const char       *object_path,
     791                 :             :                         const char       *interface_name,
     792                 :             :                         const char       *property_name,
     793                 :             :                         GVariant         *property_value,
     794                 :             :                         GError          **error)
     795                 :             : {
     796                 :          18 :   g_autoptr(GVariant) owned_property_value = g_variant_ref_sink (property_value);
     797                 :           9 :   g_autoptr(GAsyncResult) result = NULL;
     798                 :          18 :   g_autoptr(GVariant) reply = NULL;
     799                 :             : 
     800                 :           9 :   g_dbus_connection_call (connection,
     801                 :             :                           bus_name,
     802                 :             :                           object_path,
     803                 :             :                           "org.freedesktop.DBus.Properties",
     804                 :             :                           "Set",
     805                 :             :                           g_variant_new ("(ssv)",
     806                 :             :                                          interface_name,
     807                 :             :                                          property_name,
     808                 :             :                                          owned_property_value),
     809                 :             :                           NULL,
     810                 :             :                           G_DBUS_CALL_FLAGS_NONE,
     811                 :             :                           G_MAXINT,  /* timeout (ms) */
     812                 :             :                           NULL,
     813                 :             :                           async_result_cb,
     814                 :             :                           &result);
     815         [ +  + ]:          44 :   while (result == NULL)
     816                 :          35 :     g_main_context_iteration (NULL, TRUE);
     817                 :           9 :   reply = g_dbus_connection_call_finish (connection, result, error);
     818                 :             : 
     819                 :           9 :   return (reply != NULL);
     820                 :             : }
     821                 :             : 
     822                 :             : static GVariant *
     823                 :           8 : call_dbus_property_get_all (GDBusConnection  *connection,
     824                 :             :                             const char       *bus_name,
     825                 :             :                             const char       *object_path,
     826                 :             :                             const char       *interface_name,
     827                 :             :                             GError          **error)
     828                 :             : {
     829                 :           8 :   g_autoptr(GAsyncResult) result = NULL;
     830                 :           8 :   g_autoptr(GVariant) reply = NULL;
     831                 :             : 
     832                 :           8 :   g_dbus_connection_call (connection,
     833                 :             :                           bus_name,
     834                 :             :                           object_path,
     835                 :             :                           "org.freedesktop.DBus.Properties",
     836                 :             :                           "GetAll",
     837                 :             :                           g_variant_new ("(s)", interface_name),
     838                 :             :                           NULL,
     839                 :             :                           G_DBUS_CALL_FLAGS_NONE,
     840                 :             :                           G_MAXINT,  /* timeout (ms) */
     841                 :             :                           NULL,
     842                 :             :                           async_result_cb,
     843                 :             :                           &result);
     844         [ +  + ]:          39 :   while (result == NULL)
     845                 :          31 :     g_main_context_iteration (NULL, TRUE);
     846                 :           8 :   reply = g_dbus_connection_call_finish (connection, result, error);
     847                 :             : 
     848         [ +  + ]:           8 :   if (reply == NULL)
     849                 :           6 :     return NULL;
     850                 :             : 
     851                 :           2 :   return g_variant_get_child_value (reply, 0);
     852                 :             : }
     853                 :             : 
     854                 :             : /* Test that calling methods on `org.freedesktop.DBus.Properties` works as
     855                 :             :  * expected for the agent object. Currently it exposes no properties, so the
     856                 :             :  * replies should basically all be errors.
     857                 :             :  *
     858                 :             :  * The object is exposed directly by the agent, so no mock D-Bus replies are
     859                 :             :  * needed. */
     860                 :             : static void
     861                 :           1 : test_extension_agent_polkit_properties (BusFixture *fixture,
     862                 :             :                                         const void *test_data G_GNUC_UNUSED)
     863                 :             : {
     864                 :             :   const struct
     865                 :             :     {
     866                 :             :       const char *interface_name;
     867                 :             :       const char *property_name;
     868                 :             :       GDBusError expected_error_code;
     869                 :             :     }
     870                 :           1 :   get_vectors[] =
     871                 :             :     {
     872                 :             :       {
     873                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent",
     874                 :             :         "PropertyDoesntExist",
     875                 :             :         G_DBUS_ERROR_UNKNOWN_PROPERTY
     876                 :             :       },
     877                 :             :       {
     878                 :             :         "org.freedesktop.NonExistentInterface",
     879                 :             :         "PropertyDoesntExist",
     880                 :             :         G_DBUS_ERROR_UNKNOWN_PROPERTY
     881                 :             :       },
     882                 :             :       {
     883                 :             :         "123invalidinterface",
     884                 :             :         "PropertyDoesntExist",
     885                 :             :         G_DBUS_ERROR_UNKNOWN_INTERFACE
     886                 :             :       },
     887                 :             :       {
     888                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent",
     889                 :             :         "",
     890                 :             :         G_DBUS_ERROR_UNKNOWN_PROPERTY
     891                 :             :       },
     892                 :             :     };
     893                 :             : 
     894         [ +  + ]:           5 :   for (size_t i = 0; i < G_N_ELEMENTS (get_vectors); i++)
     895                 :             :     {
     896                 :           4 :       g_autoptr(GVariant) value = NULL;
     897                 :           4 :       g_autoptr(GError) local_error = NULL;
     898                 :             : 
     899                 :           8 :       value = call_dbus_property_get (fixture->timerd_connection,
     900                 :             :                                       "org.freedesktop.MalcontentTimer1.ExtensionAgent",
     901                 :             :                                       "/org/freedesktop/MalcontentTimer1/ExtensionAgent",
     902                 :           4 :                                       get_vectors[i].interface_name,
     903                 :           4 :                                       get_vectors[i].property_name,
     904                 :             :                                       &local_error);
     905                 :           4 :       g_assert_error (local_error, G_DBUS_ERROR, (int) get_vectors[i].expected_error_code);
     906                 :           4 :       g_assert_null (value);
     907                 :             :     }
     908                 :             : 
     909                 :             :   const struct
     910                 :             :     {
     911                 :             :       const char *interface_name;
     912                 :             :       const char *property_name;
     913                 :             :       GDBusError expected_error_code;
     914                 :             :     }
     915                 :           1 :   set_vectors[] =
     916                 :             :     {
     917                 :             :       {
     918                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent",
     919                 :             :         "PropertyDoesntExist",
     920                 :             :         G_DBUS_ERROR_UNKNOWN_PROPERTY
     921                 :             :       },
     922                 :             :       {
     923                 :             :         "org.freedesktop.NonExistentInterface",
     924                 :             :         "PropertyDoesntExist",
     925                 :             :         G_DBUS_ERROR_UNKNOWN_PROPERTY
     926                 :             :       },
     927                 :             :       {
     928                 :             :         "123invalidinterface",
     929                 :             :         "PropertyDoesntExist",
     930                 :             :         G_DBUS_ERROR_UNKNOWN_INTERFACE
     931                 :             :       },
     932                 :             :       {
     933                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent",
     934                 :             :         "",
     935                 :             :         G_DBUS_ERROR_UNKNOWN_PROPERTY
     936                 :             :       },
     937                 :             :     };
     938                 :             : 
     939         [ +  + ]:           5 :   for (size_t i = 0; i < G_N_ELEMENTS (set_vectors); i++)
     940                 :             :     {
     941                 :             :       gboolean success;
     942                 :           4 :       g_autoptr(GError) local_error = NULL;
     943                 :             : 
     944                 :           4 :       success = call_dbus_property_set (fixture->timerd_connection,
     945                 :             :                                         "org.freedesktop.MalcontentTimer1.ExtensionAgent",
     946                 :             :                                         "/org/freedesktop/MalcontentTimer1/ExtensionAgent",
     947                 :           4 :                                         set_vectors[i].interface_name,
     948                 :           4 :                                         set_vectors[i].property_name,
     949                 :             :                                         g_variant_new_parsed ("<'nope'>"),
     950                 :             :                                         &local_error);
     951                 :           4 :       g_assert_error (local_error, G_DBUS_ERROR, (int) set_vectors[i].expected_error_code);
     952                 :           4 :       g_assert_false (success);
     953                 :             :     }
     954                 :             : 
     955                 :             :   const struct
     956                 :             :     {
     957                 :             :       const char *interface_name;
     958                 :             :       GDBusError expected_error_code;
     959                 :             :       const char *expected_values;  /* (nullable) */
     960                 :             :     }
     961                 :           1 :   get_all_vectors[] =
     962                 :             :     {
     963                 :             :       {
     964                 :             :         "org.freedesktop.NonExistentInterface",
     965                 :             :         G_DBUS_ERROR_UNKNOWN_INTERFACE,
     966                 :             :         NULL
     967                 :             :       },
     968                 :             :       {
     969                 :             :         "123invalidinterface",
     970                 :             :         G_DBUS_ERROR_UNKNOWN_INTERFACE,
     971                 :             :         NULL
     972                 :             :       },
     973                 :             :       {
     974                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent",
     975                 :             :         0,
     976                 :             :         "@a{sv} {}"
     977                 :             :       },
     978                 :             :     };
     979                 :             : 
     980         [ +  + ]:           4 :   for (size_t i = 0; i < G_N_ELEMENTS (get_all_vectors); i++)
     981                 :             :     {
     982                 :           3 :       g_autoptr(GVariant) values = NULL;
     983                 :           3 :       g_autoptr(GError) local_error = NULL;
     984                 :             : 
     985                 :           6 :       values = call_dbus_property_get_all (fixture->timerd_connection,
     986                 :             :                                            "org.freedesktop.MalcontentTimer1.ExtensionAgent",
     987                 :             :                                            "/org/freedesktop/MalcontentTimer1/ExtensionAgent",
     988                 :           3 :                                            get_all_vectors[i].interface_name,
     989                 :             :                                            &local_error);
     990                 :             : 
     991         [ +  + ]:           3 :       if (get_all_vectors[i].expected_error_code != 0)
     992                 :             :         {
     993                 :           2 :           g_assert_error (local_error, G_DBUS_ERROR, (int) get_all_vectors[i].expected_error_code);
     994                 :           2 :           g_assert_null (values);
     995                 :             :         }
     996                 :             :       else
     997                 :             :         {
     998                 :           1 :           g_assert_no_error (local_error);
     999         [ -  + ]:           1 :           assert_cmpvariant_parsed (values, get_all_vectors[i].expected_values);
    1000                 :             :         }
    1001                 :             :     }
    1002                 :           1 : }
    1003                 :             : 
    1004                 :             : /* Test that calling methods on `org.freedesktop.DBus.Properties` works as
    1005                 :             :  * expected for a request object. Currently it exposes no properties, so the
    1006                 :             :  * replies should basically all be errors.
    1007                 :             :  *
    1008                 :             :  * The mock D-Bus replies are generated in polkit_server_cb(). */
    1009                 :             : static void
    1010                 :           1 : test_extension_agent_polkit_request_properties (BusFixture *fixture,
    1011                 :             :                                                 const void *test_data G_GNUC_UNUSED)
    1012                 :             : {
    1013                 :           1 :   g_autoptr(GError) outer_error = NULL;
    1014                 :           1 :   g_autofree char *request_object_path = NULL;
    1015                 :           1 :   g_autofree char *nonexistent_request_object_path = NULL;
    1016                 :           2 :   const PolkitData polkit_data =
    1017                 :             :     {
    1018                 :           1 :       .expected_subject_uid = getuid (),
    1019                 :             :       .expected_polkit_message = "$(child_user_display_name) has requested a device screen time limit extension of $(duration_str) (until $(time_str))",
    1020                 :             :       .expected_duration_str = "1 hour",
    1021                 :             :       .expected_allow_user_interaction = FALSE,
    1022                 :             :       .result.is_authorized = TRUE,
    1023                 :             :       .result.is_challenge = FALSE,
    1024                 :             :       .result.details = "@a{ss} {}",
    1025                 :             :     };
    1026                 :             : 
    1027                 :           1 :   gt_dbus_queue_set_server_func (fixture->queue, polkit_server_cb,
    1028                 :             :                                  (void *) &polkit_data);
    1029                 :             : 
    1030                 :             :   /* Call the RequestExtension method on the agent to get a Request object to
    1031                 :             :    * test the properties of. We don’t care about the response, so don’t bother
    1032                 :             :    * connecting the signal for that. */
    1033                 :           1 :   request_object_path = extension_agent_request_extension (fixture->timerd_connection,
    1034                 :             :                                                            getuid (),
    1035                 :             :                                                            "login-session",
    1036                 :             :                                                            "",
    1037                 :             :                                                            3600,
    1038                 :             :                                                            FALSE,
    1039                 :             :                                                            &outer_error);
    1040                 :           1 :   g_assert_no_error (outer_error);
    1041                 :             : 
    1042                 :           1 :   nonexistent_request_object_path = g_strconcat (request_object_path, "0", NULL);
    1043                 :           1 :   g_assert_false (dbus_object_exists (fixture->timerd_connection, "org.freedesktop.MalcontentTimer1.ExtensionAgent", nonexistent_request_object_path));
    1044                 :             : 
    1045                 :             :   /* Now test the D-Bus properties of the request object */
    1046                 :             :   const struct
    1047                 :             :     {
    1048                 :             :       const char *object_path;
    1049                 :             :       const char *interface_name;
    1050                 :             :       const char *property_name;
    1051                 :             :       GDBusError expected_error_code;
    1052                 :             :     }
    1053                 :           1 :   get_vectors[] =
    1054                 :             :     {
    1055                 :             :       {
    1056                 :             :         request_object_path,
    1057                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request",
    1058                 :             :         "PropertyDoesntExist",
    1059                 :             :         G_DBUS_ERROR_UNKNOWN_PROPERTY
    1060                 :             :       },
    1061                 :             :       {
    1062                 :             :         request_object_path,
    1063                 :             :         "org.freedesktop.NonExistentInterface",
    1064                 :             :         "PropertyDoesntExist",
    1065                 :             :         G_DBUS_ERROR_UNKNOWN_PROPERTY
    1066                 :             :       },
    1067                 :             :       {
    1068                 :             :         request_object_path,
    1069                 :             :         "123invalidinterface",
    1070                 :             :         "PropertyDoesntExist",
    1071                 :             :         G_DBUS_ERROR_UNKNOWN_INTERFACE
    1072                 :             :       },
    1073                 :             :       {
    1074                 :             :         request_object_path,
    1075                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request",
    1076                 :             :         "",
    1077                 :             :         G_DBUS_ERROR_UNKNOWN_PROPERTY
    1078                 :             :       },
    1079                 :             :       {
    1080                 :             :         nonexistent_request_object_path,
    1081                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request",
    1082                 :             :         "PropertyDoesntExist",
    1083                 :             :         /* FIXME: I think this should be G_DBUS_ERROR_UNKNOWN_OBJECT, but the
    1084                 :             :          * fallback from the (!handled) return from
    1085                 :             :          * handle_subtree_method_invocation() in GIO unconditionally returns
    1086                 :             :          * this error (https://gitlab.gnome.org/GNOME/glib/-/issues/3822): */
    1087                 :             :         G_DBUS_ERROR_UNKNOWN_METHOD
    1088                 :             :       },
    1089                 :             :     };
    1090                 :             : 
    1091         [ +  + ]:           6 :   for (size_t i = 0; i < G_N_ELEMENTS (get_vectors); i++)
    1092                 :             :     {
    1093                 :           5 :       g_autoptr(GVariant) value = NULL;
    1094                 :           5 :       g_autoptr(GError) local_error = NULL;
    1095                 :             : 
    1096                 :          10 :       value = call_dbus_property_get (fixture->timerd_connection,
    1097                 :             :                                       "org.freedesktop.MalcontentTimer1.ExtensionAgent",
    1098                 :           5 :                                       get_vectors[i].object_path,
    1099                 :           5 :                                       get_vectors[i].interface_name,
    1100                 :           5 :                                       get_vectors[i].property_name,
    1101                 :             :                                       &local_error);
    1102                 :           5 :       g_assert_error (local_error, G_DBUS_ERROR, (int) get_vectors[i].expected_error_code);
    1103                 :           5 :       g_assert_null (value);
    1104                 :             :     }
    1105                 :             : 
    1106                 :             :   const struct
    1107                 :             :     {
    1108                 :             :       const char *object_path;
    1109                 :             :       const char *interface_name;
    1110                 :             :       const char *property_name;
    1111                 :             :       GDBusError expected_error_code;
    1112                 :             :     }
    1113                 :           1 :   set_vectors[] =
    1114                 :             :     {
    1115                 :             :       {
    1116                 :             :         request_object_path,
    1117                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request",
    1118                 :             :         "PropertyDoesntExist",
    1119                 :             :         G_DBUS_ERROR_UNKNOWN_PROPERTY
    1120                 :             :       },
    1121                 :             :       {
    1122                 :             :         request_object_path,
    1123                 :             :         "org.freedesktop.NonExistentInterface",
    1124                 :             :         "PropertyDoesntExist",
    1125                 :             :         G_DBUS_ERROR_UNKNOWN_PROPERTY
    1126                 :             :       },
    1127                 :             :       {
    1128                 :             :         request_object_path,
    1129                 :             :         "123invalidinterface",
    1130                 :             :         "PropertyDoesntExist",
    1131                 :             :         G_DBUS_ERROR_UNKNOWN_INTERFACE
    1132                 :             :       },
    1133                 :             :       {
    1134                 :             :         request_object_path,
    1135                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request",
    1136                 :             :         "",
    1137                 :             :         G_DBUS_ERROR_UNKNOWN_PROPERTY
    1138                 :             :       },
    1139                 :             :       {
    1140                 :             :         nonexistent_request_object_path,
    1141                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request",
    1142                 :             :         "PropertyDoesntExist",
    1143                 :             :         /* FIXME: Same issue as with Get() on an unknown object: */
    1144                 :             :         G_DBUS_ERROR_UNKNOWN_METHOD
    1145                 :             :       },
    1146                 :             :     };
    1147                 :             : 
    1148         [ +  + ]:           6 :   for (size_t i = 0; i < G_N_ELEMENTS (set_vectors); i++)
    1149                 :             :     {
    1150                 :             :       gboolean success;
    1151                 :           5 :       g_autoptr(GError) local_error = NULL;
    1152                 :             : 
    1153                 :           5 :       success = call_dbus_property_set (fixture->timerd_connection,
    1154                 :             :                                         "org.freedesktop.MalcontentTimer1.ExtensionAgent",
    1155                 :           5 :                                         set_vectors[i].object_path,
    1156                 :           5 :                                         set_vectors[i].interface_name,
    1157                 :           5 :                                         set_vectors[i].property_name,
    1158                 :             :                                         g_variant_new_parsed ("<'nope'>"),
    1159                 :             :                                         &local_error);
    1160                 :           5 :       g_assert_error (local_error, G_DBUS_ERROR, (int) set_vectors[i].expected_error_code);
    1161                 :           5 :       g_assert_false (success);
    1162                 :             :     }
    1163                 :             : 
    1164                 :             :   const struct
    1165                 :             :     {
    1166                 :             :       const char *object_path;
    1167                 :             :       const char *interface_name;
    1168                 :             :       GDBusError expected_error_code;
    1169                 :             :       const char *expected_values;  /* (nullable) */
    1170                 :             :     }
    1171                 :           1 :   get_all_vectors[] =
    1172                 :             :     {
    1173                 :             :       {
    1174                 :             :         request_object_path,
    1175                 :             :         "org.freedesktop.NonExistentInterface",
    1176                 :             :         G_DBUS_ERROR_UNKNOWN_INTERFACE,
    1177                 :             :         NULL
    1178                 :             :       },
    1179                 :             :       {
    1180                 :             :         request_object_path,
    1181                 :             :         "123invalidinterface",
    1182                 :             :         G_DBUS_ERROR_UNKNOWN_INTERFACE,
    1183                 :             :         NULL
    1184                 :             :       },
    1185                 :             :       {
    1186                 :             :         request_object_path,
    1187                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request",
    1188                 :             :         0,
    1189                 :             :         "@a{sv} {}"
    1190                 :             :       },
    1191                 :             :       {
    1192                 :             :         request_object_path,
    1193                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent",
    1194                 :             :         G_DBUS_ERROR_UNKNOWN_INTERFACE,
    1195                 :             :         NULL
    1196                 :             :       },
    1197                 :             :       {
    1198                 :             :         nonexistent_request_object_path,
    1199                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request",
    1200                 :             :         /* FIXME: Same issue as with Get() on an unknown object: */
    1201                 :             :         G_DBUS_ERROR_UNKNOWN_METHOD,
    1202                 :             :         NULL
    1203                 :             :       },
    1204                 :             :     };
    1205                 :             : 
    1206         [ +  + ]:           6 :   for (size_t i = 0; i < G_N_ELEMENTS (get_all_vectors); i++)
    1207                 :             :     {
    1208                 :           5 :       g_autoptr(GVariant) values = NULL;
    1209                 :           5 :       g_autoptr(GError) local_error = NULL;
    1210                 :             : 
    1211                 :          10 :       values = call_dbus_property_get_all (fixture->timerd_connection,
    1212                 :             :                                            "org.freedesktop.MalcontentTimer1.ExtensionAgent",
    1213                 :           5 :                                            get_all_vectors[i].object_path,
    1214                 :           5 :                                            get_all_vectors[i].interface_name,
    1215                 :             :                                            &local_error);
    1216                 :             : 
    1217         [ +  + ]:           5 :       if (get_all_vectors[i].expected_error_code != 0)
    1218                 :             :         {
    1219                 :           4 :           g_assert_error (local_error, G_DBUS_ERROR, (int) get_all_vectors[i].expected_error_code);
    1220                 :           4 :           g_assert_null (values);
    1221                 :             :         }
    1222                 :             :       else
    1223                 :             :         {
    1224                 :           1 :           g_assert_no_error (local_error);
    1225         [ -  + ]:           1 :           assert_cmpvariant_parsed (values, get_all_vectors[i].expected_values);
    1226                 :             :         }
    1227                 :             :     }
    1228                 :             : 
    1229                 :             :   /* Clean up */
    1230                 :           1 :   extension_agent_request_close (fixture->timerd_connection, request_object_path, &outer_error);
    1231                 :           1 :   g_assert_no_error (outer_error);
    1232                 :           1 : }
    1233                 :             : 
    1234                 :             : /* Test that a pending extension request is cancelled automatically if the
    1235                 :             :  * client which requested it drops off the bus.
    1236                 :             :  *
    1237                 :             :  * The mock D-Bus replies are generated inline for clarity. */
    1238                 :             : static void
    1239                 :           1 : test_extension_agent_polkit_request_error_cancelled (BusFixture *fixture,
    1240                 :             :                                                      const void *test_data G_GNUC_UNUSED)
    1241                 :             : {
    1242                 :           1 :   unsigned int signal_id = 0;
    1243                 :           1 :   g_autoptr(GVariant) response_variant = NULL;
    1244                 :           1 :   g_autoptr(GAsyncResult) request_extension_result = NULL;
    1245                 :           1 :   g_autoptr(GAsyncResult) close_result = NULL;
    1246                 :           1 :   const uid_t subject_uid = getuid ();
    1247                 :           1 :   g_autoptr(GDBusMethodInvocation) check_authorization_invocation = NULL;
    1248                 :           1 :   g_autoptr(GDBusMethodInvocation) cancel_check_authorization_invocation = NULL;
    1249                 :             :   const char *expected_cancellation_id, *cancellation_id;
    1250                 :           1 :   g_autoptr(GError) local_error = NULL;
    1251                 :             : 
    1252                 :             :   /* Call the RequestExtension method on the agent. We need to use a separate
    1253                 :             :    * D-Bus connection so we call from a different unique name from the one
    1254                 :             :    * which registered the Agent object.
    1255                 :             :    *
    1256                 :             :    * Connect to signals from requests first, to avoid race conditions. */
    1257                 :           1 :   signal_id = extension_agent_subscribe_responses (fixture->timerd_connection,
    1258                 :             :                                                    &response_variant);
    1259                 :             : 
    1260                 :           1 :   g_dbus_connection_call (fixture->timerd_connection,
    1261                 :             :                           "org.freedesktop.MalcontentTimer1.ExtensionAgent",
    1262                 :             :                           "/org/freedesktop/MalcontentTimer1/ExtensionAgent",
    1263                 :             :                           "org.freedesktop.MalcontentTimer1.ExtensionAgent",
    1264                 :             :                           "RequestExtension",
    1265                 :             :                           g_variant_new ("(sst(s@a{sv})@a{sv})",
    1266                 :             :                                          "login-session",
    1267                 :             :                                          "",
    1268                 :             :                                          3600,
    1269                 :             :                                          "unix-process",
    1270                 :             :                                          g_variant_new_parsed ("@a{sv} { 'pidfd': <@h 0>, 'uid': <@i %i> }", subject_uid),
    1271                 :             :                                          g_variant_new_parsed ("@a{sv} {}")),
    1272                 :             :                           G_VARIANT_TYPE ("(o)"),
    1273                 :             :                           G_DBUS_CALL_FLAGS_NONE,
    1274                 :             :                           G_MAXINT,  /* timeout (ms) */
    1275                 :             :                           NULL,
    1276                 :             :                           async_result_cb,
    1277                 :             :                           &request_extension_result);
    1278                 :             : 
    1279                 :             :   /* Handle the CheckAuthorization() call. */
    1280                 :           1 :   check_authorization_invocation =
    1281                 :           1 :       gt_dbus_queue_assert_pop_message (fixture->queue,
    1282                 :             :                                         "/org/freedesktop/PolicyKit1/Authority",
    1283                 :             :                                         "org.freedesktop.PolicyKit1.Authority",
    1284                 :             :                                         "CheckAuthorization",
    1285                 :             :                                         "(@r&sa{ss}u&s)",
    1286                 :             :                                         NULL,
    1287                 :             :                                         NULL,
    1288                 :             :                                         NULL,
    1289                 :             :                                         NULL,
    1290                 :             :                                         &expected_cancellation_id);
    1291                 :             : 
    1292                 :           1 :   g_assert_cmpstr (expected_cancellation_id, !=, "");
    1293                 :             : 
    1294                 :             :   /* Before anything else happens, drop off the bus (as if the timerd has
    1295                 :             :    * crashed). The agent should gracefully cancel the pending authorisation
    1296                 :             :    * request. */
    1297                 :           1 :   g_dbus_connection_close (fixture->timerd_connection, NULL, async_result_cb, &close_result);
    1298         [ +  + ]:           3 :   while (close_result == NULL)
    1299                 :           2 :     g_main_context_iteration (NULL, TRUE);
    1300                 :           1 :   g_dbus_connection_close_finish (fixture->timerd_connection, close_result, &local_error);
    1301                 :           1 :   g_assert_no_error (local_error);
    1302         [ +  - ]:           1 :   g_clear_object (&fixture->timerd_connection);
    1303                 :             :   (void) signal_id;  /* can’t actually explicitly disonnect this, but it has just been disconnected */
    1304                 :           1 :   signal_id = 0;
    1305                 :             : 
    1306                 :             :   /* So we expect a cancellation call to polkit from the agent. */
    1307                 :           1 :   cancel_check_authorization_invocation =
    1308                 :           1 :       gt_dbus_queue_assert_pop_message (fixture->queue,
    1309                 :             :                                         "/org/freedesktop/PolicyKit1/Authority",
    1310                 :             :                                         "org.freedesktop.PolicyKit1.Authority",
    1311                 :             :                                         "CancelCheckAuthorization",
    1312                 :             :                                         "(&s)",
    1313                 :             :                                         &cancellation_id);
    1314                 :             : 
    1315                 :           1 :   g_assert_cmpstr (cancellation_id, ==, expected_cancellation_id);
    1316                 :             : 
    1317                 :           1 :   g_dbus_method_invocation_return_dbus_error (check_authorization_invocation,
    1318                 :             :                                               "org.freedesktop.PolicyKit1.Error.Cancelled",
    1319                 :             :                                               "Authorization was cancelled");
    1320                 :           1 :   g_dbus_method_invocation_return_value (cancel_check_authorization_invocation, NULL);
    1321                 :             : 
    1322                 :             :   /* Clear out the async result for the (now cancelled) D-Bus call. */
    1323         [ -  + ]:           1 :   while (request_extension_result == NULL)
    1324                 :           0 :     g_main_context_iteration (NULL, TRUE);
    1325         [ +  - ]:           1 :   g_clear_object (&request_extension_result);
    1326                 :             : 
    1327                 :             :   /* We shouldn’t have received a response signal. */
    1328                 :           1 :   g_assert_null (response_variant);
    1329                 :           1 : }
    1330                 :             : 
    1331                 :             : static void
    1332                 :           4 : request_response_queue_cb (GDBusConnection *connection,
    1333                 :             :                            const char      *sender_name,
    1334                 :             :                            const char      *object_path,
    1335                 :             :                            const char      *interface_name,
    1336                 :             :                            const char      *signal_name,
    1337                 :             :                            GVariant        *parameters,
    1338                 :             :                            void            *user_data)
    1339                 :             : {
    1340                 :           4 :   GQueue *queue = user_data;
    1341                 :             : 
    1342                 :             :   /* This is the extension agent’s unique name, which we don’t easily have
    1343                 :             :    * access to in this callback: */
    1344                 :           4 :   g_assert_cmpstr (sender_name, !=, "");
    1345                 :             : 
    1346                 :           4 :   g_assert_true (g_str_has_prefix (object_path, "/org/freedesktop/MalcontentTimer1/ExtensionAgent/Request"));
    1347                 :           4 :   g_assert_cmpstr (interface_name, ==, "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request");
    1348                 :           4 :   g_assert_cmpstr (signal_name, ==, "Response");
    1349                 :             : 
    1350                 :           4 :   g_queue_push_tail (queue, g_variant_ref (parameters));
    1351                 :           4 :   g_main_context_wakeup (NULL);
    1352                 :           4 : }
    1353                 :             : 
    1354                 :             : static void
    1355                 :           4 : assert_pop_request_response_queue (GQueue     *queue,
    1356                 :             :                                    const char *expected_response_str)
    1357                 :             : {
    1358                 :           4 :   g_autoptr(GVariant) response = NULL;
    1359                 :             : 
    1360                 :             :   /* Wait for the Response signal. A queue item is pushed in request_response_queue_cb(). */
    1361         [ +  + ]:           8 :   while (g_queue_is_empty (queue))
    1362                 :           4 :     g_main_context_iteration (NULL, TRUE);
    1363                 :             : 
    1364                 :           4 :   response = g_queue_pop_head (queue);
    1365                 :           4 :   g_assert_nonnull (response);
    1366         [ -  + ]:           4 :   assert_cmpvariant_parsed (response, expected_response_str);
    1367                 :           4 : }
    1368                 :             : 
    1369                 :             : /* Test that the agent can handle requests from multiple clients simultaneously,
    1370                 :             :  * some in parallel and some overlapping.
    1371                 :             :  *
    1372                 :             :  * The mock D-Bus replies are generated inline for clarity. */
    1373                 :             : static void
    1374                 :           1 : test_extension_agent_polkit_request_multiplexing (BusFixture *fixture,
    1375                 :             :                                                   const void *test_data G_GNUC_UNUSED)
    1376                 :             : {
    1377                 :           1 :   g_autoptr(GError) local_error = NULL;
    1378                 :           1 :   g_autoptr(GDBusConnection) client1_connection = NULL;
    1379                 :           1 :   g_autoptr(GDBusConnection) client2_connection = NULL;
    1380                 :           1 :   unsigned int client1_signal_id = 0, client2_signal_id = 0;
    1381                 :           1 :   g_autoptr(GQueue) client1_request_response_queue = NULL;
    1382                 :           1 :   g_autoptr(GQueue) client2_request_response_queue = NULL;
    1383                 :           1 :   g_autofree char *client1_request1_object_path = NULL;
    1384                 :           1 :   g_autofree char *client1_request2_object_path = NULL;
    1385                 :           1 :   g_autofree char *client1_request3_object_path = NULL;
    1386                 :           1 :   g_autofree char *client2_request1_object_path = NULL;
    1387                 :           1 :   g_autoptr(GDBusMethodInvocation) client1_request1_invocation = NULL;
    1388                 :           1 :   g_autoptr(GDBusMethodInvocation) client1_request2_invocation = NULL;
    1389                 :           1 :   g_autoptr(GDBusMethodInvocation) client1_request3_invocation = NULL;
    1390                 :           1 :   g_autoptr(GDBusMethodInvocation) client2_request1_invocation = NULL;
    1391                 :           2 :   g_autoptr(GVariant) details = NULL;
    1392                 :           1 :   const char *duration_str = NULL;
    1393                 :             : 
    1394                 :           1 :   client1_connection = g_object_ref (fixture->timerd_connection);
    1395                 :           1 :   client2_connection = dbus_queue_open_additional_client_connection ();
    1396                 :             : 
    1397                 :             :   /* Connect to signals from requests first, to avoid race conditions. We can’t
    1398                 :             :    * use extension_agent_subscribe_responses() here as we need a queue. */
    1399                 :           1 :   client1_request_response_queue = g_queue_new ();
    1400                 :           1 :   client2_request_response_queue = g_queue_new ();
    1401                 :             : 
    1402                 :             :   client1_signal_id =
    1403                 :           1 :       g_dbus_connection_signal_subscribe (client1_connection,
    1404                 :             :                                           "org.freedesktop.MalcontentTimer1.ExtensionAgent",
    1405                 :             :                                           "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request",
    1406                 :             :                                           "Response",
    1407                 :             :                                           NULL,  /* object path */
    1408                 :             :                                           NULL,  /* arg0 */
    1409                 :             :                                           G_DBUS_SIGNAL_FLAGS_NONE,
    1410                 :             :                                           request_response_queue_cb,
    1411                 :             :                                           client1_request_response_queue,
    1412                 :             :                                           NULL);
    1413                 :             :   client2_signal_id =
    1414                 :           1 :       g_dbus_connection_signal_subscribe (client2_connection,
    1415                 :             :                                           "org.freedesktop.MalcontentTimer1.ExtensionAgent",
    1416                 :             :                                           "org.freedesktop.MalcontentTimer1.ExtensionAgent.Request",
    1417                 :             :                                           "Response",
    1418                 :             :                                           NULL,  /* object path */
    1419                 :             :                                           NULL,  /* arg0 */
    1420                 :             :                                           G_DBUS_SIGNAL_FLAGS_NONE,
    1421                 :             :                                           request_response_queue_cb,
    1422                 :             :                                           client2_request_response_queue,
    1423                 :             :                                           NULL);
    1424                 :             : 
    1425                 :             :   /* Make a request from client 1 */
    1426                 :           1 :   client1_request1_object_path = extension_agent_request_extension (client1_connection,
    1427                 :             :                                                                     getuid (),
    1428                 :             :                                                                     "login-session",
    1429                 :             :                                                                     "",
    1430                 :             :                                                                     3600,
    1431                 :             :                                                                     FALSE,
    1432                 :             :                                                                     &local_error);
    1433                 :           1 :   g_assert_no_error (local_error);
    1434                 :             : 
    1435                 :             :   /* Handle the polkit call for client 1’s request */
    1436                 :           1 :   client1_request1_invocation =
    1437                 :           1 :       gt_dbus_queue_assert_pop_message (fixture->queue,
    1438                 :             :                                         "/org/freedesktop/PolicyKit1/Authority",
    1439                 :             :                                         "org.freedesktop.PolicyKit1.Authority",
    1440                 :             :                                         "CheckAuthorization",
    1441                 :             :                                         "(@r&s@a{ss}u&s)",
    1442                 :             :                                         NULL,
    1443                 :             :                                         NULL,
    1444                 :             :                                         &details,
    1445                 :             :                                         NULL,
    1446                 :             :                                         NULL);
    1447                 :           1 :   g_assert_true (g_variant_lookup (details, "duration_str", "&s", &duration_str));
    1448                 :           1 :   g_assert_cmpstr (duration_str, ==, "1 hour");
    1449         [ +  - ]:           1 :   g_clear_pointer (&details, g_variant_unref);
    1450                 :             : 
    1451                 :           1 :   g_dbus_method_invocation_return_value (client1_request1_invocation,
    1452                 :             :                                          g_variant_new_parsed ("((true, false, {'id-for-tests': '1'}),)"));
    1453                 :             : 
    1454                 :             :   /* Make a request from client 2 */
    1455                 :           1 :   client2_request1_object_path = extension_agent_request_extension (client2_connection,
    1456                 :             :                                                                     getuid (),
    1457                 :             :                                                                     "login-session",
    1458                 :             :                                                                     "",
    1459                 :             :                                                                     1800,
    1460                 :             :                                                                     FALSE,
    1461                 :             :                                                                     &local_error);
    1462                 :           1 :   g_assert_no_error (local_error);
    1463                 :             : 
    1464                 :             :   /* Close client 1’s request now it’s complete */
    1465                 :           1 :   assert_pop_request_response_queue (client1_request_response_queue, "(true, @a{sv} {})");
    1466                 :           1 :   extension_agent_request_close (client1_connection, client1_request1_object_path, &local_error);
    1467                 :           1 :   g_assert_no_error (local_error);
    1468                 :             : 
    1469                 :             :   /* Make another two requests from client 1 */
    1470                 :           1 :   client1_request2_object_path = extension_agent_request_extension (client1_connection,
    1471                 :             :                                                                     getuid (),
    1472                 :             :                                                                     "app",
    1473                 :             :                                                                     "org.freedesktop.Malcontent.TestApp",
    1474                 :             :                                                                     1000,
    1475                 :             :                                                                     FALSE,
    1476                 :             :                                                                     &local_error);
    1477                 :           1 :   g_assert_no_error (local_error);
    1478                 :             : 
    1479                 :           1 :   client1_request3_object_path = extension_agent_request_extension (client1_connection,
    1480                 :             :                                                                     getuid (),
    1481                 :             :                                                                     "login-session",
    1482                 :             :                                                                     "",
    1483                 :             :                                                                     7200,
    1484                 :             :                                                                     FALSE,
    1485                 :             :                                                                     &local_error);
    1486                 :           1 :   g_assert_no_error (local_error);
    1487                 :             : 
    1488                 :             :   /* Handle the polkit call for client 2’s first request */
    1489                 :           1 :   client2_request1_invocation =
    1490                 :           1 :       gt_dbus_queue_assert_pop_message (fixture->queue,
    1491                 :             :                                         "/org/freedesktop/PolicyKit1/Authority",
    1492                 :             :                                         "org.freedesktop.PolicyKit1.Authority",
    1493                 :             :                                         "CheckAuthorization",
    1494                 :             :                                         "(@r&s@a{ss}u&s)",
    1495                 :             :                                         NULL,
    1496                 :             :                                         NULL,
    1497                 :             :                                         &details,
    1498                 :             :                                         NULL,
    1499                 :             :                                         NULL);
    1500                 :           1 :   g_assert_true (g_variant_lookup (details, "duration_str", "&s", &duration_str));
    1501                 :           1 :   g_assert_cmpstr (duration_str, ==, "30 minutes");
    1502         [ +  - ]:           1 :   g_clear_pointer (&details, g_variant_unref);
    1503                 :             : 
    1504                 :           1 :   g_dbus_method_invocation_return_value (client2_request1_invocation,
    1505                 :             :                                          g_variant_new_parsed ("((true, false, {'id-for-tests': '2'}),)"));
    1506                 :             : 
    1507                 :             :   /* Handle the polkit call for client 1’s second request */
    1508                 :           1 :   client1_request2_invocation =
    1509                 :           1 :       gt_dbus_queue_assert_pop_message (fixture->queue,
    1510                 :             :                                         "/org/freedesktop/PolicyKit1/Authority",
    1511                 :             :                                         "org.freedesktop.PolicyKit1.Authority",
    1512                 :             :                                         "CheckAuthorization",
    1513                 :             :                                         "(@r&s@a{ss}u&s)",
    1514                 :             :                                         NULL,
    1515                 :             :                                         NULL,
    1516                 :             :                                         &details,
    1517                 :             :                                         NULL,
    1518                 :             :                                         NULL);
    1519                 :           1 :   g_assert_true (g_variant_lookup (details, "duration_str", "&s", &duration_str));
    1520                 :           1 :   g_assert_cmpstr (duration_str, ==, "16 minutes 40 seconds");
    1521         [ +  - ]:           1 :   g_clear_pointer (&details, g_variant_unref);
    1522                 :             : 
    1523                 :           1 :   g_dbus_method_invocation_return_value (client1_request2_invocation,
    1524                 :             :                                          g_variant_new_parsed ("((true, false, {'id-for-tests': '3'}),)"));
    1525                 :             : 
    1526                 :             :   /* Close client 1’s second request */
    1527                 :           1 :   assert_pop_request_response_queue (client1_request_response_queue, "(true, @a{sv} {})");
    1528                 :           1 :   extension_agent_request_close (client1_connection, client1_request2_object_path, &local_error);
    1529                 :           1 :   g_assert_no_error (local_error);
    1530                 :             : 
    1531                 :             :   /* Handle the polkit call for client 1’s third request */
    1532                 :           1 :   client1_request3_invocation =
    1533                 :           1 :       gt_dbus_queue_assert_pop_message (fixture->queue,
    1534                 :             :                                         "/org/freedesktop/PolicyKit1/Authority",
    1535                 :             :                                         "org.freedesktop.PolicyKit1.Authority",
    1536                 :             :                                         "CheckAuthorization",
    1537                 :             :                                         "(@r&s@a{ss}u&s)",
    1538                 :             :                                         NULL,
    1539                 :             :                                         NULL,
    1540                 :             :                                         &details,
    1541                 :             :                                         NULL,
    1542                 :             :                                         NULL);
    1543                 :           1 :   g_assert_true (g_variant_lookup (details, "duration_str", "&s", &duration_str));
    1544                 :           1 :   g_assert_cmpstr (duration_str, ==, "2 hours");
    1545         [ +  - ]:           1 :   g_clear_pointer (&details, g_variant_unref);
    1546                 :             : 
    1547                 :           1 :   g_dbus_method_invocation_return_value (client1_request3_invocation,
    1548                 :             :                                          g_variant_new_parsed ("((true, false, {'id-for-tests': '4'}),)"));
    1549                 :             : 
    1550                 :             :   /* Close the final remaining client 1 request */
    1551                 :           1 :   assert_pop_request_response_queue (client1_request_response_queue, "(true, @a{sv} {})");
    1552                 :           1 :   extension_agent_request_close (client1_connection, client1_request3_object_path, &local_error);
    1553                 :           1 :   g_assert_no_error (local_error);
    1554                 :             : 
    1555                 :             :   /* Client 1 disappears off the bus */
    1556                 :           1 :   g_assert_false (dbus_object_exists (client1_connection, "org.freedesktop.MalcontentTimer1.ExtensionAgent", client1_request1_object_path));
    1557                 :           1 :   g_assert_false (dbus_object_exists (client1_connection, "org.freedesktop.MalcontentTimer1.ExtensionAgent", client1_request2_object_path));
    1558                 :           1 :   g_assert_false (dbus_object_exists (client1_connection, "org.freedesktop.MalcontentTimer1.ExtensionAgent", client1_request3_object_path));
    1559                 :             : 
    1560                 :           1 :   g_assert_true (g_queue_is_empty (client1_request_response_queue));
    1561                 :           1 :   g_dbus_connection_signal_unsubscribe (client1_connection, client1_signal_id);
    1562                 :           1 :   client1_signal_id = 0;
    1563                 :             : 
    1564                 :           1 :   g_dbus_connection_close_sync (client1_connection, NULL, NULL);
    1565         [ +  - ]:           1 :   g_clear_object (&client1_connection);
    1566                 :             : 
    1567                 :             :   /* Close the remaining client 2 request */
    1568                 :           1 :   assert_pop_request_response_queue (client2_request_response_queue, "(true, @a{sv} {})");
    1569                 :           1 :   extension_agent_request_close (client2_connection, client2_request1_object_path, &local_error);
    1570                 :           1 :   g_assert_no_error (local_error);
    1571                 :             : 
    1572                 :             :   /* The agent should no longer be busy, and none of the Request objects should
    1573                 :             :    * exist any longer. */
    1574                 :           1 :   g_assert_false (mct_extension_agent_object_get_busy (MCT_EXTENSION_AGENT_OBJECT (fixture->extension_agent)));
    1575                 :           1 :   g_assert_false (dbus_object_exists (client2_connection, "org.freedesktop.MalcontentTimer1.ExtensionAgent", client2_request1_object_path));
    1576                 :             : 
    1577                 :             :   /* Clean up. */
    1578                 :           1 :   g_assert_true (g_queue_is_empty (client2_request_response_queue));
    1579                 :           1 :   g_dbus_connection_signal_unsubscribe (client2_connection, client2_signal_id);
    1580                 :           1 :   client2_signal_id = 0;
    1581                 :           1 : }
    1582                 :             : 
    1583                 :             : /* Test that calling Close() on a request belonging to a different D-Bus
    1584                 :             :  * connection is disallowed.
    1585                 :             :  *
    1586                 :             :  * The mock D-Bus replies are generated inline for simplicity. */
    1587                 :             : static void
    1588                 :           1 : test_extension_agent_polkit_request_close_unowned (BusFixture *fixture,
    1589                 :             :                                                    const void *test_data G_GNUC_UNUSED)
    1590                 :             : {
    1591                 :           1 :   g_autoptr(GError) local_error = NULL;
    1592                 :           1 :   g_autoptr(GDBusConnection) client1_connection = NULL;
    1593                 :           1 :   g_autoptr(GDBusConnection) client2_connection = NULL;
    1594                 :           1 :   g_autofree char *client1_request_object_path = NULL;
    1595                 :           1 :   g_autoptr(GDBusMethodInvocation) client1_request_invocation = NULL;
    1596                 :             : 
    1597                 :           1 :   client1_connection = g_object_ref (fixture->timerd_connection);
    1598                 :           1 :   client2_connection = dbus_queue_open_additional_client_connection ();
    1599                 :             : 
    1600                 :             :   /* Make a request from client 1 */
    1601                 :           1 :   client1_request_object_path = extension_agent_request_extension (client1_connection,
    1602                 :             :                                                                    getuid (),
    1603                 :             :                                                                    "login-session",
    1604                 :             :                                                                    "",
    1605                 :             :                                                                    3600,
    1606                 :             :                                                                    FALSE,
    1607                 :             :                                                                    &local_error);
    1608                 :           1 :   g_assert_no_error (local_error);
    1609                 :             : 
    1610                 :             :   /* Handle the polkit call for client 1’s request */
    1611                 :           1 :   client1_request_invocation =
    1612                 :           1 :       gt_dbus_queue_assert_pop_message (fixture->queue,
    1613                 :             :                                         "/org/freedesktop/PolicyKit1/Authority",
    1614                 :             :                                         "org.freedesktop.PolicyKit1.Authority",
    1615                 :             :                                         "CheckAuthorization",
    1616                 :             :                                         "(@r&s@a{ss}u&s)",
    1617                 :             :                                         NULL,
    1618                 :             :                                         NULL,
    1619                 :             :                                         NULL,
    1620                 :             :                                         NULL,
    1621                 :             :                                         NULL);
    1622                 :           1 :   g_dbus_method_invocation_return_value (client1_request_invocation,
    1623                 :             :                                          g_variant_new_parsed ("((true, false, @a{ss} {}),)"));
    1624                 :             : 
    1625                 :             :   /* Try and close client 1’s request from client 2 */
    1626                 :           1 :   extension_agent_request_close (client2_connection, client1_request_object_path, &local_error);
    1627                 :           1 :   g_assert_error (local_error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT);
    1628                 :           1 :   g_clear_error (&local_error);
    1629                 :             : 
    1630                 :             :   /* Actually close client 1’s request from client 1 */
    1631                 :           1 :   extension_agent_request_close (client1_connection, client1_request_object_path, &local_error);
    1632                 :           1 :   g_assert_no_error (local_error);
    1633                 :             : 
    1634                 :             :   /* The agent should no longer be busy, and none of the Request objects should
    1635                 :             :    * exist any longer. */
    1636                 :           1 :   g_assert_false (mct_extension_agent_object_get_busy (MCT_EXTENSION_AGENT_OBJECT (fixture->extension_agent)));
    1637                 :           1 :   g_assert_false (dbus_object_exists (client1_connection, "org.freedesktop.MalcontentTimer1.ExtensionAgent", client1_request_object_path));
    1638                 :           1 : }
    1639                 :             : 
    1640                 :             : /* Get the UID of a user with no GECOS field in `/etc/passwd`, so we can test
    1641                 :             :  * fallback code paths in the agent for handling that field being missing.
    1642                 :             :  *
    1643                 :             :  * On Fedora systems, the `gdm` user seems to have no GECOS data, so use that
    1644                 :             :  * if possible. Otherwise, we’ll just have to use a user with some GECOS data
    1645                 :             :  * and pass the test trivially.
    1646                 :             :  *
    1647                 :             :  * We could improve on this by mocking getpwuid_r(), but that’s more work for
    1648                 :             :  * now. */
    1649                 :             : static uid_t
    1650                 :           2 : get_uid_with_no_gecos (void)
    1651                 :             : {
    1652                 :             :   char buffer[4096];
    1653                 :             :   struct passwd pwbuf;
    1654                 :             :   struct passwd *result;
    1655                 :             :   int pwnam_errno;
    1656                 :             : 
    1657                 :           2 :   pwnam_errno = getpwnam_r ("gdm", &pwbuf, buffer, sizeof (buffer), &result);
    1658   [ +  -  -  +  :           2 :   if (pwnam_errno == 0 && result != NULL && (result->pw_gecos == NULL || result->pw_gecos[0] == '\0'))
             -  -  -  - ]
    1659                 :           0 :     return result->pw_uid;
    1660                 :             : 
    1661                 :           2 :   return getuid ();
    1662                 :             : }
    1663                 :             : 
    1664                 :             : int
    1665                 :           2 : main (int    argc,
    1666                 :             :       char **argv)
    1667                 :             : {
    1668                 :           2 :   setlocale (LC_ALL, "");
    1669                 :           2 :   g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
    1670                 :             : 
    1671                 :           2 :   g_test_add ("/extension-agent-polkit/construction", BusFixture, NULL,
    1672                 :             :               bus_set_up, test_extension_agent_polkit_construction, bus_tear_down);
    1673                 :             : 
    1674                 :          30 :   const SuccessfulRequestData successful_request_datas[] =
    1675                 :             :     {
    1676                 :             :       {
    1677                 :           2 :         .subject_uid = getuid (),
    1678                 :             :         .record_type = "login-session",
    1679                 :             :         .identifier = "",
    1680                 :             :         .duration_secs = 3600,
    1681                 :             :         .expected_polkit_message = "$(child_user_display_name) has requested a device screen time limit extension of $(duration_str) (until $(time_str))",
    1682                 :             :         .expected_duration_str = "1 hour",
    1683                 :             :       },
    1684                 :             :       {
    1685                 :           2 :         .subject_uid = getuid (),
    1686                 :             :         .record_type = "login-session",
    1687                 :             :         .identifier = "",
    1688                 :             :         .duration_secs = 0,
    1689                 :             :         .expected_polkit_message = "$(child_user_display_name) has requested a device screen time limit extension until the end of today (at $(time_str))",
    1690                 :             :       },
    1691                 :             :       {
    1692                 :           2 :         .subject_uid = getuid (),
    1693                 :             :         .record_type = "app",
    1694                 :             :         .identifier = "org.freedesktop.Malcontent.TestApp",
    1695                 :             :         .duration_secs = 3600,
    1696                 :             :         .expected_polkit_message = "$(child_user_display_name) has requested an app screen time limit extension of $(duration_str) (until $(time_str)) for $(app_name)",
    1697                 :             :         .expected_duration_str = "1 hour",
    1698                 :             :         .expected_app_name = "Test Application",
    1699                 :             :       },
    1700                 :             :       {
    1701                 :           2 :         .subject_uid = getuid (),
    1702                 :             :         .record_type = "app",
    1703                 :             :         .identifier = "org.freedesktop.Malcontent.TestApp",
    1704                 :             :         .duration_secs = 0,
    1705                 :             :         .expected_polkit_message = "$(child_user_display_name) has requested an app screen time limit extension until the end of today (at $(time_str)) for $(app_name)",
    1706                 :             :         .expected_app_name = "Test Application",
    1707                 :             :       },
    1708                 :             :       {
    1709                 :           2 :         .subject_uid = getuid (),
    1710                 :             :         .record_type = "app",
    1711                 :             :         .identifier = "org.gnome.Unknown",
    1712                 :             :         .duration_secs = 3600,
    1713                 :             :         .expected_polkit_message = "$(child_user_display_name) has requested an app screen time limit extension of $(duration_str) (until $(time_str)) for $(app_name)",
    1714                 :             :         .expected_duration_str = "1 hour",
    1715                 :             :         .expected_app_name = "org.gnome.Unknown",
    1716                 :             :       },
    1717                 :             :       {
    1718                 :           2 :         .subject_uid = getuid (),
    1719                 :             :         .record_type = "login-session",
    1720                 :             :         .identifier = "",
    1721                 :             :         .duration_secs = 1,
    1722                 :             :         .expected_polkit_message = "$(child_user_display_name) has requested a device screen time limit extension of $(duration_str) (until $(time_str))",
    1723                 :             :         .expected_duration_str = "1 second",
    1724                 :             :       },
    1725                 :             :       {
    1726                 :           2 :         .subject_uid = getuid (),
    1727                 :             :         .record_type = "login-session",
    1728                 :             :         .identifier = "",
    1729                 :             :         .duration_secs = 2,
    1730                 :             :         .expected_polkit_message = "$(child_user_display_name) has requested a device screen time limit extension of $(duration_str) (until $(time_str))",
    1731                 :             :         .expected_duration_str = "2 seconds",
    1732                 :             :       },
    1733                 :             :       {
    1734                 :           2 :         .subject_uid = getuid (),
    1735                 :             :         .record_type = "login-session",
    1736                 :             :         .identifier = "",
    1737                 :             :         .duration_secs = 60,
    1738                 :             :         .expected_polkit_message = "$(child_user_display_name) has requested a device screen time limit extension of $(duration_str) (until $(time_str))",
    1739                 :             :         .expected_duration_str = "1 minute",
    1740                 :             :       },
    1741                 :             :       {
    1742                 :           2 :         .subject_uid = getuid (),
    1743                 :             :         .record_type = "login-session",
    1744                 :             :         .identifier = "",
    1745                 :             :         .duration_secs = 120,
    1746                 :             :         .expected_polkit_message = "$(child_user_display_name) has requested a device screen time limit extension of $(duration_str) (until $(time_str))",
    1747                 :             :         .expected_duration_str = "2 minutes",
    1748                 :             :       },
    1749                 :             :       {
    1750                 :           2 :         .subject_uid = getuid (),
    1751                 :             :         .record_type = "login-session",
    1752                 :             :         .identifier = "",
    1753                 :             :         .duration_secs = 65,
    1754                 :             :         .expected_polkit_message = "$(child_user_display_name) has requested a device screen time limit extension of $(duration_str) (until $(time_str))",
    1755                 :             :         .expected_duration_str = "1 minute 5 seconds",
    1756                 :             :       },
    1757                 :             :       {
    1758                 :           2 :         .subject_uid = getuid (),
    1759                 :             :         .record_type = "login-session",
    1760                 :             :         .identifier = "",
    1761                 :             :         .duration_secs = 7200,
    1762                 :             :         .expected_polkit_message = "$(child_user_display_name) has requested a device screen time limit extension of $(duration_str) (until $(time_str))",
    1763                 :             :         .expected_duration_str = "2 hours",
    1764                 :             :       },
    1765                 :             :       {
    1766                 :           2 :         .subject_uid = getuid (),
    1767                 :             :         .record_type = "login-session",
    1768                 :             :         .identifier = "",
    1769                 :             :         .duration_secs = 3660,
    1770                 :             :         .expected_polkit_message = "$(child_user_display_name) has requested a device screen time limit extension of $(duration_str) (until $(time_str))",
    1771                 :             :         .expected_duration_str = "1 hour 1 minute",
    1772                 :             :       },
    1773                 :             :       {
    1774                 :           2 :         .subject_uid = getuid (),
    1775                 :             :         .record_type = "login-session",
    1776                 :             :         .identifier = "",
    1777                 :             :         .duration_secs = 3666,
    1778                 :             :         .expected_polkit_message = "$(child_user_display_name) has requested a device screen time limit extension of $(duration_str) (until $(time_str))",
    1779                 :             :         .expected_duration_str = "1 hour 1 minute 6 seconds",
    1780                 :             :       },
    1781                 :             :       {
    1782                 :           2 :         .subject_uid = get_uid_with_no_gecos (),
    1783                 :             :         .record_type = "login-session",
    1784                 :             :         .identifier = "",
    1785                 :             :         .duration_secs = 3600,
    1786                 :             :         .expected_polkit_message = "$(child_user_display_name) has requested a device screen time limit extension of $(duration_str) (until $(time_str))",
    1787                 :             :         .expected_duration_str = "1 hour",
    1788                 :             :       },
    1789                 :             :     };
    1790                 :             : 
    1791         [ +  + ]:          30 :   for (size_t i = 0; i < G_N_ELEMENTS (successful_request_datas); i++)
    1792                 :             :     {
    1793                 :          56 :       g_autofree char *test_path = g_strdup_printf ("/extension-agent-polkit/request/error/successful/%" G_GSIZE_FORMAT, i);
    1794                 :          28 :       g_test_add (test_path, BusFixture, &successful_request_datas[i],
    1795                 :             :                   bus_set_up, test_extension_agent_polkit_request_successful, bus_tear_down);
    1796                 :             :     }
    1797                 :             : 
    1798                 :           2 :   const PolkitErrorPair polkit_error_pairs[] =
    1799                 :             :     {
    1800                 :             :       { "org.freedesktop.PolicyKit1.Error.Failed",
    1801                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent.Error.Failed" },
    1802                 :             :       { "org.freedesktop.PolicyKit1.Error.NotSupported",
    1803                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent.Error.Failed" },
    1804                 :             :       { "org.freedesktop.PolicyKit1.Error.CancellationIdNotUnique",
    1805                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent.Error.Failed" },
    1806                 :             :       { "org.freedesktop.PolicyKit1.Error.Cancelled",
    1807                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent.Error.Cancelled" },
    1808                 :             :       { "org.freedesktop.PolicyKit1.Error.MadeUpDoesntExist",
    1809                 :             :         "org.freedesktop.MalcontentTimer1.ExtensionAgent.Error.Failed" },
    1810                 :             :     };
    1811         [ +  + ]:          12 :   for (size_t i = 0; i < G_N_ELEMENTS (polkit_error_pairs); i++)
    1812                 :             :     {
    1813                 :          20 :       g_autofree char *test_path = g_strdup_printf ("/extension-agent-polkit/request/error/polkit/%" G_GSIZE_FORMAT, i);
    1814                 :          10 :       g_test_add (test_path, BusFixture, &polkit_error_pairs[i],
    1815                 :             :                   bus_set_up, test_extension_agent_polkit_request_error_polkit, bus_tear_down);
    1816                 :             :     }
    1817                 :             : 
    1818                 :           2 :   g_test_add ("/extension-agent-polkit/request/error/user-non-existent", BusFixture, NULL,
    1819                 :             :               bus_set_up, test_extension_agent_polkit_request_error_user_non_existent, bus_tear_down);
    1820                 :             : 
    1821                 :           2 :   const ValidationData validation_datas[] =
    1822                 :             :     {
    1823                 :             :       {
    1824                 :             :         .record_type = "invalid",
    1825                 :             :         .identifier = "",
    1826                 :             :         .subject_kind = "unix-process",
    1827                 :             :         .subject_details = "@a{sv} { 'pidfd': <@h 0>, 'uid': <@i 1000> }",
    1828                 :             :       },
    1829                 :             :       {
    1830                 :             :         .record_type = "login-session",
    1831                 :             :         .identifier = "invalid",
    1832                 :             :         .subject_kind = "unix-process",
    1833                 :             :         .subject_details = "@a{sv} { 'pidfd': <@h 0>, 'uid': <@i 1000> }",
    1834                 :             :       },
    1835                 :             :       {
    1836                 :             :         .record_type = "app",
    1837                 :             :         .identifier = "",
    1838                 :             :         .subject_kind = "unix-process",
    1839                 :             :         .subject_details = "@a{sv} { 'pidfd': <@h 0>, 'uid': <@i 1000> }",
    1840                 :             :       },
    1841                 :             :       {
    1842                 :             :         .record_type = "login-session",
    1843                 :             :         .identifier = "",
    1844                 :             :         .subject_kind = "invalid",
    1845                 :             :         .subject_details = "@a{sv} { 'pidfd': <@h 0>, 'uid': <@i 1000> }",
    1846                 :             :       },
    1847                 :             :       {
    1848                 :             :         .record_type = "login-session",
    1849                 :             :         .identifier = "",
    1850                 :             :         .subject_kind = "unix-process",
    1851                 :             :         .subject_details = "@a{sv} { 'pidfd': <@h 0>, 'uid': <@s 'invald'> }",
    1852                 :             :       },
    1853                 :             :       {
    1854                 :             :         .record_type = "login-session",
    1855                 :             :         .identifier = "",
    1856                 :             :         .subject_kind = "unix-process",
    1857                 :             :         .subject_details = "@a{sv} { 'pidfd': <@h 0> }",
    1858                 :             :       },
    1859                 :             :       {
    1860                 :             :         .record_type = "login-session",
    1861                 :             :         .identifier = "",
    1862                 :             :         .subject_kind = "unix-process",
    1863                 :             :         .subject_details = "@a{sv} { 'uid': <@i 1000> }",
    1864                 :             :       },
    1865                 :             :       {
    1866                 :             :         .record_type = "login-session",
    1867                 :             :         .identifier = "",
    1868                 :             :         .subject_kind = "unix-process",
    1869                 :             :         .subject_details = "@a{sv} { 'pidfd': <@s 'invalid'>, 'uid': <@i 1000> }",
    1870                 :             :       },
    1871                 :             :       {
    1872                 :             :         .record_type = "login-session",
    1873                 :             :         .identifier = "",
    1874                 :             :         .subject_kind = "unix-process",
    1875                 :             :         .subject_details = "@a{sv} {}",
    1876                 :             :       },
    1877                 :             :     };
    1878         [ +  + ]:          20 :   for (size_t i = 0; i < G_N_ELEMENTS (validation_datas); i++)
    1879                 :             :     {
    1880                 :          36 :       g_autofree char *test_path = g_strdup_printf ("/extension-agent-polkit/request/error/validation/%" G_GSIZE_FORMAT, i);
    1881                 :          18 :       g_test_add (test_path, BusFixture, &validation_datas[i],
    1882                 :             :                   bus_set_up, test_extension_agent_polkit_request_error_validation, bus_tear_down);
    1883                 :             :     }
    1884                 :             : 
    1885                 :           2 :   g_test_add ("/extension-agent-polkit/properties", BusFixture, NULL,
    1886                 :             :               bus_set_up, test_extension_agent_polkit_properties, bus_tear_down);
    1887                 :           2 :   g_test_add ("/extension-agent-polkit/request/properties", BusFixture, NULL,
    1888                 :             :               bus_set_up, test_extension_agent_polkit_request_properties, bus_tear_down);
    1889                 :           2 :   g_test_add ("/extension-agent-polkit/request/error/cancelled", BusFixture, NULL,
    1890                 :             :               bus_set_up, test_extension_agent_polkit_request_error_cancelled, bus_tear_down);
    1891                 :           2 :   g_test_add ("/extension-agent-polkit/request/multiplexing", BusFixture, NULL,
    1892                 :             :               bus_set_up, test_extension_agent_polkit_request_multiplexing, bus_tear_down);
    1893                 :           2 :   g_test_add ("/extension-agent-polkit/request/close-unowned", BusFixture, NULL,
    1894                 :             :               bus_set_up, test_extension_agent_polkit_request_close_unowned, bus_tear_down);
    1895                 :             : 
    1896                 :           2 :   return g_test_run ();
    1897                 :             : }
        

Generated by: LCOV version 2.0-1