diff --git a/libmalcontent/app-filter-private.h b/libmalcontent/app-filter-private.h new file mode 100644 index 0000000..42e97b3 --- /dev/null +++ b/libmalcontent/app-filter-private.h @@ -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 + */ + +#pragma once + +#include +#include +#include +#include + +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 diff --git a/libmalcontent/app-filter.c b/libmalcontent/app-filter.c index d4d345c..8d15810 100644 --- a/libmalcontent/app-filter.c +++ b/libmalcontent/app-filter.c @@ -29,39 +29,12 @@ #include #include +#include "libmalcontent/app-filter-private.h" + G_DEFINE_QUARK (MctAppFilterError, mct_app_filter_error) -/** - * 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; -}; +/* struct _MctAppFilter is defined in app-filter-private.h */ G_DEFINE_BOXED_TYPE (MctAppFilter, mct_app_filter, 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; } -/** - * _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 doesn’t 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 don’t 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, that’s - * 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. It’s 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. It’s 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 user’s 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. * diff --git a/libmalcontent/app-filter.h b/libmalcontent/app-filter.h index b534352..c77f62a 100644 --- a/libmalcontent/app-filter.h +++ b/libmalcontent/app-filter.h @@ -37,7 +37,8 @@ G_BEGIN_DECLS * a user is inconsistent or invalid * @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 */ @@ -78,40 +79,6 @@ typedef enum MCT_APP_FILTER_OARS_VALUE_INTENSE, } 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: * @@ -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_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: * diff --git a/libmalcontent/manager.c b/libmalcontent/manager.c new file mode 100644 index 0000000..5c30d5d --- /dev/null +++ b/libmalcontent/manager.c @@ -0,0 +1,779 @@ +/* -*- 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 + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#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) */ +}; + +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_dispose (GObject *object) +{ + MctManager *self = MCT_MANAGER (object); + + 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->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. It’s 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); +} + +/** + * 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); +} + +/** + * _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 doesn’t 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 don’t 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, that’s + * 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 user’s 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); +} \ No newline at end of file diff --git a/libmalcontent/manager.h b/libmalcontent/manager.h new file mode 100644 index 0000000..841ba8a --- /dev/null +++ b/libmalcontent/manager.h @@ -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 + */ + +#pragma once + +#include +#include +#include +#include + +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 diff --git a/libmalcontent/meson.build b/libmalcontent/meson.build index ea10b25..8bc9aa0 100644 --- a/libmalcontent/meson.build +++ b/libmalcontent/meson.build @@ -2,9 +2,14 @@ libmalcontent_api_version = '0' libmalcontent_api_name = 'malcontent-' + libmalcontent_api_version libmalcontent_sources = [ 'app-filter.c', + 'manager.c', ] libmalcontent_headers = [ 'app-filter.h', + 'manager.h', +] +libmalcontent_private_headers = [ + 'app-filter-private.h', ] libmalcontent_public_deps = [ @@ -20,7 +25,7 @@ libmalcontent_private_deps = [ libmalcontent_include_subdir = join_paths(libmalcontent_api_name, 'libmalcontent') libmalcontent = library(libmalcontent_api_name, - libmalcontent_sources + libmalcontent_headers, + libmalcontent_sources + libmalcontent_headers + libmalcontent_private_headers, dependencies: libmalcontent_public_deps + libmalcontent_private_deps, include_directories: root_inc, install: true, @@ -49,7 +54,7 @@ pkgconfig.generate( ) gnome.generate_gir(libmalcontent, - sources: libmalcontent_sources + libmalcontent_headers, + sources: libmalcontent_sources + libmalcontent_headers + libmalcontent_private_headers, nsversion: libmalcontent_api_version, namespace: 'Malcontent', symbol_prefix: 'mct_', @@ -57,6 +62,7 @@ gnome.generate_gir(libmalcontent, export_packages: 'libmalcontent', includes: ['GObject-2.0', 'Gio-2.0'], install: true, + dependencies: libmalcontent_dep, ) subdir('tests') \ No newline at end of file diff --git a/libmalcontent/tests/app-filter.c b/libmalcontent/tests/app-filter.c index 51a3ccd..1ab4433 100644 --- a/libmalcontent/tests/app-filter.c +++ b/libmalcontent/tests/app-filter.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -422,6 +423,7 @@ typedef struct GtDBusQueue *queue; /* (owned) */ uid_t valid_uid; uid_t missing_uid; + MctManager *manager; /* (owned) */ } BusFixture; static void @@ -452,12 +454,15 @@ bus_set_up (BusFixture *fixture, (GDBusInterfaceInfo *) &accounts_interface_info, &local_error); g_assert_no_error (local_error); + + fixture->manager = mct_manager_new (gt_dbus_queue_get_client_connection (fixture->queue)); } static void bus_tear_down (BusFixture *fixture, gconstpointer test_data) { + g_clear_object (&fixture->manager); gt_dbus_queue_disconnect (fixture->queue, TRUE); 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 * given in #GetAppFilterData.properties if queried for a UID matching * #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 { uid_t expected_uid; @@ -553,21 +558,21 @@ test_app_filter_bus_get (BusFixture *fixture, { g_autoptr(GAsyncResult) result = NULL; - mct_get_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue), - fixture->valid_uid, - MCT_GET_APP_FILTER_FLAGS_NONE, NULL, - async_result_cb, &result); + mct_manager_get_app_filter_async (fixture->manager, + fixture->valid_uid, + MCT_GET_APP_FILTER_FLAGS_NONE, NULL, + async_result_cb, &result); while (result == NULL) 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 { - app_filter = mct_get_app_filter (gt_dbus_queue_get_client_connection (fixture->queue), - fixture->valid_uid, - MCT_GET_APP_FILTER_FLAGS_NONE, NULL, - &local_error); + app_filter = mct_manager_get_app_filter (fixture->manager, + fixture->valid_uid, + MCT_GET_APP_FILTER_FLAGS_NONE, NULL, + &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, (gpointer) &get_app_filter_data); - app_filter = mct_get_app_filter (gt_dbus_queue_get_client_connection (fixture->queue), - fixture->valid_uid, - MCT_GET_APP_FILTER_FLAGS_NONE, NULL, - &local_error); + app_filter = mct_manager_get_app_filter (fixture->manager, + fixture->valid_uid, + MCT_GET_APP_FILTER_FLAGS_NONE, NULL, + &local_error); g_assert_no_error (local_error); 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, (gpointer) &get_app_filter_data); - app_filter = mct_get_app_filter (gt_dbus_queue_get_client_connection (fixture->queue), - fixture->valid_uid, - MCT_GET_APP_FILTER_FLAGS_NONE, NULL, - &local_error); + app_filter = mct_manager_get_app_filter (fixture->manager, + fixture->valid_uid, + MCT_GET_APP_FILTER_FLAGS_NONE, NULL, + &local_error); g_assert_no_error (local_error); 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, (gpointer) &get_app_filter_data); - app_filter = mct_get_app_filter (gt_dbus_queue_get_client_connection (fixture->queue), - fixture->valid_uid, - MCT_GET_APP_FILTER_FLAGS_NONE, NULL, - &local_error); + app_filter = mct_manager_get_app_filter (fixture->manager, + fixture->valid_uid, + MCT_GET_APP_FILTER_FLAGS_NONE, NULL, + &local_error); g_assert_no_error (local_error); 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)); } -/* Test that mct_get_app_filter() returns an appropriate error if the mock D-Bus - * service reports that the given user cannot be found. +/* Test that mct_manager_get_app_filter() returns an appropriate error if the + * mock D-Bus service reports that the given user cannot be found. * * The mock D-Bus replies are generated inline. */ static void @@ -739,10 +744,10 @@ test_app_filter_bus_get_error_invalid_user (BusFixture *fixture, g_autofree gchar *error_message = NULL; g_autoptr(MctAppFilter) app_filter = NULL; - mct_get_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue), - fixture->missing_uid, - MCT_GET_APP_FILTER_FLAGS_NONE, NULL, - async_result_cb, &result); + mct_manager_get_app_filter_async (fixture->manager, + fixture->missing_uid, + MCT_GET_APP_FILTER_FLAGS_NONE, NULL, + async_result_cb, &result); /* Handle the FindUserById() call and claim the user doesn’t exist. */ gint64 user_id; @@ -761,16 +766,17 @@ test_app_filter_bus_get_error_invalid_user (BusFixture *fixture, /* Get the get_app_filter() result. */ while (result == NULL) 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, MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_INVALID_USER); g_assert_null (app_filter); } -/* Test that mct_get_app_filter() returns an appropriate error if the mock D-Bus - * service reports that the properties of the given user can’t be accessed due - * to permissions. +/* Test that mct_manager_get_app_filter() returns an appropriate error if the + * mock D-Bus service reports that the properties of the given user can’t be + * accessed due to permissions. * * The mock D-Bus replies are generated inline. */ static void @@ -784,10 +790,10 @@ test_app_filter_bus_get_error_permission_denied (BusFixture *fixture, g_autofree gchar *object_path = NULL; g_autoptr(MctAppFilter) app_filter = NULL; - mct_get_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue), - fixture->valid_uid, - MCT_GET_APP_FILTER_FLAGS_NONE, NULL, - async_result_cb, &result); + mct_manager_get_app_filter_async (fixture->manager, + fixture->valid_uid, + MCT_GET_APP_FILTER_FLAGS_NONE, NULL, + async_result_cb, &result); /* Handle the FindUserById() call. */ gint64 user_id; @@ -817,16 +823,17 @@ test_app_filter_bus_get_error_permission_denied (BusFixture *fixture, /* Get the get_app_filter() result. */ while (result == NULL) 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, MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_PERMISSION_DENIED); g_assert_null (app_filter); } -/* Test that mct_get_app_filter() returns an appropriate error if the mock D-Bus - * service replies with no app filter properties (implying that it hasn’t sent - * the property values because of permissions). +/* Test that mct_manager_get_app_filter() returns an appropriate error if the + * mock D-Bus service replies with no app filter properties (implying that it + * hasn’t sent the property values because of permissions). * * The mock D-Bus replies are generated inline. */ static void @@ -840,10 +847,10 @@ test_app_filter_bus_get_error_permission_denied_missing (BusFixture *fixture, g_autofree gchar *object_path = NULL; g_autoptr(MctAppFilter) app_filter = NULL; - mct_get_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue), - fixture->valid_uid, - MCT_GET_APP_FILTER_FLAGS_NONE, NULL, - async_result_cb, &result); + mct_manager_get_app_filter_async (fixture->manager, + fixture->valid_uid, + MCT_GET_APP_FILTER_FLAGS_NONE, NULL, + async_result_cb, &result); /* Handle the FindUserById() call. */ gint64 user_id; @@ -874,15 +881,16 @@ test_app_filter_bus_get_error_permission_denied_missing (BusFixture *fixture, /* Get the get_app_filter() result. */ while (result == NULL) 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, MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_PERMISSION_DENIED); g_assert_null (app_filter); } -/* Test that mct_get_app_filter() returns an error if the mock D-Bus service - * reports an unrecognised error. +/* Test that mct_manager_get_app_filter() returns an error if the mock D-Bus + * service reports an unrecognised error. * * The mock D-Bus replies are generated inline. */ static void @@ -894,10 +902,10 @@ test_app_filter_bus_get_error_unknown (BusFixture *fixture, g_autoptr(GDBusMethodInvocation) invocation = NULL; g_autoptr(MctAppFilter) app_filter = NULL; - mct_get_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue), - fixture->valid_uid, - MCT_GET_APP_FILTER_FLAGS_NONE, NULL, - async_result_cb, &result); + mct_manager_get_app_filter_async (fixture->manager, + fixture->valid_uid, + MCT_GET_APP_FILTER_FLAGS_NONE, NULL, + async_result_cb, &result); /* Handle the FindUserById() call and return a bogus error. */ gint64 user_id; @@ -918,16 +926,17 @@ test_app_filter_bus_get_error_unknown (BusFixture *fixture, /* Get the get_app_filter() result. */ while (result == NULL) 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 don’t actually care what error is actually used here. */ g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR); g_assert_null (app_filter); } -/* Test that mct_get_app_filter() returns an error if the mock D-Bus service - * reports an unknown interface, which means that parental controls are not - * installed properly. +/* Test that mct_manager_get_app_filter() returns an error if the mock D-Bus + * service reports an unknown interface, which means that parental controls are + * not installed properly. * * The mock D-Bus replies are generated inline. */ static void @@ -941,10 +950,10 @@ test_app_filter_bus_get_error_disabled (BusFixture *fixture, g_autofree gchar *object_path = NULL; g_autoptr(MctAppFilter) app_filter = NULL; - mct_get_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue), - fixture->valid_uid, - MCT_GET_APP_FILTER_FLAGS_NONE, NULL, - async_result_cb, &result); + mct_manager_get_app_filter_async (fixture->manager, + fixture->valid_uid, + MCT_GET_APP_FILTER_FLAGS_NONE, NULL, + async_result_cb, &result); /* Handle the FindUserById() call. */ gint64 user_id; @@ -975,7 +984,8 @@ test_app_filter_bus_get_error_disabled (BusFixture *fixture, /* Get the get_app_filter() result. */ while (result == NULL) 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, 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; - mct_set_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue), - fixture->valid_uid, app_filter, - MCT_SET_APP_FILTER_FLAGS_NONE, NULL, - async_result_cb, &result); + mct_manager_set_app_filter_async (fixture->manager, + fixture->valid_uid, app_filter, + MCT_SET_APP_FILTER_FLAGS_NONE, NULL, + async_result_cb, &result); while (result == NULL) 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 { - success = mct_set_app_filter (gt_dbus_queue_get_client_connection (fixture->queue), - fixture->valid_uid, app_filter, - MCT_SET_APP_FILTER_FLAGS_NONE, NULL, - &local_error); + success = mct_manager_set_app_filter (fixture->manager, + fixture->valid_uid, app_filter, + MCT_SET_APP_FILTER_FLAGS_NONE, NULL, + &local_error); } g_assert_no_error (local_error); g_assert_true (success); } -/* Test that mct_set_app_filter() returns an appropriate error if the mock D-Bus - * service reports that the given user cannot be found. +/* Test that mct_manager__set_app_filter() returns an appropriate error if the + * mock D-Bus service reports that the given user cannot be found. * * The mock D-Bus replies are generated inline. */ static void @@ -1172,10 +1183,10 @@ test_app_filter_bus_set_error_invalid_user (BusFixture *fixture, /* Use the default app filter. */ app_filter = mct_app_filter_builder_end (&builder); - mct_set_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue), - fixture->missing_uid, app_filter, - MCT_SET_APP_FILTER_FLAGS_NONE, NULL, - async_result_cb, &result); + mct_manager_set_app_filter_async (fixture->manager, + fixture->missing_uid, app_filter, + MCT_SET_APP_FILTER_FLAGS_NONE, NULL, + async_result_cb, &result); /* Handle the FindUserById() call and claim the user doesn’t exist. */ gint64 user_id; @@ -1194,15 +1205,17 @@ test_app_filter_bus_set_error_invalid_user (BusFixture *fixture, /* Get the set_app_filter() result. */ while (result == NULL) 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, MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_INVALID_USER); g_assert_false (success); } -/* Test that mct_set_app_filter() returns an appropriate error if the mock D-Bus - * service replies with a permission denied error when setting properties. +/* Test that mct_manager_set_app_filter() returns an appropriate error if the + * 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(). */ 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, (gpointer) &set_app_filter_data); - success = mct_set_app_filter (gt_dbus_queue_get_client_connection (fixture->queue), - fixture->valid_uid, app_filter, - MCT_SET_APP_FILTER_FLAGS_NONE, NULL, - &local_error); + success = mct_manager_set_app_filter (fixture->manager, + fixture->valid_uid, app_filter, + MCT_SET_APP_FILTER_FLAGS_NONE, NULL, + &local_error); g_assert_error (local_error, MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_PERMISSION_DENIED); g_assert_false (success); } -/* Test that mct_set_app_filter() returns an error if the mock D-Bus service - * reports an unrecognised error. +/* Test that mct_manager_set_app_filter() returns an error if the mock D-Bus + * service reports an unrecognised error. * * The mock D-Bus replies are generated in set_app_filter_server_cb(). */ 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, (gpointer) &set_app_filter_data); - success = mct_set_app_filter (gt_dbus_queue_get_client_connection (fixture->queue), - fixture->valid_uid, app_filter, - MCT_SET_APP_FILTER_FLAGS_NONE, NULL, - &local_error); + success = mct_manager_set_app_filter (fixture->manager, + fixture->valid_uid, app_filter, + MCT_SET_APP_FILTER_FLAGS_NONE, NULL, + &local_error); g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR); g_assert_false (success); } -/* Test that mct_set_app_filter() returns an error if the mock D-Bus service - * reports an InvalidArgs error with a given one of its Set() calls. +/* Test that mct_manager_set_app_filter() returns an error if the mock D-Bus + * service reports an InvalidArgs error with a given one of its Set() calls. * * @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 @@ -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, (gpointer) &set_app_filter_data); - success = mct_set_app_filter (gt_dbus_queue_get_client_connection (fixture->queue), - fixture->valid_uid, app_filter, - MCT_SET_APP_FILTER_FLAGS_NONE, NULL, - &local_error); + success = mct_manager_set_app_filter (fixture->manager, + fixture->valid_uid, app_filter, + MCT_SET_APP_FILTER_FLAGS_NONE, NULL, + &local_error); g_assert_error (local_error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS); g_assert_false (success); diff --git a/malcontent-client/malcontent-client.py b/malcontent-client/malcontent-client.py index 49834e4..46e23aa 100644 --- a/malcontent-client/malcontent-client.py +++ b/malcontent-client/malcontent-client.py @@ -22,7 +22,7 @@ import pwd import sys import gi 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. @@ -42,8 +42,10 @@ def __get_app_filter(user_id, interactive): else: flags = Malcontent.GetAppFilterFlags.NONE - return Malcontent.get_app_filter( - connection=None, user_id=user_id, + connection = Gio.bus_get_sync(Gio.BusType.SYSTEM) + manager = Malcontent.Manager.new(connection) + return manager.get_app_filter( + user_id=user_id, flags=flags, cancellable=None) @@ -68,8 +70,10 @@ def __set_app_filter(user_id, app_filter, interactive): else: flags = Malcontent.GetAppFilterFlags.NONE - Malcontent.set_app_filter( - connection=None, user_id=user_id, app_filter=app_filter, + connection = Gio.bus_get_sync(Gio.BusType.SYSTEM) + manager = Malcontent.Manager.new(connection) + manager.set_app_filter( + user_id=user_id, app_filter=app_filter, flags=flags, cancellable=None)