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..62c340e 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; @@ -893,9 +738,6 @@ mct_manager_get_session_limits (MctManager *self, g_autoptr(GVariant) result_variant = NULL; g_autoptr(GVariant) properties = NULL; g_autoptr(GError) local_error = NULL; - g_autoptr(MctSessionLimits) session_limits = NULL; - guint32 limit_type; - guint32 daily_start_time, daily_end_time; g_return_val_if_fail (MCT_IS_MANAGER (self), NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); @@ -946,8 +788,7 @@ mct_manager_get_session_limits (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, "LimitType", "u", - &limit_type)) + if (!g_variant_lookup (properties, "LimitType", "u", NULL)) { g_set_error (error, MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_PERMISSION_DENIED, @@ -956,45 +797,7 @@ mct_manager_get_session_limits (MctManager *self, return NULL; } - /* Check that the limit type is something we support. */ - G_STATIC_ASSERT (sizeof (limit_type) >= sizeof (MctSessionLimitsType)); - - if ((guint) limit_type > MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE) - { - g_set_error (error, MCT_MANAGER_ERROR, - MCT_MANAGER_ERROR_INVALID_DATA, - _("Session limit for user %u has an unrecognized type ‘%u’"), - (guint) user_id, limit_type); - return NULL; - } - - if (!g_variant_lookup (properties, "DailySchedule", "(uu)", - &daily_start_time, &daily_end_time)) - { - /* Default value. */ - daily_start_time = 0; - daily_end_time = 24 * 60 * 60; - } - - if (daily_start_time >= daily_end_time || - daily_end_time > 24 * 60 * 60) - { - g_set_error (error, MCT_MANAGER_ERROR, - MCT_MANAGER_ERROR_INVALID_DATA, - _("Session limit for user %u has invalid daily schedule %u–%u"), - (guint) user_id, daily_start_time, daily_end_time); - return NULL; - } - - /* Success. Create an #MctSessionLimits object to contain the results. */ - session_limits = g_new0 (MctSessionLimits, 1); - session_limits->ref_count = 1; - session_limits->user_id = user_id; - session_limits->limit_type = limit_type; - session_limits->daily_start_time = daily_start_time; - session_limits->daily_end_time = daily_end_time; - - return g_steal_pointer (&session_limits); + return mct_session_limits_deserialize (properties, user_id, error); } static void get_session_limits_thread_cb (GTask *task, @@ -1128,11 +931,12 @@ mct_manager_set_session_limits (MctManager *self, GError **error) { g_autofree gchar *object_path = NULL; - g_autoptr(GVariant) limit_variant = NULL; - const gchar *limit_property_name = NULL; g_autoptr(GVariant) limit_type_variant = NULL; - g_autoptr(GVariant) limit_result_variant = NULL; g_autoptr(GVariant) limit_type_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); @@ -1147,29 +951,22 @@ mct_manager_set_session_limits (MctManager *self, if (object_path == NULL) return FALSE; - switch (session_limits->limit_type) - { - case MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE: - limit_variant = g_variant_new ("(uu)", - session_limits->daily_start_time, - session_limits->daily_end_time); - limit_property_name = "DailySchedule"; - break; - case MCT_SESSION_LIMITS_TYPE_NONE: - limit_variant = NULL; - limit_property_name = NULL; - break; - default: - g_assert_not_reached (); - } + properties_variant = mct_session_limits_serialize (session_limits); - limit_type_variant = g_variant_new_uint32 (session_limits->limit_type); - - if (limit_property_name != NULL) + g_variant_iter_init (&iter, properties_variant); + while (g_variant_iter_loop (&iter, "{&sv}", &properties_key, &properties_value)) { - /* Change the details of the new limit first, so that all the properties are - * correct by the time the limit type is changed over. */ - limit_result_variant = + g_autoptr(GVariant) result_variant = NULL; + + /* Change the limit type last, so all the details of the new limit are + * correct by the time it’s changed over. */ + if (g_str_equal (properties_key, "LimitType")) + { + limit_type_variant = g_steal_pointer (&properties_value); + continue; + } + + result_variant = g_dbus_connection_call_sync (self->connection, "org.freedesktop.Accounts", object_path, @@ -1177,8 +974,8 @@ mct_manager_set_session_limits (MctManager *self, "Set", g_variant_new ("(ssv)", "com.endlessm.ParentalControls.SessionLimits", - limit_property_name, - g_steal_pointer (&limit_variant)), + properties_key, + properties_value), G_VARIANT_TYPE ("()"), (flags & MCT_MANAGER_SET_VALUE_FLAGS_INTERACTIVE) ? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION @@ -1202,7 +999,7 @@ mct_manager_set_session_limits (MctManager *self, g_variant_new ("(ssv)", "com.endlessm.ParentalControls.SessionLimits", "LimitType", - g_steal_pointer (&limit_type_variant)), + limit_type_variant), G_VARIANT_TYPE ("()"), (flags & MCT_MANAGER_SET_VALUE_FLAGS_INTERACTIVE) ? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION diff --git a/libmalcontent/session-limits.c b/libmalcontent/session-limits.c index be4373f..dbe07ce 100644 --- a/libmalcontent/session-limits.c +++ b/libmalcontent/session-limits.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "libmalcontent/session-limits-private.h" @@ -85,7 +86,7 @@ mct_session_limits_unref (MctSessionLimits *limits) * * Get the user ID of the user this #MctSessionLimits is for. * - * Returns: user ID of the relevant user + * Returns: user ID of the relevant user, or `(uid_t) -1` if unknown * Since: 0.5.0 */ uid_t @@ -190,6 +191,149 @@ out: return user_allowed_now; } +/** + * mct_session_limits_serialize: + * @limits: an #MctSessionLimits + * + * Build a #GVariant which contains the session limits from @limits, in an + * opaque variant format. This format may change in future, but + * mct_session_limits_deserialize() is guaranteed to always be able to load any + * variant produced by the current or any previous version of + * mct_session_limits_serialize(). + * + * Returns: (transfer floating): a new, floating #GVariant containing the + * session limits + * Since: 0.7.0 + */ +GVariant * +mct_session_limits_serialize (MctSessionLimits *limits) +{ + g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{sv}")); + g_autoptr(GVariant) limit_variant = NULL; + const gchar *limit_property_name; + + g_return_val_if_fail (limits != NULL, NULL); + g_return_val_if_fail (limits->ref_count >= 1, NULL); + + /* The serialisation format is exactly the + * `com.endlessm.ParentalControls.SessionLimits` D-Bus interface. */ + switch (limits->limit_type) + { + case MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE: + limit_variant = g_variant_new ("(uu)", + limits->daily_start_time, + limits->daily_end_time); + limit_property_name = "DailySchedule"; + break; + case MCT_SESSION_LIMITS_TYPE_NONE: + limit_variant = NULL; + limit_property_name = NULL; + break; + default: + g_assert_not_reached (); + } + + if (limit_property_name != NULL) + { + g_variant_builder_add (&builder, "{sv}", limit_property_name, + g_steal_pointer (&limit_variant)); + } + + g_variant_builder_add (&builder, "{sv}", "LimitType", + g_variant_new_uint32 (limits->limit_type)); + + return g_variant_builder_end (&builder); +} + +/** + * mct_session_limits_deserialize: + * @variant: a serialized session limits variant + * @user_id: the ID of the user the session limits relate to + * @error: return location for a #GError, or %NULL + * + * Deserialize a set of session limits previously serialized with + * mct_session_limits_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 session limits + * Since: 0.7.0 + */ +MctSessionLimits * +mct_session_limits_deserialize (GVariant *variant, + uid_t user_id, + GError **error) +{ + g_autoptr(MctSessionLimits) session_limits = NULL; + guint32 limit_type; + guint32 daily_start_time, daily_end_time; + + 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, + _("Session limit 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.SessionLimits` + * D-Bus interface. */ + if (!g_variant_lookup (variant, "LimitType", "u", + &limit_type)) + { + /* Default value. */ + limit_type = MCT_SESSION_LIMITS_TYPE_NONE; + } + + /* Check that the limit type is something we support. */ + G_STATIC_ASSERT (sizeof (limit_type) >= sizeof (MctSessionLimitsType)); + + if ((guint) limit_type > MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE) + { + g_set_error (error, MCT_MANAGER_ERROR, + MCT_MANAGER_ERROR_INVALID_DATA, + _("Session limit for user %u has an unrecognized type ‘%u’"), + (guint) user_id, limit_type); + return NULL; + } + + if (!g_variant_lookup (variant, "DailySchedule", "(uu)", + &daily_start_time, &daily_end_time)) + { + /* Default value. */ + daily_start_time = 0; + daily_end_time = 24 * 60 * 60; + } + + if (daily_start_time >= daily_end_time || + daily_end_time > 24 * 60 * 60) + { + g_set_error (error, MCT_MANAGER_ERROR, + MCT_MANAGER_ERROR_INVALID_DATA, + _("Session limit for user %u has invalid daily schedule %u–%u"), + (guint) user_id, daily_start_time, daily_end_time); + return NULL; + } + + /* Success. Create an #MctSessionLimits object to contain the results. */ + session_limits = g_new0 (MctSessionLimits, 1); + session_limits->ref_count = 1; + session_limits->user_id = user_id; + session_limits->limit_type = limit_type; + session_limits->daily_start_time = daily_start_time; + session_limits->daily_end_time = daily_end_time; + + return g_steal_pointer (&session_limits); +} + /* * Actual implementation of #MctSessionLimitsBuilder. * diff --git a/libmalcontent/session-limits.h b/libmalcontent/session-limits.h index d8fb5f1..dd5e9e9 100644 --- a/libmalcontent/session-limits.h +++ b/libmalcontent/session-limits.h @@ -57,6 +57,11 @@ gboolean mct_session_limits_check_time_remaining (MctSessionLimits *limits, guint64 *time_remaining_secs_out, gboolean *time_limit_enabled_out); +GVariant *mct_session_limits_serialize (MctSessionLimits *limits); +MctSessionLimits *mct_session_limits_deserialize (GVariant *variant, + uid_t user_id, + GError **error); + /** * MctSessionLimitsBuilder: * 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); diff --git a/libmalcontent/tests/session-limits.c b/libmalcontent/tests/session-limits.c index 2559f3a..2a3e8bf 100644 --- a/libmalcontent/tests/session-limits.c +++ b/libmalcontent/tests/session-limits.c @@ -92,6 +92,84 @@ test_session_limits_check_time_remaining_invalid_time (void) g_assert_true (time_limit_enabled); } +/* Basic test of mct_session_limits_serialize() on session limits. */ +static void +test_session_limits_serialize (void) +{ + g_auto(MctSessionLimitsBuilder) builder = MCT_SESSION_LIMITS_BUILDER_INIT (); + g_autoptr(MctSessionLimits) limits = NULL; + g_autoptr(GVariant) serialized = NULL; + + /* Use an empty #MctSessionLimits. */ + limits = mct_session_limits_builder_end (&builder); + + /* We can’t assert anything about the serialisation format, since it’s opaque. */ + serialized = mct_session_limits_serialize (limits); + g_assert_nonnull (serialized); +} + +/* Basic test of mct_session_limits_deserialize() on various current and historic + * serialised app filter variants. */ +static void +test_session_limits_deserialize (void) +{ + /* These are all opaque. Older versions should be kept around to test + * backwards compatibility. */ + const gchar *valid_session_limits[] = + { + "@a{sv} {}", + "{ 'LimitType': <@u 0> }", + "{ 'LimitType': <@u 1>, 'DailySchedule': <(@u 0, @u 100)> }", + "{ 'DailySchedule': <(@u 0, @u 100)> }", + }; + + for (gsize i = 0; i < G_N_ELEMENTS (valid_session_limits); i++) + { + g_autoptr(GVariant) serialized = NULL; + g_autoptr(MctSessionLimits) limits = NULL; + g_autoptr(GError) local_error = NULL; + + g_test_message ("%" G_GSIZE_FORMAT ": %s", i, valid_session_limits[i]); + + serialized = g_variant_parse (NULL, valid_session_limits[i], NULL, NULL, NULL); + g_assert (serialized != NULL); + + limits = mct_session_limits_deserialize (serialized, 1, &local_error); + g_assert_no_error (local_error); + g_assert_nonnull (limits); + } +} + +/* Test of mct_session_limits_deserialize() on various invalid variants. */ +static void +test_session_limits_deserialize_invalid (void) +{ + const gchar *invalid_session_limits[] = + { + "false", + "()", + "{ 'LimitType': <@u 100> }", + "{ 'DailySchedule': <(@u 100, @u 0)> }", + "{ 'DailySchedule': <(@u 0, @u 4294967295)> }", + }; + + for (gsize i = 0; i < G_N_ELEMENTS (invalid_session_limits); i++) + { + g_autoptr(GVariant) serialized = NULL; + g_autoptr(MctSessionLimits) limits = NULL; + g_autoptr(GError) local_error = NULL; + + g_test_message ("%" G_GSIZE_FORMAT ": %s", i, invalid_session_limits[i]); + + serialized = g_variant_parse (NULL, invalid_session_limits[i], NULL, NULL, NULL); + g_assert (serialized != NULL); + + limits = mct_session_limits_deserialize (serialized, 1, &local_error); + g_assert_error (local_error, MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_INVALID_DATA); + g_assert_null (limits); + } +} + /* Fixture for tests which use an #MctSessionLimitsBuilder. The builder can * either be heap- or stack-allocated. @builder will always be a valid pointer * to it. @@ -1130,6 +1208,10 @@ main (int argc, g_test_add_func ("/session-limits/check-time-remaining/invalid-time", test_session_limits_check_time_remaining_invalid_time); + g_test_add_func ("/session-limits/serialize", test_session_limits_serialize); + g_test_add_func ("/session-limits/deserialize", test_session_limits_deserialize); + g_test_add_func ("/session-limits/deserialize/invalid", test_session_limits_deserialize_invalid); + g_test_add ("/session-limits/builder/stack/non-empty", BuilderFixture, NULL, builder_set_up_stack, test_session_limits_builder_non_empty, builder_tear_down_stack);