Merge branch '1-change-signals' into 'master'

libmalcontent: Add MctManager::app-filter-changed signal

Closes #1

See merge request pwithnall/malcontent!10
This commit is contained in:
Philip Withnall 2019-04-24 12:47:55 +00:00
commit 126190a6a9
8 changed files with 1200 additions and 829 deletions

View File

@ -0,0 +1,63 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright © 2018, 2019 Endless Mobile, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Authors:
* - Philip Withnall <withnall@endlessm.com>
*/
#pragma once
#include <gio/gio.h>
#include <glib.h>
#include <glib-object.h>
#include <libmalcontent/app-filter.h>
G_BEGIN_DECLS
/**
* MctAppFilterListType:
* @MCT_APP_FILTER_LIST_BLACKLIST: Any program in the list is not allowed to
* be run.
* @MCT_APP_FILTER_LIST_WHITELIST: Any program not in the list is not allowed
* to be run.
*
* Different semantics for interpreting an application list.
*
* Since: 0.2.0
*/
typedef enum
{
MCT_APP_FILTER_LIST_BLACKLIST,
MCT_APP_FILTER_LIST_WHITELIST,
} MctAppFilterListType;
struct _MctAppFilter
{
gint ref_count;
uid_t user_id;
gchar **app_list; /* (owned) (array zero-terminated=1) */
MctAppFilterListType app_list_type;
GVariant *oars_ratings; /* (type a{ss}) (owned non-floating) */
gboolean allow_user_installation;
gboolean allow_system_installation;
};
G_END_DECLS

View File

@ -29,39 +29,12 @@
#include <gio/gio.h> #include <gio/gio.h>
#include <libmalcontent/app-filter.h> #include <libmalcontent/app-filter.h>
#include "libmalcontent/app-filter-private.h"
G_DEFINE_QUARK (MctAppFilterError, mct_app_filter_error) G_DEFINE_QUARK (MctAppFilterError, mct_app_filter_error)
/** /* struct _MctAppFilter is defined in app-filter-private.h */
* MctAppFilterListType:
* @MCT_APP_FILTER_LIST_BLACKLIST: Any program in the list is not allowed to
* be run.
* @MCT_APP_FILTER_LIST_WHITELIST: Any program not in the list is not allowed
* to be run.
*
* Different semantics for interpreting an application list.
*
* Since: 0.2.0
*/
typedef enum
{
MCT_APP_FILTER_LIST_BLACKLIST,
MCT_APP_FILTER_LIST_WHITELIST,
} MctAppFilterListType;
struct _MctAppFilter
{
gint ref_count;
uid_t user_id;
gchar **app_list; /* (owned) (array zero-terminated=1) */
MctAppFilterListType app_list_type;
GVariant *oars_ratings; /* (type a{ss}) (owned non-floating) */
gboolean allow_user_installation;
gboolean allow_system_installation;
};
G_DEFINE_BOXED_TYPE (MctAppFilter, mct_app_filter, G_DEFINE_BOXED_TYPE (MctAppFilter, mct_app_filter,
mct_app_filter_ref, mct_app_filter_unref) mct_app_filter_ref, mct_app_filter_unref)
@ -453,639 +426,6 @@ mct_app_filter_is_system_installation_allowed (MctAppFilter *filter)
return filter->allow_system_installation; return filter->allow_system_installation;
} }
/**
* _mct_app_filter_build_app_filter_variant:
* @filter: an #MctAppFilter
*
* Build a #GVariant which contains the app filter from @filter, in the format
* used for storing it in AccountsService.
*
* Returns: (transfer floating): a new, floating #GVariant containing the app
* filter
* Since: 0.2.0
*/
static GVariant *
_mct_app_filter_build_app_filter_variant (MctAppFilter *filter)
{
g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(bas)"));
g_return_val_if_fail (filter != NULL, NULL);
g_return_val_if_fail (filter->ref_count >= 1, NULL);
g_variant_builder_add (&builder, "b",
(filter->app_list_type == MCT_APP_FILTER_LIST_WHITELIST));
g_variant_builder_open (&builder, G_VARIANT_TYPE ("as"));
for (gsize i = 0; filter->app_list[i] != NULL; i++)
g_variant_builder_add (&builder, "s", filter->app_list[i]);
g_variant_builder_close (&builder);
return g_variant_builder_end (&builder);
}
/* Check if @error is a D-Bus remote error matching @expected_error_name. */
static gboolean
bus_remote_error_matches (const GError *error,
const gchar *expected_error_name)
{
g_autofree gchar *error_name = NULL;
if (!g_dbus_error_is_remote_error (error))
return FALSE;
error_name = g_dbus_error_get_remote_error (error);
return g_str_equal (error_name, expected_error_name);
}
/* Convert a #GDBusError into a #MctAppFilter error. */
static GError *
bus_error_to_app_filter_error (const GError *bus_error,
uid_t user_id)
{
if (g_error_matches (bus_error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED) ||
bus_remote_error_matches (bus_error, "org.freedesktop.Accounts.Error.PermissionDenied"))
return g_error_new (MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_PERMISSION_DENIED,
_("Not allowed to query app filter data for user %u"),
(guint) user_id);
else if (g_error_matches (bus_error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD) ||
bus_remote_error_matches (bus_error, "org.freedesktop.Accounts.Error.Failed"))
return g_error_new (MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_INVALID_USER,
_("User %u does not exist"), (guint) user_id);
else
return g_error_copy (bus_error);
}
/* Find the object path for the given @user_id on the accountsservice D-Bus
* interface, by calling its FindUserById() method. This is a synchronous,
* blocking function. */
static gchar *
accounts_find_user_by_id (GDBusConnection *connection,
uid_t user_id,
gboolean allow_interactive_authorization,
GCancellable *cancellable,
GError **error)
{
g_autofree gchar *object_path = NULL;
g_autoptr(GVariant) result_variant = NULL;
g_autoptr(GError) local_error = NULL;
result_variant =
g_dbus_connection_call_sync (connection,
"org.freedesktop.Accounts",
"/org/freedesktop/Accounts",
"org.freedesktop.Accounts",
"FindUserById",
g_variant_new ("(x)", (gint64) user_id),
G_VARIANT_TYPE ("(o)"),
allow_interactive_authorization
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
cancellable,
&local_error);
if (local_error != NULL)
{
g_autoptr(GError) app_filter_error = bus_error_to_app_filter_error (local_error,
user_id);
g_propagate_error (error, g_steal_pointer (&app_filter_error));
return NULL;
}
g_variant_get (result_variant, "(o)", &object_path);
return g_steal_pointer (&object_path);
}
/**
* mct_get_app_filter:
* @connection: (nullable): a #GDBusConnection to the system bus, or %NULL to
* use the default
* @user_id: ID of the user to query, typically coming from getuid()
* @flags: flags to affect the behaviour of the call
* @cancellable: (nullable): a #GCancellable, or %NULL
* @error: return location for a #GError, or %NULL
*
* Synchronous version of mct_get_app_filter_async().
*
* Returns: (transfer full): app filter for the queried user
* Since: 0.3.0
*/
MctAppFilter *
mct_get_app_filter (GDBusConnection *connection,
uid_t user_id,
MctGetAppFilterFlags flags,
GCancellable *cancellable,
GError **error)
{
g_autofree gchar *object_path = NULL;
g_autoptr(GVariant) result_variant = NULL;
g_autoptr(GVariant) properties = NULL;
g_autoptr(GError) local_error = NULL;
g_autoptr(MctAppFilter) app_filter = NULL;
gboolean is_whitelist;
g_auto(GStrv) app_list = NULL;
const gchar *content_rating_kind;
g_autoptr(GVariant) oars_variant = NULL;
gboolean allow_user_installation;
gboolean allow_system_installation;
g_return_val_if_fail (connection == NULL || G_IS_DBUS_CONNECTION (connection), NULL);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
if (connection == NULL)
connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, cancellable, error);
if (connection == NULL)
return NULL;
object_path = accounts_find_user_by_id (connection, user_id,
(flags & MCT_GET_APP_FILTER_FLAGS_INTERACTIVE),
cancellable, error);
if (object_path == NULL)
return NULL;
result_variant =
g_dbus_connection_call_sync (connection,
"org.freedesktop.Accounts",
object_path,
"org.freedesktop.DBus.Properties",
"GetAll",
g_variant_new ("(s)", "com.endlessm.ParentalControls.AppFilter"),
G_VARIANT_TYPE ("(a{sv})"),
(flags & MCT_GET_APP_FILTER_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
cancellable,
&local_error);
if (local_error != NULL)
{
g_autoptr(GError) app_filter_error = NULL;
if (g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS))
{
/* o.fd.D.GetAll() will return InvalidArgs errors if
* accountsservice doesnt have the com.endlessm.ParentalControls.AppFilter
* extension interface installed. */
app_filter_error = g_error_new_literal (MCT_APP_FILTER_ERROR,
MCT_APP_FILTER_ERROR_DISABLED,
_("App filtering is globally disabled"));
}
else
{
app_filter_error = bus_error_to_app_filter_error (local_error,
user_id);
}
g_propagate_error (error, g_steal_pointer (&app_filter_error));
return NULL;
}
/* Extract the properties we care about. They may be silently omitted from the
* results if we dont have permission to access them. */
properties = g_variant_get_child_value (result_variant, 0);
if (!g_variant_lookup (properties, "AppFilter", "(b^as)",
&is_whitelist, &app_list))
{
g_set_error (error, MCT_APP_FILTER_ERROR,
MCT_APP_FILTER_ERROR_PERMISSION_DENIED,
_("Not allowed to query app filter data for user %u"),
(guint) user_id);
return NULL;
}
if (!g_variant_lookup (properties, "OarsFilter", "(&s@a{ss})",
&content_rating_kind, &oars_variant))
{
/* Default value. */
content_rating_kind = "oars-1.1";
oars_variant = g_variant_new ("a{ss}", NULL);
}
/* Check that the OARS filter is in a format we support. Currently, thats
* only oars-1.0 and oars-1.1. */
if (!g_str_equal (content_rating_kind, "oars-1.0") &&
!g_str_equal (content_rating_kind, "oars-1.1"))
{
g_set_error (error, MCT_APP_FILTER_ERROR,
MCT_APP_FILTER_ERROR_INVALID_DATA,
_("OARS filter for user %u has an unrecognized kind %s"),
(guint) user_id, content_rating_kind);
return NULL;
}
if (!g_variant_lookup (properties, "AllowUserInstallation", "b",
&allow_user_installation))
{
/* Default value. */
allow_user_installation = TRUE;
}
if (!g_variant_lookup (properties, "AllowSystemInstallation", "b",
&allow_system_installation))
{
/* Default value. */
allow_system_installation = FALSE;
}
/* Success. Create an #MctAppFilter object to contain the results. */
app_filter = g_new0 (MctAppFilter, 1);
app_filter->ref_count = 1;
app_filter->user_id = user_id;
app_filter->app_list = g_steal_pointer (&app_list);
app_filter->app_list_type =
is_whitelist ? MCT_APP_FILTER_LIST_WHITELIST : MCT_APP_FILTER_LIST_BLACKLIST;
app_filter->oars_ratings = g_steal_pointer (&oars_variant);
app_filter->allow_user_installation = allow_user_installation;
app_filter->allow_system_installation = allow_system_installation;
return g_steal_pointer (&app_filter);
}
static void get_app_filter_thread_cb (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable);
typedef struct
{
GDBusConnection *connection; /* (nullable) (owned) */
uid_t user_id;
MctGetAppFilterFlags flags;
} GetAppFilterData;
static void
get_app_filter_data_free (GetAppFilterData *data)
{
g_clear_object (&data->connection);
g_free (data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GetAppFilterData, get_app_filter_data_free)
/**
* mct_get_app_filter_async:
* @connection: (nullable): a #GDBusConnection to the system bus, or %NULL to
* use the default
* @user_id: ID of the user to query, typically coming from getuid()
* @flags: flags to affect the behaviour of the call
* @cancellable: (nullable): a #GCancellable, or %NULL
* @callback: a #GAsyncReadyCallback
* @user_data: user data to pass to @callback
*
* Asynchronously get a snapshot of the app filter settings for the given
* @user_id.
*
* @connection should be a connection to the system bus, where accounts-service
* runs. Its provided mostly for testing purposes, or to allow an existing
* connection to be re-used. Pass %NULL to use the default connection.
*
* On failure, an #MctAppFilterError, a #GDBusError or a #GIOError will be
* returned.
*
* Since: 0.3.0
*/
void
mct_get_app_filter_async (GDBusConnection *connection,
uid_t user_id,
MctGetAppFilterFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = NULL;
g_autoptr(GetAppFilterData) data = NULL;
g_return_if_fail (connection == NULL || G_IS_DBUS_CONNECTION (connection));
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
task = g_task_new (NULL, cancellable, callback, user_data);
g_task_set_source_tag (task, mct_get_app_filter_async);
data = g_new0 (GetAppFilterData, 1);
data->connection = (connection != NULL) ? g_object_ref (connection) : NULL;
data->user_id = user_id;
data->flags = flags;
g_task_set_task_data (task, g_steal_pointer (&data),
(GDestroyNotify) get_app_filter_data_free);
g_task_run_in_thread (task, get_app_filter_thread_cb);
}
static void
get_app_filter_thread_cb (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
g_autoptr(MctAppFilter) filter = NULL;
GetAppFilterData *data = task_data;
g_autoptr(GError) local_error = NULL;
filter = mct_get_app_filter (data->connection, data->user_id,
data->flags,
cancellable, &local_error);
if (local_error != NULL)
g_task_return_error (task, g_steal_pointer (&local_error));
else
g_task_return_pointer (task, g_steal_pointer (&filter),
(GDestroyNotify) mct_app_filter_unref);
}
/**
* mct_get_app_filter_finish:
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finish an asynchronous operation to get the app filter for a user, started
* with mct_get_app_filter_async().
*
* Returns: (transfer full): app filter for the queried user
* Since: 0.2.0
*/
MctAppFilter *
mct_get_app_filter_finish (GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, NULL), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
/**
* mct_set_app_filter:
* @connection: (nullable): a #GDBusConnection to the system bus, or %NULL to
* use the default
* @user_id: ID of the user to set the filter for, typically coming from getuid()
* @app_filter: (transfer none): the app filter to set for the user
* @flags: flags to affect the behaviour of the call
* @cancellable: (nullable): a #GCancellable, or %NULL
* @error: return location for a #GError, or %NULL
*
* Synchronous version of mct_set_app_filter_async().
*
* Returns: %TRUE on success, %FALSE otherwise
* Since: 0.3.0
*/
gboolean
mct_set_app_filter (GDBusConnection *connection,
uid_t user_id,
MctAppFilter *app_filter,
MctSetAppFilterFlags flags,
GCancellable *cancellable,
GError **error)
{
g_autofree gchar *object_path = NULL;
g_autoptr(GVariant) app_filter_variant = NULL;
g_autoptr(GVariant) oars_filter_variant = NULL;
g_autoptr(GVariant) allow_user_installation_variant = NULL;
g_autoptr(GVariant) allow_system_installation_variant = NULL;
g_autoptr(GVariant) app_filter_result_variant = NULL;
g_autoptr(GVariant) oars_filter_result_variant = NULL;
g_autoptr(GVariant) allow_user_installation_result_variant = NULL;
g_autoptr(GVariant) allow_system_installation_result_variant = NULL;
g_autoptr(GError) local_error = NULL;
g_return_val_if_fail (connection == NULL || G_IS_DBUS_CONNECTION (connection), FALSE);
g_return_val_if_fail (app_filter != NULL, FALSE);
g_return_val_if_fail (app_filter->ref_count >= 1, FALSE);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if (connection == NULL)
connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, cancellable, error);
if (connection == NULL)
return FALSE;
object_path = accounts_find_user_by_id (connection, user_id,
(flags & MCT_SET_APP_FILTER_FLAGS_INTERACTIVE),
cancellable, error);
if (object_path == NULL)
return FALSE;
app_filter_variant = _mct_app_filter_build_app_filter_variant (app_filter);
oars_filter_variant = g_variant_new ("(s@a{ss})", "oars-1.1",
app_filter->oars_ratings);
allow_user_installation_variant = g_variant_new_boolean (app_filter->allow_user_installation);
allow_system_installation_variant = g_variant_new_boolean (app_filter->allow_system_installation);
app_filter_result_variant =
g_dbus_connection_call_sync (connection,
"org.freedesktop.Accounts",
object_path,
"org.freedesktop.DBus.Properties",
"Set",
g_variant_new ("(ssv)",
"com.endlessm.ParentalControls.AppFilter",
"AppFilter",
g_steal_pointer (&app_filter_variant)),
G_VARIANT_TYPE ("()"),
(flags & MCT_SET_APP_FILTER_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
cancellable,
&local_error);
if (local_error != NULL)
{
g_propagate_error (error, bus_error_to_app_filter_error (local_error, user_id));
return FALSE;
}
oars_filter_result_variant =
g_dbus_connection_call_sync (connection,
"org.freedesktop.Accounts",
object_path,
"org.freedesktop.DBus.Properties",
"Set",
g_variant_new ("(ssv)",
"com.endlessm.ParentalControls.AppFilter",
"OarsFilter",
g_steal_pointer (&oars_filter_variant)),
G_VARIANT_TYPE ("()"),
(flags & MCT_SET_APP_FILTER_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
cancellable,
&local_error);
if (local_error != NULL)
{
g_propagate_error (error, bus_error_to_app_filter_error (local_error, user_id));
return FALSE;
}
allow_user_installation_result_variant =
g_dbus_connection_call_sync (connection,
"org.freedesktop.Accounts",
object_path,
"org.freedesktop.DBus.Properties",
"Set",
g_variant_new ("(ssv)",
"com.endlessm.ParentalControls.AppFilter",
"AllowUserInstallation",
g_steal_pointer (&allow_user_installation_variant)),
G_VARIANT_TYPE ("()"),
(flags & MCT_SET_APP_FILTER_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
cancellable,
&local_error);
if (local_error != NULL)
{
g_propagate_error (error, bus_error_to_app_filter_error (local_error, user_id));
return FALSE;
}
allow_system_installation_result_variant =
g_dbus_connection_call_sync (connection,
"org.freedesktop.Accounts",
object_path,
"org.freedesktop.DBus.Properties",
"Set",
g_variant_new ("(ssv)",
"com.endlessm.ParentalControls.AppFilter",
"AllowSystemInstallation",
g_steal_pointer (&allow_system_installation_variant)),
G_VARIANT_TYPE ("()"),
(flags & MCT_SET_APP_FILTER_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
cancellable,
&local_error);
if (local_error != NULL)
{
g_propagate_error (error, bus_error_to_app_filter_error (local_error, user_id));
return FALSE;
}
return TRUE;
}
static void set_app_filter_thread_cb (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable);
typedef struct
{
GDBusConnection *connection; /* (nullable) (owned) */
uid_t user_id;
MctAppFilter *app_filter; /* (owned) */
MctSetAppFilterFlags flags;
} SetAppFilterData;
static void
set_app_filter_data_free (SetAppFilterData *data)
{
g_clear_object (&data->connection);
mct_app_filter_unref (data->app_filter);
g_free (data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SetAppFilterData, set_app_filter_data_free)
/**
* mct_set_app_filter_async:
* @connection: (nullable): a #GDBusConnection to the system bus, or %NULL to
* use the default
* @user_id: ID of the user to set the filter for, typically coming from getuid()
* @app_filter: (transfer none): the app filter to set for the user
* @flags: flags to affect the behaviour of the call
* @cancellable: (nullable): a #GCancellable, or %NULL
* @callback: a #GAsyncReadyCallback
* @user_data: user data to pass to @callback
*
* Asynchronously set the app filter settings for the given @user_id to the
* given @app_filter instance. This will set all fields of the app filter.
*
* @connection should be a connection to the system bus, where accounts-service
* runs. Its provided mostly for testing purposes, or to allow an existing
* connection to be re-used. Pass %NULL to use the default connection.
*
* On failure, an #MctAppFilterError, a #GDBusError or a #GIOError will be
* returned. The users app filter settings will be left in an undefined state.
*
* Since: 0.3.0
*/
void
mct_set_app_filter_async (GDBusConnection *connection,
uid_t user_id,
MctAppFilter *app_filter,
MctSetAppFilterFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = NULL;
g_autoptr(SetAppFilterData) data = NULL;
g_return_if_fail (connection == NULL || G_IS_DBUS_CONNECTION (connection));
g_return_if_fail (app_filter != NULL);
g_return_if_fail (app_filter->ref_count >= 1);
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
task = g_task_new (NULL, cancellable, callback, user_data);
g_task_set_source_tag (task, mct_set_app_filter_async);
data = g_new0 (SetAppFilterData, 1);
data->connection = (connection != NULL) ? g_object_ref (connection) : NULL;
data->user_id = user_id;
data->app_filter = mct_app_filter_ref (app_filter);
data->flags = flags;
g_task_set_task_data (task, g_steal_pointer (&data),
(GDestroyNotify) set_app_filter_data_free);
g_task_run_in_thread (task, set_app_filter_thread_cb);
}
static void
set_app_filter_thread_cb (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
gboolean success;
SetAppFilterData *data = task_data;
g_autoptr(GError) local_error = NULL;
success = mct_set_app_filter (data->connection, data->user_id,
data->app_filter, data->flags,
cancellable, &local_error);
if (local_error != NULL)
g_task_return_error (task, g_steal_pointer (&local_error));
else
g_task_return_boolean (task, success);
}
/**
* mct_set_app_filter_finish:
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finish an asynchronous operation to set the app filter for a user, started
* with mct_set_app_filter_async().
*
* Returns: %TRUE on success, %FALSE otherwise
* Since: 0.2.0
*/
gboolean
mct_set_app_filter_finish (GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, NULL), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
/* /*
* Actual implementation of #MctAppFilterBuilder. * Actual implementation of #MctAppFilterBuilder.
* *

View File

@ -37,7 +37,8 @@ G_BEGIN_DECLS
* a user is inconsistent or invalid * a user is inconsistent or invalid
* @MCT_APP_FILTER_ERROR_DISABLED: App filtering is disabled for all users (Since: 0.3.0) * @MCT_APP_FILTER_ERROR_DISABLED: App filtering is disabled for all users (Since: 0.3.0)
* *
* Errors which can be returned by mct_get_app_filter_async(). * Errors relating to #MctAppFilter instances, which can be returned by
* mct_manager_get_app_filter_async() (for example).
* *
* Since: 0.2.0 * Since: 0.2.0
*/ */
@ -78,40 +79,6 @@ typedef enum
MCT_APP_FILTER_OARS_VALUE_INTENSE, MCT_APP_FILTER_OARS_VALUE_INTENSE,
} MctAppFilterOarsValue; } MctAppFilterOarsValue;
/**
* MctGetAppFilterFlags:
* @MCT_GET_APP_FILTER_FLAGS_NONE: No flags set.
* @MCT_GET_APP_FILTER_FLAGS_INTERACTIVE: Allow interactive polkit dialogs when
* requesting authorization.
*
* Flags to control the behaviour of mct_get_app_filter() and
* mct_get_app_filter_async().
*
* Since: 0.3.0
*/
typedef enum
{
MCT_GET_APP_FILTER_FLAGS_NONE = 0,
MCT_GET_APP_FILTER_FLAGS_INTERACTIVE,
} MctGetAppFilterFlags;
/**
* MctSetAppFilterFlags:
* @MCT_SET_APP_FILTER_FLAGS_NONE: No flags set.
* @MCT_SET_APP_FILTER_FLAGS_INTERACTIVE: Allow interactive polkit dialogs when
* requesting authorization.
*
* Flags to control the behaviour of mct_set_app_filter() and
* mct_set_app_filter_async().
*
* Since: 0.3.0
*/
typedef enum
{
MCT_SET_APP_FILTER_FLAGS_NONE = 0,
MCT_SET_APP_FILTER_FLAGS_INTERACTIVE,
} MctSetAppFilterFlags;
/** /**
* MctAppFilter: * MctAppFilter:
* *
@ -150,36 +117,6 @@ MctAppFilterOarsValue mct_app_filter_get_oars_value (MctAppFilter *filter,
gboolean mct_app_filter_is_user_installation_allowed (MctAppFilter *filter); gboolean mct_app_filter_is_user_installation_allowed (MctAppFilter *filter);
gboolean mct_app_filter_is_system_installation_allowed (MctAppFilter *filter); gboolean mct_app_filter_is_system_installation_allowed (MctAppFilter *filter);
MctAppFilter *mct_get_app_filter (GDBusConnection *connection,
uid_t user_id,
MctGetAppFilterFlags flags,
GCancellable *cancellable,
GError **error);
void mct_get_app_filter_async (GDBusConnection *connection,
uid_t user_id,
MctGetAppFilterFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
MctAppFilter *mct_get_app_filter_finish (GAsyncResult *result,
GError **error);
gboolean mct_set_app_filter (GDBusConnection *connection,
uid_t user_id,
MctAppFilter *app_filter,
MctSetAppFilterFlags flags,
GCancellable *cancellable,
GError **error);
void mct_set_app_filter_async (GDBusConnection *connection,
uid_t user_id,
MctAppFilter *app_filter,
MctSetAppFilterFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean mct_set_app_filter_finish (GAsyncResult *result,
GError **error);
/** /**
* MctAppFilterBuilder: * MctAppFilterBuilder:
* *

865
libmalcontent/manager.c Normal file
View File

@ -0,0 +1,865 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright © 2019 Endless Mobile, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Authors:
* - Philip Withnall <withnall@endlessm.com>
*/
#include "config.h"
#include <glib.h>
#include <glib-object.h>
#include <glib/gi18n-lib.h>
#include <gio/gio.h>
#include <libmalcontent/app-filter.h>
#include <libmalcontent/manager.h>
#include "libmalcontent/app-filter-private.h"
/**
* MctManager:
*
* #MctManager is a top-level management object which is used to query and
* monitor #MctAppFilters for different users.
*
* Since: 0.3.0
*/
struct _MctManager
{
GObject parent_instance;
GDBusConnection *connection; /* (owned) */
guint user_changed_id;
};
G_DEFINE_TYPE (MctManager, mct_manager, G_TYPE_OBJECT)
typedef enum
{
PROP_CONNECTION = 1,
} MctManagerProperty;
static GParamSpec *props[PROP_CONNECTION + 1] = { NULL, };
static void
mct_manager_init (MctManager *self)
{
/* Nothing to do here. */
}
static void
mct_manager_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *spec)
{
MctManager *self = MCT_MANAGER (object);
switch ((MctManagerProperty) property_id)
{
case PROP_CONNECTION:
g_value_set_object (value, self->connection);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
break;
}
}
static void
mct_manager_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *spec)
{
MctManager *self = MCT_MANAGER (object);
switch ((MctManagerProperty) property_id)
{
case PROP_CONNECTION:
/* Construct-only. May be %NULL. */
g_assert (self->connection == NULL);
self->connection = g_value_dup_object (value);
g_assert (self->connection != NULL);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
break;
}
}
static void _mct_manager_user_changed_cb (GDBusConnection *connection,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data);
static void
mct_manager_constructed (GObject *object)
{
MctManager *self = MCT_MANAGER (object);
/* Chain up. */
G_OBJECT_CLASS (mct_manager_parent_class)->constructed (object);
/* Connect to notifications from AccountsService. */
g_assert (self->connection != NULL);
self->user_changed_id =
g_dbus_connection_signal_subscribe (self->connection,
"org.freedesktop.Accounts", /* sender */
"org.freedesktop.Accounts.User", /* interface name */
"Changed", /* signal name */
NULL, /* object path */
NULL, /* arg0 */
G_DBUS_SIGNAL_FLAGS_NONE,
_mct_manager_user_changed_cb,
self, NULL);
}
static void
mct_manager_dispose (GObject *object)
{
MctManager *self = MCT_MANAGER (object);
if (self->user_changed_id != 0 && self->connection != NULL)
{
g_dbus_connection_signal_unsubscribe (self->connection,
self->user_changed_id);
self->user_changed_id = 0;
}
g_clear_object (&self->connection);
G_OBJECT_CLASS (mct_manager_parent_class)->dispose (object);
}
static void
mct_manager_class_init (MctManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = mct_manager_constructed;
object_class->dispose = mct_manager_dispose;
object_class->get_property = mct_manager_get_property;
object_class->set_property = mct_manager_set_property;
/**
* MctManager:connection:
*
* A connection to the system bus, where accounts-service runs. Its provided
* mostly for testing purposes, or to allow an existing connection to be
* re-used.
*
* Since: 0.3.0
*/
props[PROP_CONNECTION] = g_param_spec_object ("connection",
"D-Bus Connection",
"A connection to the system bus.",
G_TYPE_DBUS_CONNECTION,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class,
G_N_ELEMENTS (props),
props);
/**
* MctManager::app-filter-changed:
* @self: a #MctManager
* @user_id: UID of the user whose app filter has changed
*
* Emitted when the app filter stored for a user changes.
*
* Since: 0.3.0
*/
g_signal_new ("app-filter-changed", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
G_TYPE_NONE, 1,
G_TYPE_UINT64);
}
/**
* mct_manager_new:
* @connection: (transfer none): a #GDBusConnection to use
*
* Create a new #MctManager.
*
* Returns: (transfer full): a new #MctManager
* Since: 0.3.0
*/
MctManager *
mct_manager_new (GDBusConnection *connection)
{
g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
return g_object_new (MCT_TYPE_MANAGER,
"connection", connection,
NULL);
}
static void
_mct_manager_user_changed_cb (GDBusConnection *connection,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
MctManager *manager = MCT_MANAGER (user_data);
g_autoptr(GError) local_error = NULL;
const gchar *uid_str;
guint64 uid;
g_assert (g_str_equal (interface_name, "org.freedesktop.Accounts.User"));
g_assert (g_str_equal (signal_name, "Changed"));
/* Extract the UID from the object path. This is a bit hacky, but probably
* better than depending on libaccountsservice just for this. */
if (!g_str_has_prefix (object_path, "/org/freedesktop/Accounts/User"))
return;
uid_str = object_path + strlen ("/org/freedesktop/Accounts/User");
if (!g_ascii_string_to_unsigned (uid_str, 10, 0, G_MAXUINT64, &uid, &local_error))
{
g_warning ("Error converting object path %s to user ID: %s",
object_path, local_error->message);
g_clear_error (&local_error);
}
g_signal_emit_by_name (manager, "app-filter-changed", uid);
}
/**
* _mct_app_filter_build_app_filter_variant:
* @filter: an #MctAppFilter
*
* Build a #GVariant which contains the app filter from @filter, in the format
* used for storing it in AccountsService.
*
* Returns: (transfer floating): a new, floating #GVariant containing the app
* filter
* Since: 0.2.0
*/
static GVariant *
_mct_app_filter_build_app_filter_variant (MctAppFilter *filter)
{
g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(bas)"));
g_return_val_if_fail (filter != NULL, NULL);
g_return_val_if_fail (filter->ref_count >= 1, NULL);
g_variant_builder_add (&builder, "b",
(filter->app_list_type == MCT_APP_FILTER_LIST_WHITELIST));
g_variant_builder_open (&builder, G_VARIANT_TYPE ("as"));
for (gsize i = 0; filter->app_list[i] != NULL; i++)
g_variant_builder_add (&builder, "s", filter->app_list[i]);
g_variant_builder_close (&builder);
return g_variant_builder_end (&builder);
}
/* Check if @error is a D-Bus remote error matching @expected_error_name. */
static gboolean
bus_remote_error_matches (const GError *error,
const gchar *expected_error_name)
{
g_autofree gchar *error_name = NULL;
if (!g_dbus_error_is_remote_error (error))
return FALSE;
error_name = g_dbus_error_get_remote_error (error);
return g_str_equal (error_name, expected_error_name);
}
/* Convert a #GDBusError into a #MctAppFilter error. */
static GError *
bus_error_to_app_filter_error (const GError *bus_error,
uid_t user_id)
{
if (g_error_matches (bus_error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED) ||
bus_remote_error_matches (bus_error, "org.freedesktop.Accounts.Error.PermissionDenied"))
return g_error_new (MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_PERMISSION_DENIED,
_("Not allowed to query app filter data for user %u"),
(guint) user_id);
else if (g_error_matches (bus_error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD) ||
bus_remote_error_matches (bus_error, "org.freedesktop.Accounts.Error.Failed"))
return g_error_new (MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_INVALID_USER,
_("User %u does not exist"), (guint) user_id);
else
return g_error_copy (bus_error);
}
/* Find the object path for the given @user_id on the accountsservice D-Bus
* interface, by calling its FindUserById() method. This is a synchronous,
* blocking function. */
static gchar *
accounts_find_user_by_id (GDBusConnection *connection,
uid_t user_id,
gboolean allow_interactive_authorization,
GCancellable *cancellable,
GError **error)
{
g_autofree gchar *object_path = NULL;
g_autoptr(GVariant) result_variant = NULL;
g_autoptr(GError) local_error = NULL;
result_variant =
g_dbus_connection_call_sync (connection,
"org.freedesktop.Accounts",
"/org/freedesktop/Accounts",
"org.freedesktop.Accounts",
"FindUserById",
g_variant_new ("(x)", (gint64) user_id),
G_VARIANT_TYPE ("(o)"),
allow_interactive_authorization
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
cancellable,
&local_error);
if (local_error != NULL)
{
g_autoptr(GError) app_filter_error = bus_error_to_app_filter_error (local_error,
user_id);
g_propagate_error (error, g_steal_pointer (&app_filter_error));
return NULL;
}
g_variant_get (result_variant, "(o)", &object_path);
return g_steal_pointer (&object_path);
}
/**
* mct_manager_get_app_filter:
* @self: a #MctManager
* @user_id: ID of the user to query, typically coming from getuid()
* @flags: flags to affect the behaviour of the call
* @cancellable: (nullable): a #GCancellable, or %NULL
* @error: return location for a #GError, or %NULL
*
* Synchronous version of mct_manager_get_app_filter_async().
*
* Returns: (transfer full): app filter for the queried user
* Since: 0.3.0
*/
MctAppFilter *
mct_manager_get_app_filter (MctManager *self,
uid_t user_id,
MctGetAppFilterFlags flags,
GCancellable *cancellable,
GError **error)
{
g_autofree gchar *object_path = NULL;
g_autoptr(GVariant) result_variant = NULL;
g_autoptr(GVariant) properties = NULL;
g_autoptr(GError) local_error = NULL;
g_autoptr(MctAppFilter) app_filter = NULL;
gboolean is_whitelist;
g_auto(GStrv) app_list = NULL;
const gchar *content_rating_kind;
g_autoptr(GVariant) oars_variant = NULL;
gboolean allow_user_installation;
gboolean allow_system_installation;
g_return_val_if_fail (MCT_IS_MANAGER (self), NULL);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
object_path = accounts_find_user_by_id (self->connection, user_id,
(flags & MCT_GET_APP_FILTER_FLAGS_INTERACTIVE),
cancellable, error);
if (object_path == NULL)
return NULL;
result_variant =
g_dbus_connection_call_sync (self->connection,
"org.freedesktop.Accounts",
object_path,
"org.freedesktop.DBus.Properties",
"GetAll",
g_variant_new ("(s)", "com.endlessm.ParentalControls.AppFilter"),
G_VARIANT_TYPE ("(a{sv})"),
(flags & MCT_GET_APP_FILTER_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
cancellable,
&local_error);
if (local_error != NULL)
{
g_autoptr(GError) app_filter_error = NULL;
if (g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS))
{
/* o.fd.D.GetAll() will return InvalidArgs errors if
* accountsservice doesnt have the com.endlessm.ParentalControls.AppFilter
* extension interface installed. */
app_filter_error = g_error_new_literal (MCT_APP_FILTER_ERROR,
MCT_APP_FILTER_ERROR_DISABLED,
_("App filtering is globally disabled"));
}
else
{
app_filter_error = bus_error_to_app_filter_error (local_error,
user_id);
}
g_propagate_error (error, g_steal_pointer (&app_filter_error));
return NULL;
}
/* Extract the properties we care about. They may be silently omitted from the
* results if we dont have permission to access them. */
properties = g_variant_get_child_value (result_variant, 0);
if (!g_variant_lookup (properties, "AppFilter", "(b^as)",
&is_whitelist, &app_list))
{
g_set_error (error, MCT_APP_FILTER_ERROR,
MCT_APP_FILTER_ERROR_PERMISSION_DENIED,
_("Not allowed to query app filter data for user %u"),
(guint) user_id);
return NULL;
}
if (!g_variant_lookup (properties, "OarsFilter", "(&s@a{ss})",
&content_rating_kind, &oars_variant))
{
/* Default value. */
content_rating_kind = "oars-1.1";
oars_variant = g_variant_new ("a{ss}", NULL);
}
/* Check that the OARS filter is in a format we support. Currently, thats
* only oars-1.0 and oars-1.1. */
if (!g_str_equal (content_rating_kind, "oars-1.0") &&
!g_str_equal (content_rating_kind, "oars-1.1"))
{
g_set_error (error, MCT_APP_FILTER_ERROR,
MCT_APP_FILTER_ERROR_INVALID_DATA,
_("OARS filter for user %u has an unrecognized kind %s"),
(guint) user_id, content_rating_kind);
return NULL;
}
if (!g_variant_lookup (properties, "AllowUserInstallation", "b",
&allow_user_installation))
{
/* Default value. */
allow_user_installation = TRUE;
}
if (!g_variant_lookup (properties, "AllowSystemInstallation", "b",
&allow_system_installation))
{
/* Default value. */
allow_system_installation = FALSE;
}
/* Success. Create an #MctAppFilter object to contain the results. */
app_filter = g_new0 (MctAppFilter, 1);
app_filter->ref_count = 1;
app_filter->user_id = user_id;
app_filter->app_list = g_steal_pointer (&app_list);
app_filter->app_list_type =
is_whitelist ? MCT_APP_FILTER_LIST_WHITELIST : MCT_APP_FILTER_LIST_BLACKLIST;
app_filter->oars_ratings = g_steal_pointer (&oars_variant);
app_filter->allow_user_installation = allow_user_installation;
app_filter->allow_system_installation = allow_system_installation;
return g_steal_pointer (&app_filter);
}
static void get_app_filter_thread_cb (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable);
typedef struct
{
uid_t user_id;
MctGetAppFilterFlags flags;
} GetAppFilterData;
static void
get_app_filter_data_free (GetAppFilterData *data)
{
g_free (data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GetAppFilterData, get_app_filter_data_free)
/**
* mct_manager_get_app_filter_async:
* @self: a #MctManager
* @user_id: ID of the user to query, typically coming from getuid()
* @flags: flags to affect the behaviour of the call
* @cancellable: (nullable): a #GCancellable, or %NULL
* @callback: a #GAsyncReadyCallback
* @user_data: user data to pass to @callback
*
* Asynchronously get a snapshot of the app filter settings for the given
* @user_id.
*
* On failure, an #MctAppFilterError, a #GDBusError or a #GIOError will be
* returned.
*
* Since: 0.3.0
*/
void
mct_manager_get_app_filter_async (MctManager *self,
uid_t user_id,
MctGetAppFilterFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = NULL;
g_autoptr(GetAppFilterData) data = NULL;
g_return_if_fail (MCT_IS_MANAGER (self));
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, mct_manager_get_app_filter_async);
data = g_new0 (GetAppFilterData, 1);
data->user_id = user_id;
data->flags = flags;
g_task_set_task_data (task, g_steal_pointer (&data),
(GDestroyNotify) get_app_filter_data_free);
g_task_run_in_thread (task, get_app_filter_thread_cb);
}
static void
get_app_filter_thread_cb (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
g_autoptr(MctAppFilter) filter = NULL;
MctManager *manager = MCT_MANAGER (source_object);
GetAppFilterData *data = task_data;
g_autoptr(GError) local_error = NULL;
filter = mct_manager_get_app_filter (manager, data->user_id,
data->flags,
cancellable, &local_error);
if (local_error != NULL)
g_task_return_error (task, g_steal_pointer (&local_error));
else
g_task_return_pointer (task, g_steal_pointer (&filter),
(GDestroyNotify) mct_app_filter_unref);
}
/**
* mct_manager_get_app_filter_finish:
* @self: a #MctManager
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finish an asynchronous operation to get the app filter for a user, started
* with mct_manager_get_app_filter_async().
*
* Returns: (transfer full): app filter for the queried user
* Since: 0.3.0
*/
MctAppFilter *
mct_manager_get_app_filter_finish (MctManager *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (MCT_IS_MANAGER (self), NULL);
g_return_val_if_fail (g_task_is_valid (result, self), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
/**
* mct_manager_set_app_filter:
* @self: a #MctManager
* @user_id: ID of the user to set the filter for, typically coming from getuid()
* @app_filter: (transfer none): the app filter to set for the user
* @flags: flags to affect the behaviour of the call
* @cancellable: (nullable): a #GCancellable, or %NULL
* @error: return location for a #GError, or %NULL
*
* Synchronous version of mct_manager_set_app_filter_async().
*
* Returns: %TRUE on success, %FALSE otherwise
* Since: 0.3.0
*/
gboolean
mct_manager_set_app_filter (MctManager *self,
uid_t user_id,
MctAppFilter *app_filter,
MctSetAppFilterFlags flags,
GCancellable *cancellable,
GError **error)
{
g_autofree gchar *object_path = NULL;
g_autoptr(GVariant) app_filter_variant = NULL;
g_autoptr(GVariant) oars_filter_variant = NULL;
g_autoptr(GVariant) allow_user_installation_variant = NULL;
g_autoptr(GVariant) allow_system_installation_variant = NULL;
g_autoptr(GVariant) app_filter_result_variant = NULL;
g_autoptr(GVariant) oars_filter_result_variant = NULL;
g_autoptr(GVariant) allow_user_installation_result_variant = NULL;
g_autoptr(GVariant) allow_system_installation_result_variant = NULL;
g_autoptr(GError) local_error = NULL;
g_autoptr(GDBusConnection) connection = NULL;
g_return_val_if_fail (MCT_IS_MANAGER (self), FALSE);
g_return_val_if_fail (app_filter != NULL, FALSE);
g_return_val_if_fail (app_filter->ref_count >= 1, FALSE);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
object_path = accounts_find_user_by_id (self->connection, user_id,
(flags & MCT_SET_APP_FILTER_FLAGS_INTERACTIVE),
cancellable, error);
if (object_path == NULL)
return FALSE;
app_filter_variant = _mct_app_filter_build_app_filter_variant (app_filter);
oars_filter_variant = g_variant_new ("(s@a{ss})", "oars-1.1",
app_filter->oars_ratings);
allow_user_installation_variant = g_variant_new_boolean (app_filter->allow_user_installation);
allow_system_installation_variant = g_variant_new_boolean (app_filter->allow_system_installation);
app_filter_result_variant =
g_dbus_connection_call_sync (self->connection,
"org.freedesktop.Accounts",
object_path,
"org.freedesktop.DBus.Properties",
"Set",
g_variant_new ("(ssv)",
"com.endlessm.ParentalControls.AppFilter",
"AppFilter",
g_steal_pointer (&app_filter_variant)),
G_VARIANT_TYPE ("()"),
(flags & MCT_SET_APP_FILTER_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
cancellable,
&local_error);
if (local_error != NULL)
{
g_propagate_error (error, bus_error_to_app_filter_error (local_error, user_id));
return FALSE;
}
oars_filter_result_variant =
g_dbus_connection_call_sync (self->connection,
"org.freedesktop.Accounts",
object_path,
"org.freedesktop.DBus.Properties",
"Set",
g_variant_new ("(ssv)",
"com.endlessm.ParentalControls.AppFilter",
"OarsFilter",
g_steal_pointer (&oars_filter_variant)),
G_VARIANT_TYPE ("()"),
(flags & MCT_SET_APP_FILTER_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
cancellable,
&local_error);
if (local_error != NULL)
{
g_propagate_error (error, bus_error_to_app_filter_error (local_error, user_id));
return FALSE;
}
allow_user_installation_result_variant =
g_dbus_connection_call_sync (self->connection,
"org.freedesktop.Accounts",
object_path,
"org.freedesktop.DBus.Properties",
"Set",
g_variant_new ("(ssv)",
"com.endlessm.ParentalControls.AppFilter",
"AllowUserInstallation",
g_steal_pointer (&allow_user_installation_variant)),
G_VARIANT_TYPE ("()"),
(flags & MCT_SET_APP_FILTER_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
cancellable,
&local_error);
if (local_error != NULL)
{
g_propagate_error (error, bus_error_to_app_filter_error (local_error, user_id));
return FALSE;
}
allow_system_installation_result_variant =
g_dbus_connection_call_sync (self->connection,
"org.freedesktop.Accounts",
object_path,
"org.freedesktop.DBus.Properties",
"Set",
g_variant_new ("(ssv)",
"com.endlessm.ParentalControls.AppFilter",
"AllowSystemInstallation",
g_steal_pointer (&allow_system_installation_variant)),
G_VARIANT_TYPE ("()"),
(flags & MCT_SET_APP_FILTER_FLAGS_INTERACTIVE)
? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION
: G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout, ms */
cancellable,
&local_error);
if (local_error != NULL)
{
g_propagate_error (error, bus_error_to_app_filter_error (local_error, user_id));
return FALSE;
}
return TRUE;
}
static void set_app_filter_thread_cb (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable);
typedef struct
{
uid_t user_id;
MctAppFilter *app_filter; /* (owned) */
MctSetAppFilterFlags flags;
} SetAppFilterData;
static void
set_app_filter_data_free (SetAppFilterData *data)
{
mct_app_filter_unref (data->app_filter);
g_free (data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SetAppFilterData, set_app_filter_data_free)
/**
* mct_manager_set_app_filter_async:
* @self: a #MctManager
* @user_id: ID of the user to set the filter for, typically coming from getuid()
* @app_filter: (transfer none): the app filter to set for the user
* @flags: flags to affect the behaviour of the call
* @cancellable: (nullable): a #GCancellable, or %NULL
* @callback: a #GAsyncReadyCallback
* @user_data: user data to pass to @callback
*
* Asynchronously set the app filter settings for the given @user_id to the
* given @app_filter instance. This will set all fields of the app filter.
*
* On failure, an #MctAppFilterError, a #GDBusError or a #GIOError will be
* returned. The users app filter settings will be left in an undefined state.
*
* Since: 0.3.0
*/
void
mct_manager_set_app_filter_async (MctManager *self,
uid_t user_id,
MctAppFilter *app_filter,
MctSetAppFilterFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = NULL;
g_autoptr(SetAppFilterData) data = NULL;
g_return_if_fail (MCT_IS_MANAGER (self));
g_return_if_fail (app_filter != NULL);
g_return_if_fail (app_filter->ref_count >= 1);
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, mct_manager_set_app_filter_async);
data = g_new0 (SetAppFilterData, 1);
data->user_id = user_id;
data->app_filter = mct_app_filter_ref (app_filter);
data->flags = flags;
g_task_set_task_data (task, g_steal_pointer (&data),
(GDestroyNotify) set_app_filter_data_free);
g_task_run_in_thread (task, set_app_filter_thread_cb);
}
static void
set_app_filter_thread_cb (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
gboolean success;
MctManager *manager = MCT_MANAGER (source_object);
SetAppFilterData *data = task_data;
g_autoptr(GError) local_error = NULL;
success = mct_manager_set_app_filter (manager, data->user_id,
data->app_filter, data->flags,
cancellable, &local_error);
if (local_error != NULL)
g_task_return_error (task, g_steal_pointer (&local_error));
else
g_task_return_boolean (task, success);
}
/**
* mct_manager_set_app_filter_finish:
* @self: a #MctManager
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finish an asynchronous operation to set the app filter for a user, started
* with mct_manager_set_app_filter_async().
*
* Returns: %TRUE on success, %FALSE otherwise
* Since: 0.3.0
*/
gboolean
mct_manager_set_app_filter_finish (MctManager *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (MCT_IS_MANAGER (self), FALSE);
g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}

103
libmalcontent/manager.h Normal file
View File

@ -0,0 +1,103 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright © 2019 Endless Mobile, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Authors:
* - Philip Withnall <withnall@endlessm.com>
*/
#pragma once
#include <gio/gio.h>
#include <glib.h>
#include <glib-object.h>
#include <libmalcontent/app-filter.h>
G_BEGIN_DECLS
/**
* MctGetAppFilterFlags:
* @MCT_GET_APP_FILTER_FLAGS_NONE: No flags set.
* @MCT_GET_APP_FILTER_FLAGS_INTERACTIVE: Allow interactive polkit dialogs when
* requesting authorization.
*
* Flags to control the behaviour of mct_manager_get_app_filter() and
* mct_manager_get_app_filter_async().
*
* Since: 0.3.0
*/
typedef enum
{
MCT_GET_APP_FILTER_FLAGS_NONE = 0,
MCT_GET_APP_FILTER_FLAGS_INTERACTIVE,
} MctGetAppFilterFlags;
/**
* MctSetAppFilterFlags:
* @MCT_SET_APP_FILTER_FLAGS_NONE: No flags set.
* @MCT_SET_APP_FILTER_FLAGS_INTERACTIVE: Allow interactive polkit dialogs when
* requesting authorization.
*
* Flags to control the behaviour of mct_manager_set_app_filter() and
* mct_manager_set_app_filter_async().
*
* Since: 0.3.0
*/
typedef enum
{
MCT_SET_APP_FILTER_FLAGS_NONE = 0,
MCT_SET_APP_FILTER_FLAGS_INTERACTIVE,
} MctSetAppFilterFlags;
#define MCT_TYPE_MANAGER mct_manager_get_type ()
G_DECLARE_FINAL_TYPE (MctManager, mct_manager, MCT, MANAGER, GObject)
MctManager *mct_manager_new (GDBusConnection *connection);
MctAppFilter *mct_manager_get_app_filter (MctManager *self,
uid_t user_id,
MctGetAppFilterFlags flags,
GCancellable *cancellable,
GError **error);
void mct_manager_get_app_filter_async (MctManager *self,
uid_t user_id,
MctGetAppFilterFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
MctAppFilter *mct_manager_get_app_filter_finish (MctManager *self,
GAsyncResult *result,
GError **error);
gboolean mct_manager_set_app_filter (MctManager *self,
uid_t user_id,
MctAppFilter *app_filter,
MctSetAppFilterFlags flags,
GCancellable *cancellable,
GError **error);
void mct_manager_set_app_filter_async (MctManager *self,
uid_t user_id,
MctAppFilter *app_filter,
MctSetAppFilterFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean mct_manager_set_app_filter_finish (MctManager *self,
GAsyncResult *result,
GError **error);
G_END_DECLS

View File

@ -2,9 +2,14 @@ libmalcontent_api_version = '0'
libmalcontent_api_name = 'malcontent-' + libmalcontent_api_version libmalcontent_api_name = 'malcontent-' + libmalcontent_api_version
libmalcontent_sources = [ libmalcontent_sources = [
'app-filter.c', 'app-filter.c',
'manager.c',
] ]
libmalcontent_headers = [ libmalcontent_headers = [
'app-filter.h', 'app-filter.h',
'manager.h',
]
libmalcontent_private_headers = [
'app-filter-private.h',
] ]
libmalcontent_public_deps = [ libmalcontent_public_deps = [
@ -20,7 +25,7 @@ libmalcontent_private_deps = [
libmalcontent_include_subdir = join_paths(libmalcontent_api_name, 'libmalcontent') libmalcontent_include_subdir = join_paths(libmalcontent_api_name, 'libmalcontent')
libmalcontent = library(libmalcontent_api_name, libmalcontent = library(libmalcontent_api_name,
libmalcontent_sources + libmalcontent_headers, libmalcontent_sources + libmalcontent_headers + libmalcontent_private_headers,
dependencies: libmalcontent_public_deps + libmalcontent_private_deps, dependencies: libmalcontent_public_deps + libmalcontent_private_deps,
include_directories: root_inc, include_directories: root_inc,
install: true, install: true,
@ -49,7 +54,7 @@ pkgconfig.generate(
) )
gnome.generate_gir(libmalcontent, gnome.generate_gir(libmalcontent,
sources: libmalcontent_sources + libmalcontent_headers, sources: libmalcontent_sources + libmalcontent_headers + libmalcontent_private_headers,
nsversion: libmalcontent_api_version, nsversion: libmalcontent_api_version,
namespace: 'Malcontent', namespace: 'Malcontent',
symbol_prefix: 'mct_', symbol_prefix: 'mct_',
@ -57,6 +62,7 @@ gnome.generate_gir(libmalcontent,
export_packages: 'libmalcontent', export_packages: 'libmalcontent',
includes: ['GObject-2.0', 'Gio-2.0'], includes: ['GObject-2.0', 'Gio-2.0'],
install: true, install: true,
dependencies: libmalcontent_dep,
) )
subdir('tests') subdir('tests')

View File

@ -26,6 +26,7 @@
#include <gio/gdesktopappinfo.h> #include <gio/gdesktopappinfo.h>
#include <gio/gio.h> #include <gio/gio.h>
#include <libmalcontent/app-filter.h> #include <libmalcontent/app-filter.h>
#include <libmalcontent/manager.h>
#include <libglib-testing/dbus-queue.h> #include <libglib-testing/dbus-queue.h>
#include <locale.h> #include <locale.h>
#include <string.h> #include <string.h>
@ -422,6 +423,7 @@ typedef struct
GtDBusQueue *queue; /* (owned) */ GtDBusQueue *queue; /* (owned) */
uid_t valid_uid; uid_t valid_uid;
uid_t missing_uid; uid_t missing_uid;
MctManager *manager; /* (owned) */
} BusFixture; } BusFixture;
static void static void
@ -452,12 +454,15 @@ bus_set_up (BusFixture *fixture,
(GDBusInterfaceInfo *) &accounts_interface_info, (GDBusInterfaceInfo *) &accounts_interface_info,
&local_error); &local_error);
g_assert_no_error (local_error); g_assert_no_error (local_error);
fixture->manager = mct_manager_new (gt_dbus_queue_get_client_connection (fixture->queue));
} }
static void static void
bus_tear_down (BusFixture *fixture, bus_tear_down (BusFixture *fixture,
gconstpointer test_data) gconstpointer test_data)
{ {
g_clear_object (&fixture->manager);
gt_dbus_queue_disconnect (fixture->queue, TRUE); gt_dbus_queue_disconnect (fixture->queue, TRUE);
g_clear_pointer (&fixture->queue, gt_dbus_queue_free); g_clear_pointer (&fixture->queue, gt_dbus_queue_free);
} }
@ -477,7 +482,7 @@ async_result_cb (GObject *obj,
/* Generic mock accountsservice implementation which returns the properties /* Generic mock accountsservice implementation which returns the properties
* given in #GetAppFilterData.properties if queried for a UID matching * given in #GetAppFilterData.properties if queried for a UID matching
* #GetAppFilterData.expected_uid. Intended to be used for writing successful * #GetAppFilterData.expected_uid. Intended to be used for writing successful
* mct_get_app_filter() tests returning a variety of values. */ * mct_manager_get_app_filter() tests returning a variety of values. */
typedef struct typedef struct
{ {
uid_t expected_uid; uid_t expected_uid;
@ -553,21 +558,21 @@ test_app_filter_bus_get (BusFixture *fixture,
{ {
g_autoptr(GAsyncResult) result = NULL; g_autoptr(GAsyncResult) result = NULL;
mct_get_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue), mct_manager_get_app_filter_async (fixture->manager,
fixture->valid_uid, fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL, MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
async_result_cb, &result); async_result_cb, &result);
while (result == NULL) while (result == NULL)
g_main_context_iteration (NULL, TRUE); g_main_context_iteration (NULL, TRUE);
app_filter = mct_get_app_filter_finish (result, &local_error); app_filter = mct_manager_get_app_filter_finish (fixture->manager, result, &local_error);
} }
else else
{ {
app_filter = mct_get_app_filter (gt_dbus_queue_get_client_connection (fixture->queue), app_filter = mct_manager_get_app_filter (fixture->manager,
fixture->valid_uid, fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL, MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
&local_error); &local_error);
} }
g_assert_no_error (local_error); g_assert_no_error (local_error);
@ -608,10 +613,10 @@ test_app_filter_bus_get_whitelist (BusFixture *fixture,
gt_dbus_queue_set_server_func (fixture->queue, get_app_filter_server_cb, gt_dbus_queue_set_server_func (fixture->queue, get_app_filter_server_cb,
(gpointer) &get_app_filter_data); (gpointer) &get_app_filter_data);
app_filter = mct_get_app_filter (gt_dbus_queue_get_client_connection (fixture->queue), app_filter = mct_manager_get_app_filter (fixture->manager,
fixture->valid_uid, fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL, MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
&local_error); &local_error);
g_assert_no_error (local_error); g_assert_no_error (local_error);
g_assert_nonnull (app_filter); g_assert_nonnull (app_filter);
@ -659,10 +664,10 @@ test_app_filter_bus_get_all_oars_values (BusFixture *fixture,
gt_dbus_queue_set_server_func (fixture->queue, get_app_filter_server_cb, gt_dbus_queue_set_server_func (fixture->queue, get_app_filter_server_cb,
(gpointer) &get_app_filter_data); (gpointer) &get_app_filter_data);
app_filter = mct_get_app_filter (gt_dbus_queue_get_client_connection (fixture->queue), app_filter = mct_manager_get_app_filter (fixture->manager,
fixture->valid_uid, fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL, MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
&local_error); &local_error);
g_assert_no_error (local_error); g_assert_no_error (local_error);
g_assert_nonnull (app_filter); g_assert_nonnull (app_filter);
@ -707,10 +712,10 @@ test_app_filter_bus_get_defaults (BusFixture *fixture,
gt_dbus_queue_set_server_func (fixture->queue, get_app_filter_server_cb, gt_dbus_queue_set_server_func (fixture->queue, get_app_filter_server_cb,
(gpointer) &get_app_filter_data); (gpointer) &get_app_filter_data);
app_filter = mct_get_app_filter (gt_dbus_queue_get_client_connection (fixture->queue), app_filter = mct_manager_get_app_filter (fixture->manager,
fixture->valid_uid, fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL, MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
&local_error); &local_error);
g_assert_no_error (local_error); g_assert_no_error (local_error);
g_assert_nonnull (app_filter); g_assert_nonnull (app_filter);
@ -725,8 +730,8 @@ test_app_filter_bus_get_defaults (BusFixture *fixture,
g_assert_false (mct_app_filter_is_system_installation_allowed (app_filter)); g_assert_false (mct_app_filter_is_system_installation_allowed (app_filter));
} }
/* Test that mct_get_app_filter() returns an appropriate error if the mock D-Bus /* Test that mct_manager_get_app_filter() returns an appropriate error if the
* service reports that the given user cannot be found. * mock D-Bus service reports that the given user cannot be found.
* *
* The mock D-Bus replies are generated inline. */ * The mock D-Bus replies are generated inline. */
static void static void
@ -739,10 +744,10 @@ test_app_filter_bus_get_error_invalid_user (BusFixture *fixture,
g_autofree gchar *error_message = NULL; g_autofree gchar *error_message = NULL;
g_autoptr(MctAppFilter) app_filter = NULL; g_autoptr(MctAppFilter) app_filter = NULL;
mct_get_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue), mct_manager_get_app_filter_async (fixture->manager,
fixture->missing_uid, fixture->missing_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL, MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
async_result_cb, &result); async_result_cb, &result);
/* Handle the FindUserById() call and claim the user doesnt exist. */ /* Handle the FindUserById() call and claim the user doesnt exist. */
gint64 user_id; gint64 user_id;
@ -761,16 +766,17 @@ test_app_filter_bus_get_error_invalid_user (BusFixture *fixture,
/* Get the get_app_filter() result. */ /* Get the get_app_filter() result. */
while (result == NULL) while (result == NULL)
g_main_context_iteration (NULL, TRUE); g_main_context_iteration (NULL, TRUE);
app_filter = mct_get_app_filter_finish (result, &local_error); app_filter = mct_manager_get_app_filter_finish (fixture->manager, result,
&local_error);
g_assert_error (local_error, g_assert_error (local_error,
MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_INVALID_USER); MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_INVALID_USER);
g_assert_null (app_filter); g_assert_null (app_filter);
} }
/* Test that mct_get_app_filter() returns an appropriate error if the mock D-Bus /* Test that mct_manager_get_app_filter() returns an appropriate error if the
* service reports that the properties of the given user cant be accessed due * mock D-Bus service reports that the properties of the given user cant be
* to permissions. * accessed due to permissions.
* *
* The mock D-Bus replies are generated inline. */ * The mock D-Bus replies are generated inline. */
static void static void
@ -784,10 +790,10 @@ test_app_filter_bus_get_error_permission_denied (BusFixture *fixture,
g_autofree gchar *object_path = NULL; g_autofree gchar *object_path = NULL;
g_autoptr(MctAppFilter) app_filter = NULL; g_autoptr(MctAppFilter) app_filter = NULL;
mct_get_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue), mct_manager_get_app_filter_async (fixture->manager,
fixture->valid_uid, fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL, MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
async_result_cb, &result); async_result_cb, &result);
/* Handle the FindUserById() call. */ /* Handle the FindUserById() call. */
gint64 user_id; gint64 user_id;
@ -817,16 +823,17 @@ test_app_filter_bus_get_error_permission_denied (BusFixture *fixture,
/* Get the get_app_filter() result. */ /* Get the get_app_filter() result. */
while (result == NULL) while (result == NULL)
g_main_context_iteration (NULL, TRUE); g_main_context_iteration (NULL, TRUE);
app_filter = mct_get_app_filter_finish (result, &local_error); app_filter = mct_manager_get_app_filter_finish (fixture->manager, result,
&local_error);
g_assert_error (local_error, g_assert_error (local_error,
MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_PERMISSION_DENIED); MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_PERMISSION_DENIED);
g_assert_null (app_filter); g_assert_null (app_filter);
} }
/* Test that mct_get_app_filter() returns an appropriate error if the mock D-Bus /* Test that mct_manager_get_app_filter() returns an appropriate error if the
* service replies with no app filter properties (implying that it hasnt sent * mock D-Bus service replies with no app filter properties (implying that it
* the property values because of permissions). * hasnt sent the property values because of permissions).
* *
* The mock D-Bus replies are generated inline. */ * The mock D-Bus replies are generated inline. */
static void static void
@ -840,10 +847,10 @@ test_app_filter_bus_get_error_permission_denied_missing (BusFixture *fixture,
g_autofree gchar *object_path = NULL; g_autofree gchar *object_path = NULL;
g_autoptr(MctAppFilter) app_filter = NULL; g_autoptr(MctAppFilter) app_filter = NULL;
mct_get_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue), mct_manager_get_app_filter_async (fixture->manager,
fixture->valid_uid, fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL, MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
async_result_cb, &result); async_result_cb, &result);
/* Handle the FindUserById() call. */ /* Handle the FindUserById() call. */
gint64 user_id; gint64 user_id;
@ -874,15 +881,16 @@ test_app_filter_bus_get_error_permission_denied_missing (BusFixture *fixture,
/* Get the get_app_filter() result. */ /* Get the get_app_filter() result. */
while (result == NULL) while (result == NULL)
g_main_context_iteration (NULL, TRUE); g_main_context_iteration (NULL, TRUE);
app_filter = mct_get_app_filter_finish (result, &local_error); app_filter = mct_manager_get_app_filter_finish (fixture->manager, result,
&local_error);
g_assert_error (local_error, g_assert_error (local_error,
MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_PERMISSION_DENIED); MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_PERMISSION_DENIED);
g_assert_null (app_filter); g_assert_null (app_filter);
} }
/* Test that mct_get_app_filter() returns an error if the mock D-Bus service /* Test that mct_manager_get_app_filter() returns an error if the mock D-Bus
* reports an unrecognised error. * service reports an unrecognised error.
* *
* The mock D-Bus replies are generated inline. */ * The mock D-Bus replies are generated inline. */
static void static void
@ -894,10 +902,10 @@ test_app_filter_bus_get_error_unknown (BusFixture *fixture,
g_autoptr(GDBusMethodInvocation) invocation = NULL; g_autoptr(GDBusMethodInvocation) invocation = NULL;
g_autoptr(MctAppFilter) app_filter = NULL; g_autoptr(MctAppFilter) app_filter = NULL;
mct_get_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue), mct_manager_get_app_filter_async (fixture->manager,
fixture->valid_uid, fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL, MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
async_result_cb, &result); async_result_cb, &result);
/* Handle the FindUserById() call and return a bogus error. */ /* Handle the FindUserById() call and return a bogus error. */
gint64 user_id; gint64 user_id;
@ -918,16 +926,17 @@ test_app_filter_bus_get_error_unknown (BusFixture *fixture,
/* Get the get_app_filter() result. */ /* Get the get_app_filter() result. */
while (result == NULL) while (result == NULL)
g_main_context_iteration (NULL, TRUE); g_main_context_iteration (NULL, TRUE);
app_filter = mct_get_app_filter_finish (result, &local_error); app_filter = mct_manager_get_app_filter_finish (fixture->manager, result,
&local_error);
/* We dont actually care what error is actually used here. */ /* We dont actually care what error is actually used here. */
g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR); g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR);
g_assert_null (app_filter); g_assert_null (app_filter);
} }
/* Test that mct_get_app_filter() returns an error if the mock D-Bus service /* Test that mct_manager_get_app_filter() returns an error if the mock D-Bus
* reports an unknown interface, which means that parental controls are not * service reports an unknown interface, which means that parental controls are
* installed properly. * not installed properly.
* *
* The mock D-Bus replies are generated inline. */ * The mock D-Bus replies are generated inline. */
static void static void
@ -941,10 +950,10 @@ test_app_filter_bus_get_error_disabled (BusFixture *fixture,
g_autofree gchar *object_path = NULL; g_autofree gchar *object_path = NULL;
g_autoptr(MctAppFilter) app_filter = NULL; g_autoptr(MctAppFilter) app_filter = NULL;
mct_get_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue), mct_manager_get_app_filter_async (fixture->manager,
fixture->valid_uid, fixture->valid_uid,
MCT_GET_APP_FILTER_FLAGS_NONE, NULL, MCT_GET_APP_FILTER_FLAGS_NONE, NULL,
async_result_cb, &result); async_result_cb, &result);
/* Handle the FindUserById() call. */ /* Handle the FindUserById() call. */
gint64 user_id; gint64 user_id;
@ -975,7 +984,8 @@ test_app_filter_bus_get_error_disabled (BusFixture *fixture,
/* Get the get_app_filter() result. */ /* Get the get_app_filter() result. */
while (result == NULL) while (result == NULL)
g_main_context_iteration (NULL, TRUE); g_main_context_iteration (NULL, TRUE);
app_filter = mct_get_app_filter_finish (result, &local_error); app_filter = mct_manager_get_app_filter_finish (fixture->manager, result,
&local_error);
g_assert_error (local_error, g_assert_error (local_error,
MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_DISABLED); MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_DISABLED);
@ -1132,29 +1142,30 @@ test_app_filter_bus_set (BusFixture *fixture,
{ {
g_autoptr(GAsyncResult) result = NULL; g_autoptr(GAsyncResult) result = NULL;
mct_set_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue), mct_manager_set_app_filter_async (fixture->manager,
fixture->valid_uid, app_filter, fixture->valid_uid, app_filter,
MCT_SET_APP_FILTER_FLAGS_NONE, NULL, MCT_SET_APP_FILTER_FLAGS_NONE, NULL,
async_result_cb, &result); async_result_cb, &result);
while (result == NULL) while (result == NULL)
g_main_context_iteration (NULL, TRUE); g_main_context_iteration (NULL, TRUE);
success = mct_set_app_filter_finish (result, &local_error); success = mct_manager_set_app_filter_finish (fixture->manager, result,
&local_error);
} }
else else
{ {
success = mct_set_app_filter (gt_dbus_queue_get_client_connection (fixture->queue), success = mct_manager_set_app_filter (fixture->manager,
fixture->valid_uid, app_filter, fixture->valid_uid, app_filter,
MCT_SET_APP_FILTER_FLAGS_NONE, NULL, MCT_SET_APP_FILTER_FLAGS_NONE, NULL,
&local_error); &local_error);
} }
g_assert_no_error (local_error); g_assert_no_error (local_error);
g_assert_true (success); g_assert_true (success);
} }
/* Test that mct_set_app_filter() returns an appropriate error if the mock D-Bus /* Test that mct_manager__set_app_filter() returns an appropriate error if the
* service reports that the given user cannot be found. * mock D-Bus service reports that the given user cannot be found.
* *
* The mock D-Bus replies are generated inline. */ * The mock D-Bus replies are generated inline. */
static void static void
@ -1172,10 +1183,10 @@ test_app_filter_bus_set_error_invalid_user (BusFixture *fixture,
/* Use the default app filter. */ /* Use the default app filter. */
app_filter = mct_app_filter_builder_end (&builder); app_filter = mct_app_filter_builder_end (&builder);
mct_set_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue), mct_manager_set_app_filter_async (fixture->manager,
fixture->missing_uid, app_filter, fixture->missing_uid, app_filter,
MCT_SET_APP_FILTER_FLAGS_NONE, NULL, MCT_SET_APP_FILTER_FLAGS_NONE, NULL,
async_result_cb, &result); async_result_cb, &result);
/* Handle the FindUserById() call and claim the user doesnt exist. */ /* Handle the FindUserById() call and claim the user doesnt exist. */
gint64 user_id; gint64 user_id;
@ -1194,15 +1205,17 @@ test_app_filter_bus_set_error_invalid_user (BusFixture *fixture,
/* Get the set_app_filter() result. */ /* Get the set_app_filter() result. */
while (result == NULL) while (result == NULL)
g_main_context_iteration (NULL, TRUE); g_main_context_iteration (NULL, TRUE);
success = mct_set_app_filter_finish (result, &local_error); success = mct_manager_set_app_filter_finish (fixture->manager, result,
&local_error);
g_assert_error (local_error, g_assert_error (local_error,
MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_INVALID_USER); MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_INVALID_USER);
g_assert_false (success); g_assert_false (success);
} }
/* Test that mct_set_app_filter() returns an appropriate error if the mock D-Bus /* Test that mct_manager_set_app_filter() returns an appropriate error if the
* service replies with a permission denied error when setting properties. * mock D-Bus service replies with a permission denied error when setting
* properties.
* *
* The mock D-Bus replies are generated in set_app_filter_server_cb(). */ * The mock D-Bus replies are generated in set_app_filter_server_cb(). */
static void static void
@ -1227,18 +1240,18 @@ test_app_filter_bus_set_error_permission_denied (BusFixture *fixture,
gt_dbus_queue_set_server_func (fixture->queue, set_app_filter_server_cb, gt_dbus_queue_set_server_func (fixture->queue, set_app_filter_server_cb,
(gpointer) &set_app_filter_data); (gpointer) &set_app_filter_data);
success = mct_set_app_filter (gt_dbus_queue_get_client_connection (fixture->queue), success = mct_manager_set_app_filter (fixture->manager,
fixture->valid_uid, app_filter, fixture->valid_uid, app_filter,
MCT_SET_APP_FILTER_FLAGS_NONE, NULL, MCT_SET_APP_FILTER_FLAGS_NONE, NULL,
&local_error); &local_error);
g_assert_error (local_error, g_assert_error (local_error,
MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_PERMISSION_DENIED); MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_PERMISSION_DENIED);
g_assert_false (success); g_assert_false (success);
} }
/* Test that mct_set_app_filter() returns an error if the mock D-Bus service /* Test that mct_manager_set_app_filter() returns an error if the mock D-Bus
* reports an unrecognised error. * service reports an unrecognised error.
* *
* The mock D-Bus replies are generated in set_app_filter_server_cb(). */ * The mock D-Bus replies are generated in set_app_filter_server_cb(). */
static void static void
@ -1265,17 +1278,17 @@ test_app_filter_bus_set_error_unknown (BusFixture *fixture,
gt_dbus_queue_set_server_func (fixture->queue, set_app_filter_server_cb, gt_dbus_queue_set_server_func (fixture->queue, set_app_filter_server_cb,
(gpointer) &set_app_filter_data); (gpointer) &set_app_filter_data);
success = mct_set_app_filter (gt_dbus_queue_get_client_connection (fixture->queue), success = mct_manager_set_app_filter (fixture->manager,
fixture->valid_uid, app_filter, fixture->valid_uid, app_filter,
MCT_SET_APP_FILTER_FLAGS_NONE, NULL, MCT_SET_APP_FILTER_FLAGS_NONE, NULL,
&local_error); &local_error);
g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR); g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR);
g_assert_false (success); g_assert_false (success);
} }
/* Test that mct_set_app_filter() returns an error if the mock D-Bus service /* Test that mct_manager_set_app_filter() returns an error if the mock D-Bus
* reports an InvalidArgs error with a given one of its Set() calls. * service reports an InvalidArgs error with a given one of its Set() calls.
* *
* @test_data contains a property index encoded with GINT_TO_POINTER(), * @test_data contains a property index encoded with GINT_TO_POINTER(),
* indicating which Set() call to return the error on, since the calls are made * indicating which Set() call to return the error on, since the calls are made
@ -1308,10 +1321,10 @@ test_app_filter_bus_set_error_invalid_property (BusFixture *fixture,
gt_dbus_queue_set_server_func (fixture->queue, set_app_filter_server_cb, gt_dbus_queue_set_server_func (fixture->queue, set_app_filter_server_cb,
(gpointer) &set_app_filter_data); (gpointer) &set_app_filter_data);
success = mct_set_app_filter (gt_dbus_queue_get_client_connection (fixture->queue), success = mct_manager_set_app_filter (fixture->manager,
fixture->valid_uid, app_filter, fixture->valid_uid, app_filter,
MCT_SET_APP_FILTER_FLAGS_NONE, NULL, MCT_SET_APP_FILTER_FLAGS_NONE, NULL,
&local_error); &local_error);
g_assert_error (local_error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS); g_assert_error (local_error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
g_assert_false (success); g_assert_false (success);

View File

@ -22,7 +22,7 @@ import pwd
import sys import sys
import gi import gi
gi.require_version('Malcontent', '0') # noqa gi.require_version('Malcontent', '0') # noqa
from gi.repository import Malcontent, GLib from gi.repository import Malcontent, GLib, Gio
# Exit codes, which are a documented part of the API. # Exit codes, which are a documented part of the API.
@ -42,8 +42,10 @@ def __get_app_filter(user_id, interactive):
else: else:
flags = Malcontent.GetAppFilterFlags.NONE flags = Malcontent.GetAppFilterFlags.NONE
return Malcontent.get_app_filter( connection = Gio.bus_get_sync(Gio.BusType.SYSTEM)
connection=None, user_id=user_id, manager = Malcontent.Manager.new(connection)
return manager.get_app_filter(
user_id=user_id,
flags=flags, cancellable=None) flags=flags, cancellable=None)
@ -68,8 +70,10 @@ def __set_app_filter(user_id, app_filter, interactive):
else: else:
flags = Malcontent.GetAppFilterFlags.NONE flags = Malcontent.GetAppFilterFlags.NONE
Malcontent.set_app_filter( connection = Gio.bus_get_sync(Gio.BusType.SYSTEM)
connection=None, user_id=user_id, app_filter=app_filter, manager = Malcontent.Manager.new(connection)
manager.set_app_filter(
user_id=user_id, app_filter=app_filter,
flags=flags, cancellable=None) flags=flags, cancellable=None)
@ -159,6 +163,37 @@ def command_get(user, quiet=False, interactive=True):
print('App installation is disallowed to system repository') print('App installation is disallowed to system repository')
def command_monitor(user, quiet=False, interactive=True):
"""Monitor app filter changes for the given user."""
if user == '':
filter_user_id = 0
else:
filter_user_id = __lookup_user_id_or_error(user)
apply_filter = (user != '')
def _on_app_filter_changed(manager, changed_user_id):
if not apply_filter or changed_user_id == filter_user_id:
print('App filter changed for user ID {}'.format(changed_user_id))
connection = Gio.bus_get_sync(Gio.BusType.SYSTEM)
manager = Malcontent.Manager.new(connection)
manager.connect('app-filter-changed', _on_app_filter_changed)
if apply_filter:
print('Monitoring app filter changes for '
'user ID {}'.format(filter_user_id))
else:
print('Monitoring app filter changes for all users')
# Loop until Ctrl+C is pressed.
context = GLib.MainContext.default()
while True:
try:
context.iteration(may_block=True)
except KeyboardInterrupt:
break
def command_check(user, path, quiet=False, interactive=True): def command_check(user, path, quiet=False, interactive=True):
"""Check the given path or flatpak ref is runnable by the given user, """Check the given path or flatpak ref is runnable by the given user,
according to their app filter.""" according to their app filter."""
@ -263,6 +298,15 @@ def main():
help='user ID or username to get the app filter ' help='user ID or username to get the app filter '
'for (default: current user)') 'for (default: current user)')
# monitor command
parser_monitor = subparsers.add_parser('monitor',
help='monitor parental controls '
'settings changes')
parser_monitor.set_defaults(function=command_monitor)
parser_monitor.add_argument('user', default='', nargs='?',
help='user ID or username to monitor the app '
'filter for (default: all users)')
# check command # check command
parser_check = subparsers.add_parser('check', parents=[common_parser], parser_check = subparsers.add_parser('check', parents=[common_parser],
help='check whether a path is ' help='check whether a path is '