diff --git a/libmalcontent/manager.c b/libmalcontent/manager.c index 3bed6a3..62c340e 100644 --- a/libmalcontent/manager.c +++ b/libmalcontent/manager.c @@ -738,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); @@ -791,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, @@ -801,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, @@ -973,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); @@ -992,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, @@ -1022,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 @@ -1047,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/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);