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 + 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') 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,