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 <glib/gi18n-lib.h>
29 : : #include <gio/gdesktopappinfo.h>
30 : : #include <gio/gio.h>
31 : : #include <libgsystemservice/peer-manager.h>
32 : : #include <libmalcontent-timer/extension-agent-object.h>
33 : : #include <libmalcontent-timer/extension-agent-object-polkit.h>
34 : : #include <sys/types.h>
35 : : #include <pwd.h>
36 : :
37 : :
38 : : static void mct_extension_agent_object_polkit_request_extension_async (MctExtensionAgentObject *agent,
39 : : const char *record_type,
40 : : const char *identifier,
41 : : uint64_t duration_secs,
42 : : GVariant *extra_data,
43 : : GVariant *subject,
44 : : GUnixFDList *subject_fd_list,
45 : : GDBusMethodInvocation *invocation,
46 : : GCancellable *cancellable,
47 : : GAsyncReadyCallback callback,
48 : : void *user_data);
49 : : static gboolean mct_extension_agent_object_polkit_request_extension_finish (MctExtensionAgentObject *agent,
50 : : GAsyncResult *result,
51 : : gboolean *out_granted,
52 : : GVariant **out_extra_data,
53 : : GError **error);
54 : :
55 : : /**
56 : : * MctExtensionAgentObjectPolkit:
57 : : *
58 : : * An implementation of [class@Malcontent.ExtensionAgentObject] which uses
59 : : * polkit on the local machine to ask the parent to authorise screen time
60 : : * extensions.
61 : : *
62 : : * It checks authorisation for the
63 : : * `org.freedesktop.Malcontent.SessionLimits.Extend` polkit action, with details
64 : : * specifying the user, record type/identifier and duration.
65 : : *
66 : : * Since: 0.14.0
67 : : */
68 : : struct _MctExtensionAgentObjectPolkit
69 : : {
70 : : MctExtensionAgentObject parent;
71 : : };
72 : :
73 [ + + + - : 94 : G_DEFINE_TYPE (MctExtensionAgentObjectPolkit, mct_extension_agent_object_polkit, MCT_TYPE_EXTENSION_AGENT_OBJECT)
+ + ]
74 : :
75 : : static void
76 : 2 : mct_extension_agent_object_polkit_class_init (MctExtensionAgentObjectPolkitClass *klass)
77 : : {
78 : 2 : MctExtensionAgentObjectClass *object_class = MCT_EXTENSION_AGENT_OBJECT_CLASS (klass);
79 : :
80 : 2 : object_class->request_extension_async = mct_extension_agent_object_polkit_request_extension_async;
81 : 2 : object_class->request_extension_finish = mct_extension_agent_object_polkit_request_extension_finish;
82 : 2 : }
83 : :
84 : : static void
85 : 36 : mct_extension_agent_object_polkit_init (MctExtensionAgentObjectPolkit *self)
86 : : {
87 : 36 : }
88 : :
89 : : static char *
90 : 4 : look_up_app_name_for_id (const char *identifier)
91 : : {
92 : 4 : g_autoptr(GDesktopAppInfo) info = NULL;
93 : 4 : g_autofree char *desktop_id = NULL;
94 : :
95 : 4 : desktop_id = g_strconcat (identifier, ".desktop", NULL);
96 : 4 : info = g_desktop_app_info_new (desktop_id);
97 : :
98 [ + + ]: 8 : return (info != NULL) ? g_strdup (g_app_info_get_display_name (G_APP_INFO (info))) : g_strdup (identifier);
99 : : }
100 : :
101 : : /* Copied from panels/common/cc-util.c in gnome-control-center */
102 : : static char *
103 : 26 : format_hours_minutes (uint64_t duration_secs)
104 : : {
105 : 26 : g_autofree char *hours = NULL;
106 : 26 : g_autofree char *mins = NULL;
107 : 26 : g_autofree char *secs = NULL;
108 : : uint64_t sec, min, hour;
109 : :
110 : 26 : sec = duration_secs % 60;
111 : 26 : duration_secs = duration_secs - sec;
112 : 26 : min = (duration_secs % (60*60)) / 60;
113 : 26 : duration_secs = duration_secs - (min * 60);
114 : 26 : hour = duration_secs / (60*60);
115 : :
116 : 26 : hours = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%" G_GUINT64_FORMAT " hour", "%" G_GUINT64_FORMAT " hours", hour), hour);
117 : 26 : mins = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%" G_GUINT64_FORMAT " minute", "%" G_GUINT64_FORMAT " minutes", min), min);
118 : 26 : secs = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%" G_GUINT64_FORMAT " second", "%" G_GUINT64_FORMAT " seconds", sec), sec);
119 : :
120 [ + + ]: 26 : if (hour > 0)
121 : : {
122 [ + + + + ]: 17 : if (min > 0 && sec > 0)
123 : : {
124 : : /* 5 hours 2 minutes 12 seconds */
125 : 1 : return g_strdup_printf (C_("hours minutes seconds", "%s %s %s"), hours, mins, secs);
126 : : }
127 [ + + ]: 16 : else if (min > 0)
128 : : {
129 : : /* 5 hours 2 minutes */
130 : 1 : return g_strdup_printf (C_("hours minutes", "%s %s"), hours, mins);
131 : : }
132 : : else
133 : : {
134 : : /* 5 hours */
135 : 15 : return g_strdup_printf (C_("hours", "%s"), hours);
136 : : }
137 : : }
138 [ + + ]: 9 : else if (min > 0)
139 : : {
140 [ + + ]: 5 : if (sec > 0)
141 : : {
142 : : /* 2 minutes 12 seconds */
143 : 2 : return g_strdup_printf (C_("minutes seconds", "%s %s"), mins, secs);
144 : : }
145 : : else
146 : : {
147 : : /* 2 minutes */
148 : 3 : return g_strdup_printf (C_("minutes", "%s"), mins);
149 : : }
150 : : }
151 [ + + ]: 4 : else if (sec > 0)
152 : : {
153 : : /* 10 seconds */
154 : 4 : return g_strdup (secs);
155 : : }
156 : : else
157 : : {
158 : : /* 0 seconds */
159 : 4 : return g_strdup (_("0 seconds"));
160 : : }
161 : : }
162 : :
163 : : /* Potentially we should be using accountsservice to do this nicely, but I
164 : : * haven’t evaluated whether it’s safe to call into accountsservice in the
165 : : * extension-agent context
166 : : *
167 : : * Note: This may return `NULL` without setting @error, if the given @uid
168 : : * doesn’t exist at all. */
169 : : static char *
170 : 27 : look_up_user_display_name (uid_t uid,
171 : : GError **error)
172 : : {
173 : : char buffer[4096];
174 : : struct passwd pwbuf;
175 : : struct passwd *result;
176 : : int pwuid_errno;
177 : 27 : g_autofree char *display_name = NULL;
178 : 27 : g_autoptr(GError) local_error = NULL;
179 : :
180 : 27 : pwuid_errno = getpwuid_r (uid, &pwbuf, buffer, sizeof (buffer), &result);
181 : :
182 [ + + ]: 27 : if (result != NULL &&
183 [ + - - + ]: 26 : result->pw_gecos != NULL && result->pw_gecos[0] != '\0')
184 : : {
185 : 0 : display_name = g_locale_to_utf8 (result->pw_gecos, -1, NULL, NULL, &local_error);
186 [ # # ]: 0 : if (display_name == NULL)
187 : : {
188 : 0 : g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
189 : : _("Error getting details for UID %d: %s"),
190 : 0 : (int) uid, local_error->message);
191 : 0 : return NULL;
192 : : }
193 : : }
194 [ + + ]: 27 : else if (result != NULL)
195 : : {
196 : 26 : display_name = g_strdup_printf ("%d", (int) uid);
197 : : }
198 [ + - ]: 1 : else if (pwuid_errno == 0)
199 : : {
200 : : /* User not found. */
201 : 1 : return NULL;
202 : : }
203 : : else
204 : : {
205 : : /* Error calling getpwuid_r(). */
206 : 0 : g_set_error (error, G_IO_ERROR, g_io_error_from_errno (pwuid_errno),
207 : : _("Error getting details for UID %d: %s"),
208 : : (int) uid, g_strerror (pwuid_errno));
209 : 0 : return NULL;
210 : : }
211 : :
212 : 26 : return g_steal_pointer (&display_name);
213 : : }
214 : :
215 : : /* https://www.freedesktop.org/software/polkit/docs/latest/eggdbus-interface-org.freedesktop.PolicyKit1.Authority.html#eggdbus-struct-Subject */
216 : : static uid_t
217 : 27 : uid_from_polkit_subject (GVariant *subject)
218 : : {
219 : : const char *subject_kind;
220 : 54 : g_autoptr(GVariant) subject_details = NULL;
221 : : uid_t uid;
222 : :
223 : 27 : g_assert (g_variant_is_of_type (subject, G_VARIANT_TYPE ("(sa{sv})")));
224 : :
225 : 27 : g_variant_get (subject, "(&s@a{sv})", &subject_kind, &subject_details);
226 : :
227 [ + - ]: 27 : if (g_str_equal (subject_kind, "unix-process"))
228 : : {
229 : : /* FIXME: This only handles the pidfd version of unix-process, but that’s
230 : : * all we use in malcontent-timerd at the moment. validate_subject()
231 : : * guarantees that this key is available. */
232 : 27 : gboolean success = g_variant_lookup (subject_details, "uid", "i", &uid);
233 : 27 : g_assert (success);
234 : :
235 : 27 : return uid;
236 : : }
237 : : else
238 : : {
239 : : /* FIXME: Not supported yet */
240 : : g_assert_not_reached ();
241 : : }
242 : : }
243 : :
244 : : static GDateTime *
245 : 2 : get_end_of_day (GDateTime *day)
246 : : {
247 : 2 : return g_date_time_new_local (g_date_time_get_year (day),
248 : : g_date_time_get_month (day),
249 : : g_date_time_get_day_of_month (day),
250 : : 23, 59, 59);
251 : : }
252 : :
253 : : static void request_extension_check_authorization_cancelled_cb (GCancellable *cancellable,
254 : : void *user_data);
255 : : static void request_extension_check_authorization_cb (GObject *object,
256 : : GAsyncResult *result,
257 : : void *user_data);
258 : :
259 : : typedef struct
260 : : {
261 : : /* In-progress state: */
262 : : char *cancellation_id; /* (not nullable) */
263 : : GCancellable *cancellable; /* (nullable) (owned) */
264 : : unsigned long cancelled_id; /* (owned) */
265 : :
266 : : /* Return values: */
267 : : gboolean granted;
268 : : GVariant *extra_data; /* (nullable) (owned) (not floating) */
269 : : } RequestExtensionData;
270 : :
271 : : static void
272 : 27 : request_extension_data_free (RequestExtensionData *data)
273 : : {
274 [ + - ]: 27 : g_clear_pointer (&data->cancellation_id, g_free);
275 : 27 : g_cancellable_disconnect (data->cancellable, data->cancelled_id);
276 [ + - ]: 27 : g_clear_object (&data->cancellable);
277 : 27 : data->cancelled_id = 0;
278 : :
279 [ + + ]: 27 : g_clear_pointer (&data->extra_data, g_variant_unref);
280 : 27 : g_free (data);
281 : 27 : }
282 : :
283 [ - + ]: 54 : G_DEFINE_AUTOPTR_CLEANUP_FUNC (RequestExtensionData, request_extension_data_free)
284 : :
285 : : static void
286 : 27 : mct_extension_agent_object_polkit_request_extension_async (MctExtensionAgentObject *agent,
287 : : const char *record_type,
288 : : const char *identifier,
289 : : uint64_t duration_secs,
290 : : GVariant *extra_data,
291 : : GVariant *subject,
292 : : GUnixFDList *subject_fd_list,
293 : : GDBusMethodInvocation *invocation,
294 : : GCancellable *cancellable,
295 : : GAsyncReadyCallback callback,
296 : : void *user_data)
297 : : {
298 : 27 : MctExtensionAgentObjectPolkit *self = MCT_EXTENSION_AGENT_OBJECT_POLKIT (agent);
299 [ + + ]: 27 : g_autoptr(GTask) task = NULL;
300 [ + + ]: 27 : g_autoptr(RequestExtensionData) data_owned = NULL;
301 : : RequestExtensionData *data;
302 : : GDBusCallFlags call_flags;
303 : : const char *action_id;
304 [ + + ]: 27 : g_autoptr(GVariant) details = NULL; /* a{ss} */
305 : : uint32_t flags;
306 : : const char *polkit_message;
307 [ + + ]: 27 : g_autofree char *app_name = NULL;
308 [ + + ]: 27 : g_autofree char *child_user_display_name = NULL;
309 [ + + ]: 27 : g_autofree char *duration_str = NULL;
310 [ + + ]: 27 : g_autofree char *time_str = NULL;
311 [ + + ]: 27 : g_autoptr(GDateTime) now = NULL;
312 [ + + ]: 27 : g_autoptr(GDateTime) end_time = NULL;
313 : : uid_t child_user_uid;
314 [ + + ]: 27 : g_autoptr(GError) local_error = NULL;
315 : :
316 : 27 : task = g_task_new (self, cancellable, callback, user_data);
317 [ + - ]: 27 : g_task_set_source_tag (task, mct_extension_agent_object_polkit_request_extension_async);
318 : :
319 : : /* We want to handle cancellation explicitly */
320 : 27 : g_task_set_check_cancellable (task, FALSE);
321 : :
322 : 27 : data = data_owned = g_new0 (RequestExtensionData, 1);
323 : 27 : g_task_set_task_data (task, g_steal_pointer (&data_owned), (GDestroyNotify) request_extension_data_free);
324 : :
325 : : /* In this simple agent implementation, let’s always delegate to polkit for
326 : : * the policy decision. Using libpolkit-gobject would be overkill here, and
327 : : * would not make it straightforward to pass the subject through from the
328 : : * caller, so just use the polkit D-Bus API directly. */
329 : 27 : call_flags = G_DBUS_CALL_FLAGS_NONE;
330 : :
331 : 27 : action_id = "org.freedesktop.Malcontent.SessionLimits.Extend";
332 : 27 : flags = 0;
333 : :
334 : : /* Handle cancellation */
335 [ + - ]: 27 : if (cancellable != NULL)
336 : : {
337 : 27 : data->cancellation_id = g_strdup_printf ("cancellation-%u", g_random_int ());
338 : 27 : data->cancellable = g_object_ref (cancellable);
339 : 27 : data->cancelled_id = g_cancellable_connect (cancellable,
340 : : G_CALLBACK (request_extension_check_authorization_cancelled_cb),
341 : : task, NULL);
342 : : }
343 : : else
344 : : {
345 : : /* Cancellation is not needed */
346 : 0 : data->cancellation_id = g_strdup ("");
347 : : }
348 : :
349 [ + + ]: 27 : if (g_str_equal (record_type, "app"))
350 : : {
351 : 4 : polkit_message = (duration_secs > 0)
352 : : /* Translators: The placeholders in $(brackets) are inserted into the string
353 : : * later by polkit. Please do not translate the text inside the brackets. */
354 : : ? N_("$(child_user_display_name) has requested an app screen time limit extension of $(duration_str) (until $(time_str)) for $(app_name)")
355 : : /* Translators: The placeholders in $(brackets) are inserted into the string
356 : : * later by polkit. Please do not translate the text inside the brackets. */
357 [ + + ]: 4 : : N_("$(child_user_display_name) has requested an app screen time limit extension until the end of today (at $(time_str)) for $(app_name)");
358 : 4 : app_name = look_up_app_name_for_id (identifier);
359 : : }
360 [ + - ]: 23 : else if (g_str_equal (record_type, "login-session"))
361 : : {
362 : 23 : polkit_message = (duration_secs > 0)
363 : : /* Translators: The placeholders in $(brackets) are inserted into the string
364 : : * later by polkit. Please do not translate the text inside the brackets. */
365 : : ? N_("$(child_user_display_name) has requested a device screen time limit extension of $(duration_str) (until $(time_str))")
366 : : /* Translators: The placeholders in $(brackets) are inserted into the string
367 : : * later by polkit. Please do not translate the text inside the brackets. */
368 [ + + ]: 23 : : N_("$(child_user_display_name) has requested a device screen time limit extension until the end of today (at $(time_str))");
369 : 23 : app_name = NULL;
370 : : }
371 : : else
372 : : {
373 : : g_assert_not_reached ();
374 : : }
375 : :
376 : 27 : child_user_uid = uid_from_polkit_subject (subject);
377 : 27 : g_assert (child_user_uid != (uid_t) -1);
378 : 27 : child_user_display_name = look_up_user_display_name (child_user_uid, &local_error);
379 [ + + ]: 27 : if (child_user_display_name == NULL)
380 : : {
381 : 1 : g_task_return_new_error (task, MCT_EXTENSION_AGENT_OBJECT_ERROR,
382 : : MCT_EXTENSION_AGENT_OBJECT_ERROR_IDENTIFYING_USER,
383 : 1 : _("Error identifying user: %s"),
384 [ - + ]: 1 : (local_error != NULL) ? local_error->message : _("Invalid or unknown user"));
385 : 1 : return;
386 : : }
387 : :
388 : 26 : duration_str = format_hours_minutes (duration_secs);
389 : 26 : now = g_date_time_new_now_local ();
390 [ + + ]: 26 : if (duration_secs == 0)
391 : 2 : end_time = get_end_of_day (now);
392 : : else
393 : 24 : end_time = g_date_time_add_seconds (now, duration_secs);
394 : 26 : time_str = g_date_time_format (end_time, "%c");
395 : :
396 : : /* See https://www.freedesktop.org/software/polkit/docs/latest/eggdbus-interface-org.freedesktop.PolicyKit1.Authority.html#eggdbus-method-org.freedesktop.PolicyKit1.Authority.CheckAuthorization
397 : : * for docs about the available details */
398 : 52 : g_auto(GVariantBuilder) details_builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}"));
399 : 26 : g_variant_builder_add (&details_builder, "{ss}", "polkit.message", polkit_message);
400 : 26 : g_variant_builder_add (&details_builder, "{ss}", "polkit.gettext_domain", GETTEXT_PACKAGE);
401 : 26 : g_variant_builder_add (&details_builder, "{ss}", "polkit.icon_name", "org.freedesktop.MalcontentControl");
402 : 26 : g_variant_builder_add (&details_builder, "{ss}", "child_user_display_name", child_user_display_name);
403 [ + + ]: 26 : if (duration_secs > 0)
404 : 24 : g_variant_builder_add (&details_builder, "{ss}", "duration_str", duration_str);
405 : 26 : g_variant_builder_add (&details_builder, "{ss}", "time_str", time_str);
406 [ + + ]: 26 : if (app_name != NULL)
407 : 4 : g_variant_builder_add (&details_builder, "{ss}", "app_name", app_name);
408 : 26 : details = g_variant_ref_sink (g_variant_builder_end (&details_builder));
409 : :
410 [ + + ]: 26 : if (g_dbus_message_get_flags (g_dbus_method_invocation_get_message (invocation)) & G_DBUS_MESSAGE_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION)
411 : : {
412 : 19 : call_flags |= G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION;
413 : 19 : flags |= 1 << 0; /* AllowUserInteraction */
414 : : }
415 : :
416 : : /* Start authorisation */
417 : 26 : g_dbus_connection_call_with_unix_fd_list (mct_extension_agent_object_get_connection (agent),
418 : : "org.freedesktop.PolicyKit1",
419 : : "/org/freedesktop/PolicyKit1/Authority",
420 : : "org.freedesktop.PolicyKit1.Authority",
421 : : "CheckAuthorization",
422 : : g_variant_new ("(@rs@a{ss}us)",
423 : : subject,
424 : : action_id,
425 : : details,
426 : : flags,
427 : : data->cancellation_id),
428 : : /* polkit unfortunately seems to have one extra level of tuple nesting */
429 : : G_VARIANT_TYPE ("((bba{ss}))"),
430 : : call_flags,
431 : : -1, /* timeout (ms) */
432 : : subject_fd_list,
433 : : cancellable,
434 : : request_extension_check_authorization_cb,
435 : : g_steal_pointer (&task));
436 : : }
437 : :
438 : : static void
439 : 1 : request_extension_check_authorization_cancelled_cb (GCancellable *cancellable,
440 : : void *user_data)
441 : : {
442 : 1 : GTask *task = G_TASK (user_data);
443 : 1 : MctExtensionAgentObjectPolkit *self = g_task_get_source_object (task);
444 : 1 : RequestExtensionData *data = g_task_get_task_data (task);
445 : :
446 : : /* Calling this should cause the CheckAuthorization() method call to return
447 : : * early, so cleanup will be handled in request_extension_check_authorization_cb() */
448 : 1 : g_dbus_connection_call (mct_extension_agent_object_get_connection (MCT_EXTENSION_AGENT_OBJECT (self)),
449 : : "org.freedesktop.PolicyKit1",
450 : : "/org/freedesktop/PolicyKit1/Authority",
451 : : "org.freedesktop.PolicyKit1.Authority",
452 : : "CancelCheckAuthorization",
453 : : g_variant_new ("(s)", data->cancellation_id),
454 : : NULL, /* don’t care about the return */
455 : : G_DBUS_CALL_FLAGS_NONE,
456 : : -1, /* timeout (ms) */
457 : : NULL, /* cancellable */
458 : : NULL, /* callback */
459 : : NULL);
460 : 1 : }
461 : :
462 : : static void
463 : 26 : request_extension_check_authorization_cb (GObject *object,
464 : : GAsyncResult *result,
465 : : void *user_data)
466 : : {
467 : 26 : GDBusConnection *connection = G_DBUS_CONNECTION (object);
468 [ + + ]: 52 : g_autoptr(GTask) task = g_steal_pointer (&user_data);
469 [ + + ]: 26 : g_autoptr(GVariant) reply = NULL;
470 : 26 : RequestExtensionData *data = g_task_get_task_data (task);
471 : 26 : gboolean is_authorized = FALSE, is_challenge = FALSE;
472 [ + + ]: 26 : g_autoptr(GVariant) details = NULL;
473 [ + + ]: 26 : g_autoptr(GError) local_error = NULL;
474 : :
475 : 26 : reply = g_dbus_connection_call_finish (connection, result, &local_error);
476 : :
477 [ + + ]: 26 : if (reply == NULL)
478 : : {
479 [ - + ]: 12 : g_autofree char *remote_error = g_dbus_error_get_remote_error (local_error);
480 : :
481 [ + + + + ]: 11 : if (g_strcmp0 (remote_error, "org.freedesktop.PolicyKit1.Error.Failed") == 0 ||
482 [ + + ]: 9 : g_strcmp0 (remote_error, "org.freedesktop.PolicyKit1.Error.NotSupported") == 0 ||
483 : 4 : g_strcmp0 (remote_error, "org.freedesktop.PolicyKit1.Error.CancellationIdNotUnique") == 0)
484 : : {
485 : : /* Treat these as internal errors */
486 : 3 : g_task_return_new_error (task, MCT_EXTENSION_AGENT_OBJECT_ERROR,
487 : : MCT_EXTENSION_AGENT_OBJECT_ERROR_FAILED,
488 : 3 : _("Error requesting extension: %s"),
489 : 3 : local_error->message);
490 : 3 : return;
491 : : }
492 [ + - ]: 3 : else if (g_strcmp0 (remote_error, "org.freedesktop.PolicyKit1.Error.NotAuthorized") == 0)
493 : : {
494 : : /* This probably means our polkit action isn’t set up properly (for
495 : : * example, the action owner might not be matching the process UID),
496 : : * but let’s treat it like the request was denied. */
497 : : /* fall through */
498 : : }
499 [ + + + + ]: 5 : else if (g_strcmp0 (remote_error, "org.freedesktop.PolicyKit1.Error.Cancelled") == 0 ||
500 : 2 : g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
501 : : {
502 : : /* In-progress request cancelled */
503 : 2 : g_task_return_new_error (task, MCT_EXTENSION_AGENT_OBJECT_ERROR,
504 : : MCT_EXTENSION_AGENT_OBJECT_ERROR_CANCELLED,
505 : 2 : _("Error requesting extension: %s"),
506 : 2 : _("Request cancelled"));
507 : 2 : return;
508 : : }
509 : : else
510 : : {
511 : 1 : g_task_return_new_error (task, MCT_EXTENSION_AGENT_OBJECT_ERROR,
512 : : MCT_EXTENSION_AGENT_OBJECT_ERROR_FAILED,
513 : 1 : _("Error requesting extension: %s"),
514 : 1 : local_error->message);
515 : 1 : return;
516 : : }
517 : : }
518 : :
519 : : /* This is an AuthorizationResult structure from polkit. We only really care
520 : : * about @is_authorized. */
521 [ + - ]: 20 : if (reply != NULL)
522 : 20 : g_variant_get (reply, "((bb@a{ss}))", &is_authorized, &is_challenge, &details);
523 : :
524 : 20 : data->granted = is_authorized;
525 : 20 : data->extra_data = g_variant_ref_sink (g_variant_new_parsed ("@a{sv} {}")); /* no extra data to return */
526 : :
527 : 20 : g_task_return_boolean (task, TRUE); /* this means success, not necessarily that permission was granted */
528 : : }
529 : :
530 : : static gboolean
531 : 27 : mct_extension_agent_object_polkit_request_extension_finish (MctExtensionAgentObject *agent,
532 : : GAsyncResult *result,
533 : : gboolean *out_granted,
534 : : GVariant **out_extra_data,
535 : : GError **error)
536 : : {
537 : : gboolean success;
538 : : gboolean granted;
539 : : GVariant *extra_data;
540 : : RequestExtensionData *data;
541 : :
542 : 27 : g_return_val_if_fail (MCT_IS_EXTENSION_AGENT_OBJECT_POLKIT (agent), FALSE);
543 : 27 : g_return_val_if_fail (g_task_is_valid (result, agent), FALSE);
544 : 27 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
545 : :
546 : 27 : success = g_task_propagate_boolean (G_TASK (result), error);
547 : 27 : data = g_task_get_task_data (G_TASK (result));
548 : :
549 : : /* Should be using MCT_EXTENSION_AGENT_OBJECT_ERROR_CANCELLED instead */
550 [ + - + + ]: 27 : if (error != NULL && *error != NULL)
551 : 7 : g_assert (!g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_CANCELLED));
552 : :
553 [ + + ]: 27 : if (success)
554 : : {
555 : 20 : granted = data->granted;
556 : 20 : extra_data = data->extra_data;
557 : : }
558 : : else
559 : : {
560 : 7 : granted = FALSE;
561 : 7 : extra_data = NULL;
562 : : }
563 : :
564 [ + - ]: 27 : if (out_granted != NULL)
565 : 27 : *out_granted = granted;
566 [ + - ]: 27 : if (out_extra_data != NULL)
567 [ + + ]: 27 : *out_extra_data = (extra_data != NULL) ? g_variant_ref (extra_data) : NULL;
568 : :
569 : 27 : return success;
570 : : }
571 : :
572 : : /**
573 : : * mct_extension_agent_object_polkit_new:
574 : : * @connection: (transfer none): D-Bus connection to export objects on
575 : : * @object_path: root path to export objects below; must be a valid D-Bus object
576 : : * path
577 : : *
578 : : * Create a new [class@Malcontent.ExtensionAgentObjectPolkit] instance which is
579 : : * set up to run as a service.
580 : : *
581 : : * Returns: (transfer full): a new [class@Malcontent.ExtensionAgentObjectPolkit]
582 : : * Since: 0.14.0
583 : : */
584 : : MctExtensionAgentObjectPolkit *
585 : 36 : mct_extension_agent_object_polkit_new (GDBusConnection *connection,
586 : : const char *object_path)
587 : : {
588 : 36 : g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
589 : 36 : g_return_val_if_fail (g_variant_is_object_path (object_path), NULL);
590 : :
591 : 36 : return g_object_new (MCT_TYPE_EXTENSION_AGENT_OBJECT_POLKIT,
592 : : "connection", connection,
593 : : "object-path", object_path,
594 : : NULL);
595 : : }
|