LCOV - code coverage report
Current view: top level - libmalcontent-timer - extension-agent-object-polkit.c (source / functions) Coverage Total Hit
Test: 2 coverage DB files Lines: 96.0 % 200 192
Test Date: 2025-11-20 19:57:30 Functions: 100.0 % 18 18
Branches: 84.1 % 126 106

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

Generated by: LCOV version 2.0-1