diff --git a/libmalcontent/app-filter.c b/libmalcontent/app-filter.c index 3599424..a9d9683 100644 --- a/libmalcontent/app-filter.c +++ b/libmalcontent/app-filter.c @@ -96,7 +96,7 @@ mct_app_filter_unref (MctAppFilter *filter) * * Get the user ID of the user this #MctAppFilter is for. * - * Returns: user ID of the relevant user + * Returns: user ID of the relevant user, or `(uid_t) -1` if unknown * Since: 0.2.0 */ uid_t @@ -533,6 +533,174 @@ 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 + */ +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); +} + +/** + * mct_app_filter_serialize: + * @filter: an #MctAppFilter + * + * Build a #GVariant which contains the app filter from @filter, in an opaque + * variant format. This format may change in future, but + * mct_app_filter_deserialize() is guaranteed to always be able to load any + * variant produced by the current or any previous version of + * mct_app_filter_serialize(). + * + * Returns: (transfer floating): a new, floating #GVariant containing the app + * filter + * Since: 0.7.0 + */ +GVariant * +mct_app_filter_serialize (MctAppFilter *filter) +{ + g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{sv}")); + + g_return_val_if_fail (filter != NULL, NULL); + g_return_val_if_fail (filter->ref_count >= 1, NULL); + + /* The serialisation format is exactly the + * `com.endlessm.ParentalControls.AppFilter` D-Bus interface. */ + g_variant_builder_add (&builder, "{sv}", "AppFilter", + _mct_app_filter_build_app_filter_variant (filter)); + g_variant_builder_add (&builder, "{sv}", "OarsFilter", + g_variant_new ("(s@a{ss})", "oars-1.1", + filter->oars_ratings)); + g_variant_builder_add (&builder, "{sv}", "AllowUserInstallation", + g_variant_new_boolean (filter->allow_user_installation)); + g_variant_builder_add (&builder, "{sv}", "AllowSystemInstallation", + g_variant_new_boolean (filter->allow_system_installation)); + + return g_variant_builder_end (&builder); +} + +/** + * mct_app_filter_deserialize: + * @variant: a serialized app filter variant + * @user_id: the ID of the user the app filter relates to + * @error: return location for a #GError, or %NULL + * + * Deserialize an app filter previously serialized with + * mct_app_filter_serialize(). This function guarantees to be able to + * deserialize any serialized form from this version or older versions of + * libmalcontent. + * + * If deserialization fails, %MCT_MANAGER_ERROR_INVALID_DATA will be returned. + * + * Returns: (transfer full): deserialized app filter + * Since: 0.7.0 + */ +MctAppFilter * +mct_app_filter_deserialize (GVariant *variant, + uid_t user_id, + GError **error) +{ + 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_autoptr(MctAppFilter) app_filter = NULL; + + g_return_val_if_fail (variant != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + /* Check the overall type. */ + if (!g_variant_is_of_type (variant, G_VARIANT_TYPE ("a{sv}"))) + { + g_set_error (error, MCT_MANAGER_ERROR, + MCT_MANAGER_ERROR_INVALID_DATA, + _("App filter for user %u was in an unrecognized format"), + (guint) user_id); + return NULL; + } + + /* Extract the properties we care about. The default values here should be + * kept in sync with those in the `com.endlessm.ParentalControls.AppFilter` + * D-Bus interface. */ + if (!g_variant_lookup (variant, "AppFilter", "(b^as)", + &is_whitelist, &app_list)) + { + /* Default value. */ + is_whitelist = FALSE; + app_list = g_new0 (gchar *, 1); + } + + if (!g_variant_lookup (variant, "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_MANAGER_ERROR, + MCT_MANAGER_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 (variant, "AllowUserInstallation", "b", + &allow_user_installation)) + { + /* Default value. */ + allow_user_installation = TRUE; + } + + if (!g_variant_lookup (variant, "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); +} + /* * Actual implementation of #MctAppFilterBuilder. * diff --git a/libmalcontent/app-filter.h b/libmalcontent/app-filter.h index 1860fe0..b5d8fb9 100644 --- a/libmalcontent/app-filter.h +++ b/libmalcontent/app-filter.h @@ -96,6 +96,11 @@ 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); +GVariant *mct_app_filter_serialize (MctAppFilter *filter); +MctAppFilter *mct_app_filter_deserialize (GVariant *variant, + uid_t user_id, + GError **error); + /** * MctAppFilterBuilder: * diff --git a/libmalcontent/manager.c b/libmalcontent/manager.c index eef42fb..3bed6a3 100644 --- a/libmalcontent/manager.c +++ b/libmalcontent/manager.c @@ -257,37 +257,6 @@ _mct_manager_user_changed_cb (GDBusConnection *connection, 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, @@ -386,13 +355,6 @@ mct_manager_get_app_filter (MctManager *self, 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); @@ -443,8 +405,7 @@ mct_manager_get_app_filter (MctManager *self, /* 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)) + if (!g_variant_lookup (properties, "AppFilter", "(b^as)", NULL, NULL)) { g_set_error (error, MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_PERMISSION_DENIED, @@ -453,52 +414,7 @@ mct_manager_get_app_filter (MctManager *self, 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_MANAGER_ERROR, - MCT_MANAGER_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); + return mct_app_filter_deserialize (properties, user_id, error); } static void get_app_filter_thread_cb (GTask *task, @@ -632,14 +548,10 @@ mct_manager_set_app_filter (MctManager *self, 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(GVariant) properties_variant = NULL; + g_autoptr(GVariant) properties_value = NULL; + const gchar *properties_key = NULL; + GVariantIter iter; g_autoptr(GError) local_error = NULL; g_return_val_if_fail (MCT_IS_MANAGER (self), FALSE); @@ -654,102 +566,35 @@ mct_manager_set_app_filter (MctManager *self, 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); + properties_variant = mct_app_filter_serialize (app_filter); - 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_MANAGER_SET_VALUE_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_variant_iter_init (&iter, properties_variant); + while (g_variant_iter_loop (&iter, "{&sv}", &properties_key, &properties_value)) { - g_propagate_error (error, bus_error_to_manager_error (local_error, user_id)); - return FALSE; - } + g_autoptr(GVariant) result_variant = NULL; - 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_MANAGER_SET_VALUE_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_manager_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_MANAGER_SET_VALUE_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_manager_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_MANAGER_SET_VALUE_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_manager_error (local_error, user_id)); - return FALSE; + 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", + properties_key, + properties_value), + G_VARIANT_TYPE ("()"), + (flags & MCT_MANAGER_SET_VALUE_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_manager_error (local_error, user_id)); + return FALSE; + } } return TRUE; diff --git a/libmalcontent/tests/app-filter.c b/libmalcontent/tests/app-filter.c index 7ce4abc..f5f9eca 100644 --- a/libmalcontent/tests/app-filter.c +++ b/libmalcontent/tests/app-filter.c @@ -85,6 +85,83 @@ test_app_filter_refs (void) /* Final ref is dropped by g_autoptr(). */ } +/* Basic test of mct_app_filter_serialize() on an app filter. */ +static void +test_app_filter_serialize (void) +{ + g_auto(MctAppFilterBuilder) builder = MCT_APP_FILTER_BUILDER_INIT (); + g_autoptr(MctAppFilter) filter = NULL; + g_autoptr(GVariant) serialized = NULL; + + /* Use an empty #MctAppFilter. */ + filter = mct_app_filter_builder_end (&builder); + + /* We can’t assert anything about the serialisation format, since it’s opaque. */ + serialized = mct_app_filter_serialize (filter); + g_assert_nonnull (serialized); +} + +/* Basic test of mct_app_filter_deserialize() on various current and historic + * serialised app filter variants. */ +static void +test_app_filter_deserialize (void) +{ + /* These are all opaque. Older versions should be kept around to test + * backwards compatibility. */ + const gchar *valid_app_filters[] = + { + "@a{sv} {}", + "{ 'AppFilter': <(true, @as [])> }", + "{ 'OarsFilter': <('oars-1.1', { 'violence-cartoon': 'mild' })> }", + "{ 'AllowUserInstallation': }", + "{ 'AllowSystemInstallation': }", + }; + + for (gsize i = 0; i < G_N_ELEMENTS (valid_app_filters); i++) + { + g_autoptr(GVariant) serialized = NULL; + g_autoptr(MctAppFilter) filter = NULL; + g_autoptr(GError) local_error = NULL; + + g_test_message ("%" G_GSIZE_FORMAT ": %s", i, valid_app_filters[i]); + + serialized = g_variant_parse (NULL, valid_app_filters[i], NULL, NULL, NULL); + g_assert (serialized != NULL); + + filter = mct_app_filter_deserialize (serialized, 1, &local_error); + g_assert_no_error (local_error); + g_assert_nonnull (filter); + } +} + +/* Test of mct_app_filter_deserialize() on various invalid variants. */ +static void +test_app_filter_deserialize_invalid (void) +{ + const gchar *invalid_app_filters[] = + { + "false", + "()", + "{ 'OarsFilter': <('invalid', { 'violence-cartoon': 'mild' })> }", + }; + + for (gsize i = 0; i < G_N_ELEMENTS (invalid_app_filters); i++) + { + g_autoptr(GVariant) serialized = NULL; + g_autoptr(MctAppFilter) filter = NULL; + g_autoptr(GError) local_error = NULL; + + g_test_message ("%" G_GSIZE_FORMAT ": %s", i, invalid_app_filters[i]); + + serialized = g_variant_parse (NULL, invalid_app_filters[i], NULL, NULL, NULL); + g_assert (serialized != NULL); + + filter = mct_app_filter_deserialize (serialized, 1, &local_error); + g_assert_error (local_error, MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_INVALID_DATA); + g_assert_null (filter); + } +} + /* Fixture for tests which use an #MctAppFilterBuilder. The builder can either * be heap- or stack-allocated. @builder will always be a valid pointer to it. */ @@ -1381,6 +1458,10 @@ main (int argc, g_test_add_func ("/app-filter/types", test_app_filter_types); g_test_add_func ("/app-filter/refs", test_app_filter_refs); + g_test_add_func ("/app-filter/serialize", test_app_filter_serialize); + g_test_add_func ("/app-filter/deserialize", test_app_filter_deserialize); + g_test_add_func ("/app-filter/deserialize/invalid", test_app_filter_deserialize_invalid); + g_test_add ("/app-filter/builder/stack/non-empty", BuilderFixture, NULL, builder_set_up_stack, test_app_filter_builder_non_empty, builder_tear_down_stack);