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,