From d718d83c91bb5e9ae483954a603f9702f6a435ce Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Tue, 9 Oct 2018 14:16:41 +1300 Subject: [PATCH 1/3] accounts-service: Add storage of OARS filter data Signed-off-by: Philip Withnall https://phabricator.endlessm.com/T23999 --- ...om.endlessm.ParentalControls.AppFilter.xml | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/accounts-service/com.endlessm.ParentalControls.AppFilter.xml b/accounts-service/com.endlessm.ParentalControls.AppFilter.xml index 3efae8e..19f72db 100644 --- a/accounts-service/com.endlessm.ParentalControls.AppFilter.xml +++ b/accounts-service/com.endlessm.ParentalControls.AppFilter.xml @@ -31,5 +31,33 @@ + + + + + - \ No newline at end of file + From 6cc9b9bb27f2fcf44f599030eeeb4c9de6bddc15 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Tue, 9 Oct 2018 23:24:25 +1300 Subject: [PATCH 2/3] libeos-parental-controls: Add support for OARS filters The OARS filter for a user will allow the administrator to define the maximum levels of violence, alcohol, sex, location sharing, etc. that apps may have in order for the user to be allowed to see them in app listings or install them. Anything more intense will be hidden and uninstallable. Signed-off-by: Philip Withnall https://phabricator.endlessm.com/T23999 --- libeos-parental-controls/app-filter.c | 71 ++++++++++++++++++++++++++- libeos-parental-controls/app-filter.h | 32 ++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/libeos-parental-controls/app-filter.c b/libeos-parental-controls/app-filter.c index 7650a7b..d42231e 100644 --- a/libeos-parental-controls/app-filter.c +++ b/libeos-parental-controls/app-filter.c @@ -56,6 +56,8 @@ struct _EpcAppFilter gchar **app_list; /* (owned) (array zero-terminated=1) */ EpcAppFilterListType app_list_type; + + GVariant *oars_ratings; /* (type a{ss}) (owned) */ }; G_DEFINE_BOXED_TYPE (EpcAppFilter, epc_app_filter, @@ -101,6 +103,7 @@ epc_app_filter_unref (EpcAppFilter *filter) if (filter->ref_count <= 0) { g_strfreev (filter->app_list); + g_variant_unref (filter->oars_ratings); g_free (filter); } } @@ -159,7 +162,50 @@ epc_app_filter_is_path_allowed (EpcAppFilter *filter, } } -/* Check if @error is a D-Bus remote error mataching @expected_error_name. */ +/** + * epc_app_filter_get_oars_value: + * @filter: an #EpcAppFilter + * @oars_section: name of the OARS section to get the value from + * + * Get the value assigned to the given @oars_section in the OARS filter stored + * within @filter. If that section has no value explicitly defined, + * %EPC_APP_FILTER_OARS_VALUE_UNKNOWN is returned. + * + * This value is the most intense value allowed for apps to have in this + * section, inclusive. Any app with a more intense value for this section must + * be hidden from the user whose @filter this is. + * + * Returns: an #EpcAppFilterOarsValue + * Since: 0.1.0 + */ +EpcAppFilterOarsValue +epc_app_filter_get_oars_value (EpcAppFilter *filter, + const gchar *oars_section) +{ + const gchar *value_str; + + g_return_val_if_fail (filter != NULL, EPC_APP_FILTER_OARS_VALUE_UNKNOWN); + g_return_val_if_fail (filter->ref_count >= 1, + EPC_APP_FILTER_OARS_VALUE_UNKNOWN); + g_return_val_if_fail (oars_section != NULL && *oars_section != '\0', + EPC_APP_FILTER_OARS_VALUE_UNKNOWN); + + if (!g_variant_lookup (filter->oars_ratings, oars_section, "&s", &value_str)) + return EPC_APP_FILTER_OARS_VALUE_UNKNOWN; + + if (g_str_equal (value_str, "none")) + return EPC_APP_FILTER_OARS_VALUE_NONE; + else if (g_str_equal (value_str, "mild")) + return EPC_APP_FILTER_OARS_VALUE_MILD; + else if (g_str_equal (value_str, "moderate")) + return EPC_APP_FILTER_OARS_VALUE_MODERATE; + else if (g_str_equal (value_str, "intense")) + return EPC_APP_FILTER_OARS_VALUE_INTENSE; + else + return EPC_APP_FILTER_OARS_VALUE_UNKNOWN; +} + +/* 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) @@ -325,6 +371,9 @@ get_app_filter_cb (GObject *obj, g_autoptr(EpcAppFilter) app_filter = NULL; gboolean is_whitelist; g_auto(GStrv) app_list = NULL; + const gchar *content_rating_kind; + g_autoptr(GVariant) oars_variant = NULL; + g_autoptr(GHashTable) oars_map = NULL; GetAppFilterData *data = g_task_get_task_data (task); result_variant = g_dbus_connection_call_finish (connection, result, &local_error); @@ -350,6 +399,25 @@ get_app_filter_cb (GObject *obj, return; } + if (!g_variant_lookup (properties, "oars-filter", "(&s@a{ss})", + &content_rating_kind, &oars_variant)) + { + /* Default value. */ + content_rating_kind = "oars-1.0"; + oars_variant = g_variant_new ("@a{ss} {}"); + } + + /* Check that the OARS filter is in a format we support. Currently, that’s + * only oars-1.0. */ + if (!g_str_equal (content_rating_kind, "oars-1.0")) + { + g_task_return_new_error (task, EPC_APP_FILTER_ERROR, + EPC_APP_FILTER_ERROR_INVALID_DATA, + _("OARS filter for user %u has an unrecognized kind ‘%s’"), + data->user_id, content_rating_kind); + return; + } + /* Success. Create an #EpcAppFilter object to contain the results. */ app_filter = g_new0 (EpcAppFilter, 1); app_filter->ref_count = 1; @@ -357,6 +425,7 @@ get_app_filter_cb (GObject *obj, app_filter->app_list = g_steal_pointer (&app_list); app_filter->app_list_type = is_whitelist ? EPC_APP_FILTER_LIST_WHITELIST : EPC_APP_FILTER_LIST_BLACKLIST; + app_filter->oars_ratings = g_steal_pointer (&oars_variant); g_task_return_pointer (task, g_steal_pointer (&app_filter), (GDestroyNotify) epc_app_filter_unref); diff --git a/libeos-parental-controls/app-filter.h b/libeos-parental-controls/app-filter.h index e5b7a7d..71a7e4b 100644 --- a/libeos-parental-controls/app-filter.h +++ b/libeos-parental-controls/app-filter.h @@ -33,6 +33,8 @@ G_BEGIN_DECLS * @EPC_APP_FILTER_ERROR_INVALID_USER: Given user ID doesn’t exist * @EPC_APP_FILTER_ERROR_PERMISSION_DENIED: Not authorized to query the app * filter for the given user + * @EPC_APP_FILTER_ERROR_INVALID_DATA: The data stored in the app filter for + * a user is inconsistent or invalid * * Errors which can be returned by epc_get_app_filter_async(). * @@ -42,11 +44,38 @@ typedef enum { EPC_APP_FILTER_ERROR_INVALID_USER, EPC_APP_FILTER_ERROR_PERMISSION_DENIED, + EPC_APP_FILTER_ERROR_INVALID_DATA, } EpcAppFilterError; GQuark epc_app_filter_error_quark (void); #define EPC_APP_FILTER_ERROR epc_app_filter_error_quark () +/** + * EpcAppFilterOarsValue: + * @EPC_APP_FILTER_OARS_VALUE_UNKNOWN: Unknown value for the given + * section. + * @EPC_APP_FILTER_OARS_VALUE_NONE: No rating for the given section. + * @EPC_APP_FILTER_OARS_VALUE_MILD: Mild rating for the given section. + * @EPC_APP_FILTER_OARS_VALUE_MODERATE: Moderate rating for the given + * section. + * @EPC_APP_FILTER_OARS_VALUE_INTENSE: Intense rating for the given + * section. + * + * Rating values of the intensity of a given section in an app or game. + * These are directly equivalent to the values in the #AsContentRatingValue + * enumeration in libappstream. + * + * Since: 0.1.0 + */ +typedef enum +{ + EPC_APP_FILTER_OARS_VALUE_UNKNOWN, + EPC_APP_FILTER_OARS_VALUE_NONE, + EPC_APP_FILTER_OARS_VALUE_MILD, + EPC_APP_FILTER_OARS_VALUE_MODERATE, + EPC_APP_FILTER_OARS_VALUE_INTENSE, +} EpcAppFilterOarsValue; + /** * EpcAppFilter: * @@ -72,6 +101,9 @@ uid_t epc_app_filter_get_user_id (EpcAppFilter *filter); gboolean epc_app_filter_is_path_allowed (EpcAppFilter *filter, const gchar *path); +EpcAppFilterOarsValue epc_app_filter_get_oars_value (EpcAppFilter *filter, + const gchar *oars_section); + void epc_get_app_filter_async (GDBusConnection *connection, uid_t user_id, gboolean allow_interactive_authorization, From 8b2115a801a428217fb55d4f53c327d57fdbc211 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Tue, 9 Oct 2018 23:25:39 +1300 Subject: [PATCH 3/3] eos-parental-controls-client: Add OARS filter support This basic support will return the value of a given OARS section to the caller. Signed-off-by: Philip Withnall https://phabricator.endlessm.com/T23999 --- .../eos-parental-controls-client.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/eos-parental-controls-client/eos-parental-controls-client.py b/eos-parental-controls-client/eos-parental-controls-client.py index 4436513..ae4f5ba 100644 --- a/eos-parental-controls-client/eos-parental-controls-client.py +++ b/eos-parental-controls-client/eos-parental-controls-client.py @@ -95,6 +95,23 @@ def __lookup_user_id_or_error(user): raise SystemExit(EXIT_INVALID_OPTION) +def __oars_value_to_string(value): + """Convert an EosParentalControls.AppFilterOarsValue to a human-readable + string.""" + mapping = { + EosParentalControls.AppFilterOarsValue.UNKNOWN: "unknown", + EosParentalControls.AppFilterOarsValue.NONE: "none", + EosParentalControls.AppFilterOarsValue.MILD: "mild", + EosParentalControls.AppFilterOarsValue.MODERATE: "moderate", + EosParentalControls.AppFilterOarsValue.INTENSE: "intense", + } + + try: + return mapping[value] + except KeyError: + return "invalid (OARS value {})".format(value) + + def command_get(user, quiet=False, interactive=True): """Get the app filter for the given user.""" user_id = __lookup_user_id_or_error(user) @@ -121,6 +138,17 @@ def command_check(user, path, quiet=False, interactive=True): raise SystemExit(EXIT_PATH_NOT_ALLOWED) +def command_oars_section(user, section, quiet=False, interactive=True): + """Get the value of the given OARS section for the given user, according + to their OARS filter.""" + user_id = __lookup_user_id_or_error(user) + app_filter = __get_app_filter_or_error(user_id, interactive) + + value = app_filter.get_oars_value(section) + print('OARS section ‘{}’ for user {} has value ‘{}’'.format( + section, user_id, __oars_value_to_string(value))) + + def main(): # Parse command line arguments parser = argparse.ArgumentParser( @@ -164,6 +192,18 @@ def main(): parser_check.add_argument('path', help='path to a program to check') + # ‘oars-section’ command + parser_oars_section = subparsers.add_parser('oars-section', + parents=[common_parser], + help='get the value of a ' + 'given OARS section') + parser_oars_section.set_defaults(function=command_oars_section) + parser_oars_section.add_argument('user', default='', nargs='?', + help='user ID or username to get the ' + 'OARS filter for (default: current ' + 'user)') + parser_oars_section.add_argument('section', help='OARS section to get') + # Parse the command line arguments and run the subcommand. args = parser.parse_args() args_dict = dict((k, v) for k, v in vars(args).items() if k != 'function')