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 : : }
|