malcontent-control: Add polkit policy support

Add an unlock screen to the application, which is shown on startup if
the current user doesn’t have permission to view the parental controls
of other users. It requests permission using a new polkit action which
implies the various accounts-service actions we need.

This adds a dependency on `polkit-gobject-1`.

Signed-off-by: Philip Withnall <withnall@endlessm.com>
This commit is contained in:
Philip Withnall 2020-01-28 14:30:26 +00:00
parent 36162c2c23
commit 8badee7fa9
5 changed files with 179 additions and 5 deletions

View File

@ -27,6 +27,7 @@
#include <glib/gi18n-lib.h>
#include <gio/gio.h>
#include <gtk/gtk.h>
#include <polkit/polkit.h>
#include "application.h"
#include "user-controls.h"
@ -39,6 +40,12 @@ static void user_selector_notify_user_cb (GObject *obj,
static void user_manager_notify_is_loaded_cb (GObject *obj,
GParamSpec *pspec,
gpointer user_data);
static void permission_new_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void permission_notify_allowed_cb (GObject *obj,
GParamSpec *pspec,
gpointer user_data);
/**
@ -53,13 +60,19 @@ struct _MctApplication
{
GtkApplication parent_instance;
GCancellable *cancellable; /* (owned) */
ActUserManager *user_manager; /* (owned) */
GPermission *permission; /* (owned) */
GError *permission_error; /* (nullable) (owned) */
MctUserSelector *user_selector;
MctUserControls *user_controls;
GtkStack *main_stack;
GtkLabel *error_title;
GtkLabel *error_message;
GtkLockButton *lock_button;
};
G_DEFINE_TYPE (MctApplication, mct_application, GTK_TYPE_APPLICATION)
@ -67,7 +80,7 @@ G_DEFINE_TYPE (MctApplication, mct_application, GTK_TYPE_APPLICATION)
static void
mct_application_init (MctApplication *self)
{
/* Nothing to do here. */
self->cancellable = g_cancellable_new ();
}
static void
@ -93,6 +106,8 @@ mct_application_dispose (GObject *object)
{
MctApplication *self = MCT_APPLICATION (object);
g_cancellable_cancel (self->cancellable);
if (self->user_manager != NULL)
{
g_signal_handlers_disconnect_by_func (self->user_manager,
@ -100,6 +115,16 @@ mct_application_dispose (GObject *object)
g_clear_object (&self->user_manager);
}
if (self->permission != NULL)
{
g_signal_handlers_disconnect_by_func (self->permission,
permission_notify_allowed_cb, self);
g_clear_object (&self->permission);
}
g_clear_error (&self->permission_error);
g_clear_object (&self->cancellable);
G_OBJECT_CLASS (mct_application_parent_class)->dispose (object);
}
@ -126,6 +151,11 @@ mct_application_activate (GApplication *application)
g_type_ensure (MCT_TYPE_USER_CONTROLS);
g_type_ensure (MCT_TYPE_USER_SELECTOR);
/* Start loading the permission */
polkit_permission_new ("org.freedesktop.MalcontentControl.administration",
NULL, self->cancellable,
permission_new_cb, self);
builder = gtk_builder_new ();
g_assert (self->user_manager == NULL);
@ -146,6 +176,7 @@ mct_application_activate (GApplication *application)
self->user_controls = MCT_USER_CONTROLS (gtk_builder_get_object (builder, "user_controls"));
self->error_title = GTK_LABEL (gtk_builder_get_object (builder, "error_title"));
self->error_message = GTK_LABEL (gtk_builder_get_object (builder, "error_message"));
self->lock_button = GTK_LOCK_BUTTON (gtk_builder_get_object (builder, "lock_button"));
/* Connect signals. */
g_signal_connect_object (self->user_selector, "notify::user",
@ -181,16 +212,19 @@ mct_application_class_init (MctApplicationClass *klass)
static void
update_main_stack (MctApplication *self)
{
gboolean is_user_manager_loaded;
gboolean is_user_manager_loaded, is_permission_loaded, has_permission;
const gchar *new_page_name, *old_page_name;
GtkWidget *new_focus_widget;
/* The implementation of #ActUserManager guarantees that once is-loaded is
* true, it is never reset to false. */
g_object_get (self->user_manager, "is-loaded", &is_user_manager_loaded, NULL);
is_permission_loaded = (self->permission != NULL || self->permission_error != NULL);
has_permission = (self->permission != NULL && g_permission_get_allowed (self->permission));
/* Handle any loading errors. */
if (is_user_manager_loaded && act_user_manager_no_service (self->user_manager))
/* Handle any loading errors (including those from getting the permission). */
if ((is_user_manager_loaded && act_user_manager_no_service (self->user_manager)) ||
self->permission_error != NULL)
{
gtk_label_set_label (self->error_title,
_("Failed to load user data from the system"));
@ -200,7 +234,15 @@ update_main_stack (MctApplication *self)
new_page_name = "error";
new_focus_widget = NULL;
}
else if (is_user_manager_loaded)
else if (is_permission_loaded && !has_permission)
{
gtk_lock_button_set_permission (self->lock_button, self->permission);
mct_user_controls_set_permission (self->user_controls, self->permission);
new_page_name = "unlock";
new_focus_widget = GTK_WIDGET (self->lock_button);
}
else if (is_permission_loaded && is_user_manager_loaded)
{
new_page_name = "controls";
new_focus_widget = GTK_WIDGET (self->user_selector);
@ -242,6 +284,45 @@ user_manager_notify_is_loaded_cb (GObject *obj,
update_main_stack (self);
}
static void
permission_new_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
MctApplication *self = MCT_APPLICATION (user_data);
g_autoptr(GPermission) permission = NULL;
g_autoptr(GError) local_error = NULL;
permission = polkit_permission_new_finish (result, &local_error);
if (permission == NULL)
{
g_assert (self->permission_error == NULL);
self->permission_error = g_steal_pointer (&local_error);
g_debug ("Error getting permission: %s", self->permission_error->message);
}
else
{
g_assert (self->permission == NULL);
self->permission = g_steal_pointer (&permission);
g_signal_connect (self->permission, "notify::allowed",
G_CALLBACK (permission_notify_allowed_cb), self);
}
/* Recalculate the UI. */
update_main_stack (self);
}
static void
permission_notify_allowed_cb (GObject *obj,
GParamSpec *pspec,
gpointer user_data)
{
MctApplication *self = MCT_APPLICATION (user_data);
update_main_stack (self);
}
/**
* mct_application_new:
*

View File

@ -48,6 +48,61 @@
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child type="center">
<object class="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="margin">18</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="label" translatable="yes">Permission Required</property>
<attributes>
<attribute name="scale" value="1.4"/>
</attributes>
<child internal-child="accessible">
<object class="AtkObject">
<property name="AtkObject::accessible-role">static</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="label" translatable="yes">Permission is required to view and change parental controls settings for other users.</property>
<property name="wrap">True</property>
<child internal-child="accessible">
<object class="AtkObject">
<property name="AtkObject::accessible-role">static</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLockButton" id="lock_button">
<property name="visible">True</property>
<property name="halign">center</property>
<property name="can-default">True</property>
<property name="has-default">True</property>
<property name="can-focus">True</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">unlock</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>

View File

@ -37,6 +37,7 @@ malcontent_control = executable('malcontent-control',
dependency('gobject-2.0', version: '>= 2.54'),
dependency('gtk+-3.0'),
dependency('flatpak'),
dependency('polkit-gobject-1'),
libmalcontent_dep,
],
include_directories: root_inc,
@ -104,6 +105,24 @@ if xmllint.found()
)
endif
policy_file = i18n.merge_file('policy-file',
input: '@0@.policy.in'.format(application_id),
output: '@0@.policy'.format(application_id),
po_dir: join_paths(meson.source_root(), 'po'),
install: true,
install_dir: join_paths(get_option('datadir'), 'polkit-1', 'actions'),
)
if xmllint.found()
test(
'validate-policy', xmllint,
args: [
'--nonet', '--noblanks', '--noout',
policy_file,
],
suite: ['malcontent-control'],
)
endif
# FIXME: Add icons and tests
#subdir('icons')
#subdir('tests')

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD polkit Policy Configuration 1.0//EN" "http://www.freedesktop.org/software/polkit/policyconfig-1.dtd">
<!-- Copyright © 2020 Endless Mobile, Inc. -->
<policyconfig>
<vendor>The Malcontent Project</vendor>
<vendor_url>https://gitlab.freedesktop.org/pwithnall/malcontent</vendor_url>
<action id="org.freedesktop.MalcontentControl.administration">
<description>Manage parental controls</description>
<message>Authentication is required to read and change user parental controls</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.imply">com.endlessm.ParentalControls.AppFilter.ReadAny com.endlessm.ParentalControls.AppFilter.ChangeAny</annotate>
</action>
</policyconfig>

View File

@ -7,6 +7,7 @@ malcontent-control/gs-content-rating.c
malcontent-control/main.ui
malcontent-control/org.freedesktop.MalcontentControl.appdata.xml.in
malcontent-control/org.freedesktop.MalcontentControl.desktop.in
malcontent-control/org.freedesktop.MalcontentControl.policy.in
malcontent-control/restrict-applications-dialog.c
malcontent-control/restrict-applications-dialog.ui
malcontent-control/restrict-applications-selector.c