From bbd1b2bdff07a711902a4beb9c7ce64429b58e9e Mon Sep 17 00:00:00 2001 From: Andre Moreira Magalhaes Date: Wed, 12 Jun 2019 21:28:56 -0300 Subject: [PATCH 1/7] libmalcontent: Rename app filter paths_blacklist member to blacklist The filter blacklist also holds information on flatpak refs that are blacklisted (apart from paths), so lets rename it for clarity. Signed-off-by: Andre Moreira Magalhaes --- libmalcontent/app-filter.c | 39 ++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/libmalcontent/app-filter.c b/libmalcontent/app-filter.c index 8d15810..624d14e 100644 --- a/libmalcontent/app-filter.c +++ b/libmalcontent/app-filter.c @@ -124,8 +124,12 @@ mct_app_filter_is_path_allowed (MctAppFilter *filter, g_return_val_if_fail (g_path_is_absolute (path), FALSE); g_autofree gchar *canonical_path = g_canonicalize_filename (path, "/"); + g_autofree gchar *canonical_path_utf8 = g_filename_to_utf8 (canonical_path, -1, + NULL, NULL, NULL); + g_return_val_if_fail (canonical_path_utf8 != NULL, FALSE); + gboolean path_in_list = g_strv_contains ((const gchar * const *) filter->app_list, - canonical_path); + canonical_path_utf8); switch (filter->app_list_type) { @@ -433,7 +437,7 @@ mct_app_filter_is_system_installation_allowed (MctAppFilter *filter) */ typedef struct { - GPtrArray *paths_blacklist; /* (nullable) (owned) (element-type filename) */ + GPtrArray *blacklist; /* (nullable) (owned) (element-type utf8) */ GHashTable *oars; /* (nullable) (owned) (element-type utf8 MctAppFilterOarsValue) */ gboolean allow_user_installation; gboolean allow_system_installation; @@ -471,7 +475,7 @@ mct_app_filter_builder_init (MctAppFilterBuilder *builder) MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder; g_return_if_fail (_builder != NULL); - g_return_if_fail (_builder->paths_blacklist == NULL); + g_return_if_fail (_builder->blacklist == NULL); g_return_if_fail (_builder->oars == NULL); memcpy (builder, &local_builder, sizeof (local_builder)); @@ -497,7 +501,7 @@ mct_app_filter_builder_clear (MctAppFilterBuilder *builder) g_return_if_fail (_builder != NULL); - g_clear_pointer (&_builder->paths_blacklist, g_ptr_array_unref); + g_clear_pointer (&_builder->blacklist, g_ptr_array_unref); g_clear_pointer (&_builder->oars, g_hash_table_unref); } @@ -547,8 +551,8 @@ mct_app_filter_builder_copy (MctAppFilterBuilder *builder) _copy = (MctAppFilterBuilderReal *) copy; mct_app_filter_builder_clear (copy); - if (_builder->paths_blacklist != NULL) - _copy->paths_blacklist = g_ptr_array_ref (_builder->paths_blacklist); + if (_builder->blacklist != NULL) + _copy->blacklist = g_ptr_array_ref (_builder->blacklist); if (_builder->oars != NULL) _copy->oars = g_hash_table_ref (_builder->oars); _copy->allow_user_installation = _builder->allow_user_installation; @@ -598,11 +602,11 @@ mct_app_filter_builder_end (MctAppFilterBuilder *builder) g_autoptr(GVariant) oars_variant = NULL; g_return_val_if_fail (_builder != NULL, NULL); - g_return_val_if_fail (_builder->paths_blacklist != NULL, NULL); + g_return_val_if_fail (_builder->blacklist != NULL, NULL); g_return_val_if_fail (_builder->oars != NULL, NULL); /* Ensure the paths list is %NULL-terminated. */ - g_ptr_array_add (_builder->paths_blacklist, NULL); + g_ptr_array_add (_builder->blacklist, NULL); /* Build the OARS variant. */ g_hash_table_iter_init (&iter, _builder->oars); @@ -633,7 +637,7 @@ mct_app_filter_builder_end (MctAppFilterBuilder *builder) app_filter = g_new0 (MctAppFilter, 1); app_filter->ref_count = 1; app_filter->user_id = -1; - app_filter->app_list = (gchar **) g_ptr_array_free (g_steal_pointer (&_builder->paths_blacklist), FALSE); + app_filter->app_list = (gchar **) g_ptr_array_free (g_steal_pointer (&_builder->blacklist), FALSE); app_filter->app_list_type = MCT_APP_FILTER_LIST_BLACKLIST; app_filter->oars_ratings = g_steal_pointer (&oars_variant); app_filter->allow_user_installation = _builder->allow_user_installation; @@ -662,15 +666,18 @@ mct_app_filter_builder_blacklist_path (MctAppFilterBuilder *builder, MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder; g_return_if_fail (_builder != NULL); - g_return_if_fail (_builder->paths_blacklist != NULL); + g_return_if_fail (_builder->blacklist != NULL); g_return_if_fail (path != NULL); g_return_if_fail (g_path_is_absolute (path)); g_autofree gchar *canonical_path = g_canonicalize_filename (path, "/"); + g_autofree gchar *canonical_path_utf8 = g_filename_to_utf8 (canonical_path, -1, + NULL, NULL, NULL); + g_return_if_fail (canonical_path_utf8 != NULL); - if (!g_ptr_array_find_with_equal_func (_builder->paths_blacklist, - canonical_path, g_str_equal, NULL)) - g_ptr_array_add (_builder->paths_blacklist, g_steal_pointer (&canonical_path)); + if (!g_ptr_array_find_with_equal_func (_builder->blacklist, + canonical_path_utf8, g_str_equal, NULL)) + g_ptr_array_add (_builder->blacklist, g_steal_pointer (&canonical_path_utf8)); } /** @@ -691,12 +698,12 @@ mct_app_filter_builder_blacklist_flatpak_ref (MctAppFilterBuilder *builder, MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder; g_return_if_fail (_builder != NULL); - g_return_if_fail (_builder->paths_blacklist != NULL); + g_return_if_fail (_builder->blacklist != NULL); g_return_if_fail (app_ref != NULL); - if (!g_ptr_array_find_with_equal_func (_builder->paths_blacklist, + if (!g_ptr_array_find_with_equal_func (_builder->blacklist, app_ref, g_str_equal, NULL)) - g_ptr_array_add (_builder->paths_blacklist, g_strdup (app_ref)); + g_ptr_array_add (_builder->blacklist, g_strdup (app_ref)); } /** From da0e63fe99fd2b648f46cc899fc01544ae7d10db Mon Sep 17 00:00:00 2001 From: Andre Moreira Magalhaes Date: Wed, 12 Jun 2019 21:34:43 -0300 Subject: [PATCH 2/7] libmalcontent: Add support for filtering by content type This is useful for example if blacklisting all apps that can handle certain content types is desired. Signed-off-by: Andre Moreira Magalhaes --- libmalcontent/app-filter.c | 138 +++++++++++++++++++++++++++++++++++-- libmalcontent/app-filter.h | 8 ++- 2 files changed, 141 insertions(+), 5 deletions(-) diff --git a/libmalcontent/app-filter.c b/libmalcontent/app-filter.c index 624d14e..266b3d6 100644 --- a/libmalcontent/app-filter.c +++ b/libmalcontent/app-filter.c @@ -1,6 +1,6 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * - * Copyright © 2018 Endless Mobile, Inc. + * Copyright © 2018-2019 Endless Mobile, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,6 +18,7 @@ * * Authors: * - Philip Withnall + * - Andre Moreira Magalhaes */ #include "config.h" @@ -142,6 +143,34 @@ mct_app_filter_is_path_allowed (MctAppFilter *filter, } } +/* Check whether a given @ref is a valid flatpak ref. + * + * For simplicity and to avoid duplicating the whole logic behind + * flatpak_ref_parse() this method will only check whether: + * - the @ref contains exactly 3 slash chars + * - the @ref starts with either app/ or runtime/ + * - the name, arch and branch components of the @ref are not empty + * + * We avoid using flatpak_ref_parse() to allow for libflatpak + * to depend on malcontent without causing a cyclic dependency. + */ +static gboolean +is_valid_flatpak_ref (const gchar *ref) +{ + g_auto(GStrv) parts = NULL; + + if (ref == NULL) + return FALSE; + + parts = g_strsplit (ref, "/", 0); + return (g_strv_length (parts) == 4 && + (strcmp (parts[0], "app") == 0 || + strcmp (parts[0], "runtime") == 0) && + *parts[1] != '\0' && + *parts[2] != '\0' && + *parts[3] != '\0'); +} + /** * mct_app_filter_is_flatpak_ref_allowed: * @filter: an #MctAppFilter @@ -161,6 +190,7 @@ mct_app_filter_is_flatpak_ref_allowed (MctAppFilter *filter, g_return_val_if_fail (filter != NULL, FALSE); g_return_val_if_fail (filter->ref_count >= 1, FALSE); g_return_val_if_fail (app_ref != NULL, FALSE); + g_return_val_if_fail (is_valid_flatpak_ref (app_ref), FALSE); gboolean ref_in_list = g_strv_contains ((const gchar * const *) filter->app_list, app_ref); @@ -205,9 +235,8 @@ mct_app_filter_is_flatpak_app_allowed (MctAppFilter *filter, gboolean id_in_list = FALSE; for (gsize i = 0; filter->app_list[i] != NULL; i++) { - /* Avoid using flatpak_ref_parse() to avoid a dependency on libflatpak - * just for that one function. */ - if (g_str_has_prefix (filter->app_list[i], "app/") && + if (is_valid_flatpak_ref (filter->app_list[i]) && + g_str_has_prefix (filter->app_list[i], "app/") && strncmp (filter->app_list[i] + strlen ("app/"), app_id, app_id_len) == 0 && filter->app_list[i][strlen ("app/") + app_id_len] == '/') { @@ -246,6 +275,7 @@ mct_app_filter_is_appinfo_allowed (MctAppFilter *filter, GAppInfo *app_info) { g_autofree gchar *abs_path = NULL; + const gchar * const *types = NULL; g_return_val_if_fail (filter != NULL, FALSE); g_return_val_if_fail (filter->ref_count >= 1, FALSE); @@ -257,6 +287,13 @@ mct_app_filter_is_appinfo_allowed (MctAppFilter *filter, !mct_app_filter_is_path_allowed (filter, abs_path)) return FALSE; + types = g_app_info_get_supported_types (app_info); + for (gsize i = 0; types != NULL && types[i] != NULL; i++) + { + if (!mct_app_filter_is_content_type_allowed (filter, types[i])) + return FALSE; + } + if (G_IS_DESKTOP_APP_INFO (app_info)) { g_autofree gchar *flatpak_app = NULL; @@ -296,6 +333,67 @@ mct_app_filter_is_appinfo_allowed (MctAppFilter *filter, return TRUE; } +/* Check whether a given @content_type is valid. + * + * For simplicity this method will only check whether: + * - the @content_type contains exactly 1 slash char + * - the @content_type does not start with a slash char + * - the type and subtype components of the @content_type are not empty + */ +static gboolean +is_valid_content_type (const gchar *content_type) +{ + g_auto(GStrv) parts = NULL; + + if (content_type == NULL) + return FALSE; + + parts = g_strsplit (content_type, "/", 0); + return (g_strv_length (parts) == 2 && + *parts[0] != '\0' && + *parts[1] != '\0'); +} + +/** + * mct_app_filter_is_content_type_allowed: + * @filter: an #MctAppFilter + * @content_type: content type to check + * + * Check whether apps handling the given @content_type are allowed to be run + * according to this app filter. + * + * Note that this method doesn’t match content subtypes. For example, if + * `application/xml` is added to the blacklist but `application/xspf+xml` is not, + * a check for whether `application/xspf+xml` is blacklisted would return false. + * + * Returns: %TRUE if the user this @filter corresponds to is allowed to run + * programs handling @content_type according to the @filter policy; + * %FALSE otherwise + * Since: 0.4.0 + */ +gboolean +mct_app_filter_is_content_type_allowed (MctAppFilter *filter, + const gchar *content_type) +{ + g_return_val_if_fail (filter != NULL, FALSE); + g_return_val_if_fail (filter->ref_count >= 1, FALSE); + g_return_val_if_fail (content_type != NULL, FALSE); + g_return_val_if_fail (is_valid_content_type (content_type), FALSE); + + gboolean ref_in_list = g_strv_contains ((const gchar * const *) filter->app_list, + content_type); + + switch (filter->app_list_type) + { + case MCT_APP_FILTER_LIST_BLACKLIST: + return !ref_in_list; + case MCT_APP_FILTER_LIST_WHITELIST: + return ref_in_list; + default: + g_assert_not_reached (); + } +} + static gint strcmp_cb (gconstpointer a, gconstpointer b) @@ -700,12 +798,44 @@ mct_app_filter_builder_blacklist_flatpak_ref (MctAppFilterBuilder *builder, g_return_if_fail (_builder != NULL); g_return_if_fail (_builder->blacklist != NULL); g_return_if_fail (app_ref != NULL); + g_return_if_fail (is_valid_flatpak_ref (app_ref)); if (!g_ptr_array_find_with_equal_func (_builder->blacklist, app_ref, g_str_equal, NULL)) g_ptr_array_add (_builder->blacklist, g_strdup (app_ref)); } +/** + * mct_app_filter_builder_blacklist_content_type: + * @builder: an initialised #MctAppFilterBuilder + * @content_type: a content type to blacklist + * + * Add @content_type to the blacklist of content types in the filter under + * construction. The @content_type will not be added again if it’s already been + * added. + * + * Note that this method doesn’t handle content subtypes. For example, if + * `application/xml` is added to the blacklist but `application/xspf+xml` is not, + * a check for whether `application/xspf+xml` is blacklisted would return false. + * + * Since: 0.4.0 + */ +void +mct_app_filter_builder_blacklist_content_type (MctAppFilterBuilder *builder, + const gchar *content_type) +{ + MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder; + + g_return_if_fail (_builder != NULL); + g_return_if_fail (_builder->blacklist != NULL); + g_return_if_fail (content_type != NULL); + g_return_if_fail (is_valid_content_type (content_type)); + + if (!g_ptr_array_find_with_equal_func (_builder->blacklist, + content_type, g_str_equal, NULL)) + g_ptr_array_add (_builder->blacklist, g_strdup (content_type)); +} + /** * mct_app_filter_builder_set_oars_value: * @builder: an initialised #MctAppFilterBuilder diff --git a/libmalcontent/app-filter.h b/libmalcontent/app-filter.h index 5b7573b..263b4ee 100644 --- a/libmalcontent/app-filter.h +++ b/libmalcontent/app-filter.h @@ -1,6 +1,6 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * - * Copyright © 2018 Endless Mobile, Inc. + * Copyright © 2018-2019 Endless Mobile, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,6 +18,7 @@ * * Authors: * - Philip Withnall + * - Andre Moreira Magalhaes */ #pragma once @@ -109,6 +110,8 @@ gboolean mct_app_filter_is_flatpak_app_allowed (MctAppFilter *filter, const gchar *app_id); gboolean mct_app_filter_is_appinfo_allowed (MctAppFilter *filter, GAppInfo *app_info); +gboolean mct_app_filter_is_content_type_allowed (MctAppFilter *filter, + const gchar *content_type); const gchar **mct_app_filter_get_oars_sections (MctAppFilter *filter); MctAppFilterOarsValue mct_app_filter_get_oars_value (MctAppFilter *filter, @@ -182,6 +185,9 @@ void mct_app_filter_builder_blacklist_path (MctAppFilterBuilder *builde const gchar *path); void mct_app_filter_builder_blacklist_flatpak_ref (MctAppFilterBuilder *builder, const gchar *app_ref); +void mct_app_filter_builder_blacklist_content_type (MctAppFilterBuilder *builder, + const gchar *content_type); + void mct_app_filter_builder_set_oars_value (MctAppFilterBuilder *builder, const gchar *oars_section, MctAppFilterOarsValue value); From 3800cd3818a807ef5068c7b2acf3eec734e90863 Mon Sep 17 00:00:00 2001 From: Andre Moreira Magalhaes Date: Thu, 13 Jun 2019 01:08:53 +0000 Subject: [PATCH 3/7] libmalcontent: Add tests for filtering by content type Signed-off-by: Andre Moreira Magalhaes --- libmalcontent/tests/app-filter.c | 78 ++++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/libmalcontent/tests/app-filter.c b/libmalcontent/tests/app-filter.c index 7eecaf4..5b7d284 100644 --- a/libmalcontent/tests/app-filter.c +++ b/libmalcontent/tests/app-filter.c @@ -155,6 +155,9 @@ test_app_filter_builder_non_empty (BuilderFixture *fixture, mct_app_filter_builder_blacklist_flatpak_ref (fixture->builder, "app/org.doom.Doom/x86_64/master"); + mct_app_filter_builder_blacklist_content_type (fixture->builder, + "x-scheme-handler/http"); + mct_app_filter_builder_set_oars_value (fixture->builder, "drugs-alcohol", MCT_APP_FILTER_OARS_VALUE_MILD); mct_app_filter_builder_set_oars_value (fixture->builder, "language-humor", @@ -175,6 +178,11 @@ test_app_filter_builder_non_empty (BuilderFixture *fixture, "app/org.doom.Doom/x86_64/master")); g_assert_false (mct_app_filter_is_flatpak_app_allowed (filter, "org.doom.Doom")); + g_assert_false (mct_app_filter_is_content_type_allowed (filter, + "x-scheme-handler/http")); + g_assert_true (mct_app_filter_is_content_type_allowed (filter, + "text/plain")); + g_assert_cmpint (mct_app_filter_get_oars_value (filter, "drugs-alcohol"), ==, MCT_APP_FILTER_OARS_VALUE_MILD); g_assert_cmpint (mct_app_filter_get_oars_value (filter, "language-humor"), ==, @@ -211,6 +219,9 @@ test_app_filter_builder_empty (BuilderFixture *fixture, "app/org.doom.Doom/x86_64/master")); g_assert_true (mct_app_filter_is_flatpak_app_allowed (filter, "org.doom.Doom")); + g_assert_true (mct_app_filter_is_content_type_allowed (filter, + "x-scheme-handler/http")); + g_assert_cmpint (mct_app_filter_get_oars_value (filter, "drugs-alcohol"), ==, MCT_APP_FILTER_OARS_VALUE_UNKNOWN); g_assert_cmpint (mct_app_filter_get_oars_value (filter, "language-humor"), ==, @@ -240,11 +251,16 @@ test_app_filter_builder_copy_empty (void) mct_app_filter_builder_init (builder_copy); mct_app_filter_builder_blacklist_path (builder_copy, "/bin/true"); + mct_app_filter_builder_blacklist_content_type (builder_copy, + "x-scheme-handler/http"); filter = mct_app_filter_builder_end (builder_copy); g_assert_true (mct_app_filter_is_path_allowed (filter, "/bin/false")); g_assert_false (mct_app_filter_is_path_allowed (filter, "/bin/true")); - + g_assert_true (mct_app_filter_is_content_type_allowed (filter, + "text/plain")); + g_assert_false (mct_app_filter_is_content_type_allowed (filter, + "x-scheme-handler/http")); g_assert_true (mct_app_filter_is_user_installation_allowed (filter)); g_assert_false (mct_app_filter_is_system_installation_allowed (filter)); } @@ -259,6 +275,8 @@ test_app_filter_builder_copy_full (void) g_autoptr(MctAppFilter) filter = NULL; mct_app_filter_builder_blacklist_path (builder, "/bin/true"); + mct_app_filter_builder_blacklist_content_type (builder, + "x-scheme-handler/http"); mct_app_filter_builder_set_allow_user_installation (builder, FALSE); mct_app_filter_builder_set_allow_system_installation (builder, TRUE); builder_copy = mct_app_filter_builder_copy (builder); @@ -266,6 +284,10 @@ test_app_filter_builder_copy_full (void) g_assert_true (mct_app_filter_is_path_allowed (filter, "/bin/false")); g_assert_false (mct_app_filter_is_path_allowed (filter, "/bin/true")); + g_assert_true (mct_app_filter_is_content_type_allowed (filter, + "text/plain")); + g_assert_false (mct_app_filter_is_content_type_allowed (filter, + "x-scheme-handler/http")); g_assert_false (mct_app_filter_is_user_installation_allowed (filter)); g_assert_true (mct_app_filter_is_system_installation_allowed (filter)); } @@ -290,42 +312,56 @@ test_app_filter_appinfo (void) "Name=Some Name\n" "Exec=/bin/true\n" "Type=Application\n" }, - /* Allowed by its path and its flatpak ID: */ + /* Allowed by its path and its content type: */ { TRUE, "[Desktop Entry]\n" "Name=Some Name\n" "Exec=/bin/true\n" "Type=Application\n" + "MimeType=text/plain\n" }, + /* Allowed by its path, its content type and its flatpak ID: */ + { TRUE, + "[Desktop Entry]\n" + "Name=Some Name\n" + "Exec=/bin/true\n" + "Type=Application\n" + "MimeType=text/plain\n" "X-Flatpak=org.gnome.Nice\n" }, - /* Allowed by its path and its flatpak ID: */ + /* Allowed by its path, its content type and its flatpak ID: */ { TRUE, "[Desktop Entry]\n" "Name=Some Name\n" "Exec=/bin/true\n" "Type=Application\n" + "MimeType=text/plain\n" "X-Flatpak=org.gnome.Nice\n" "X-Flatpak-RenamedFrom=\n" }, - /* Allowed by its path, its flatpak ID and its old flatpak IDs: */ + /* Allowed by its path, its content type, its flatpak ID and + * its old flatpak IDs: */ { TRUE, "[Desktop Entry]\n" "Name=Some Name\n" "Exec=/bin/true\n" "Type=Application\n" + "MimeType=text/plain\n" "X-Flatpak-RenamedFrom=org.gnome.OldNice\n" }, - /* Allowed by its path, its flatpak ID and its old flatpak IDs (which - * contain some spurious entries): */ + /* Allowed by its path, its content type, its flatpak ID and its old + * flatpak IDs (which contain some spurious entries): */ { TRUE, "[Desktop Entry]\n" "Name=Some Name\n" "Exec=/bin/true\n" "Type=Application\n" + "MimeType=text/plain\n" "X-Flatpak-RenamedFrom=org.gnome.OldNice;;;\n" }, - /* Allowed by its path, its flatpak ID and its old flatpak IDs: */ + /* Allowed by its path, its content type, its flatpak ID and + * its old flatpak IDs: */ { TRUE, "[Desktop Entry]\n" "Name=Some Name\n" "Exec=/bin/true\n" "Type=Application\n" + "MimeType=text/plain\n" "X-Flatpak-RenamedFrom=org.gnome.OldNice.desktop\n" }, /* Disallowed by its path: */ { FALSE, @@ -333,6 +369,13 @@ test_app_filter_appinfo (void) "Name=Some Name\n" "Exec=/bin/false\n" "Type=Application\n" }, + /* Allowed by its path, disallowed by its content type: */ + { FALSE, + "[Desktop Entry]\n" + "Name=Some Name\n" + "Exec=/bin/true\n" + "Type=Application\n" + "MimeType=x-scheme-handler/http\n" }, /* Allowed by its path, disallowed by its flatpak ID: */ { FALSE, "[Desktop Entry]\n" @@ -358,10 +401,21 @@ test_app_filter_appinfo (void) "Type=Application\n" "X-Flatpak=org.gnome.WasNasty\n" "X-Flatpak-RenamedFrom=org.gnome.Nasty.desktop;\n" }, + /* Allowed by its path, current flatpak ID, old flatpak ID, but + * disabled by content type: */ + { FALSE, + "[Desktop Entry]\n" + "Name=Some Name\n" + "Exec=/bin/true\n" + "Type=Application\n" + "X-Flatpak=org.gnome.WasNasty\n" + "X-Flatpak-RenamedFrom=org.gnome.OldNice\n" + "MimeType=x-scheme-handler/http\n" }, }; mct_app_filter_builder_blacklist_path (&builder, "/bin/false"); mct_app_filter_builder_blacklist_flatpak_ref (&builder, "app/org.gnome.Nasty/x86_64/stable"); + mct_app_filter_builder_blacklist_content_type (&builder, "x-scheme-handler/http"); filter = mct_app_filter_builder_end (&builder); @@ -584,7 +638,8 @@ test_app_filter_bus_get_whitelist (BusFixture *fixture, "'AppFilter': <(true, [" "'app/org.gnome.Whitelisted1/x86_64/stable'," "'app/org.gnome.Whitelisted2/x86_64/stable'," - "'/usr/bin/true'" + "'/usr/bin/true'," + "'text/plain'" "])>," "'OarsFilter': <('oars-1.1', @a{ss} {})>" "}" @@ -611,6 +666,10 @@ test_app_filter_bus_get_whitelist (BusFixture *fixture, g_assert_false (mct_app_filter_is_flatpak_ref_allowed (app_filter, "app/org.gnome.Whitelisted1/x86_64/unknown")); g_assert_true (mct_app_filter_is_path_allowed (app_filter, "/usr/bin/true")); g_assert_false (mct_app_filter_is_path_allowed (app_filter, "/usr/bin/false")); + g_assert_true (mct_app_filter_is_content_type_allowed (app_filter, + "text/plain")); + g_assert_false (mct_app_filter_is_content_type_allowed (app_filter, + "x-scheme-handler/http")); } /* Test that getting an #MctAppFilter containing all possible OARS values from @@ -1097,7 +1156,7 @@ test_app_filter_bus_set (BusFixture *fixture, const SetAppFilterData set_app_filter_data = { .expected_uid = fixture->valid_uid, - .expected_app_filter_value = "(false, ['/usr/bin/false', '/usr/bin/banned', 'app/org.gnome.Nasty/x86_64/stable'])", + .expected_app_filter_value = "(false, ['/usr/bin/false', '/usr/bin/banned', 'app/org.gnome.Nasty/x86_64/stable', 'x-scheme-handler/http'])", .expected_oars_filter_value = "('oars-1.1', { 'violence-fantasy': 'intense' })", .expected_allow_user_installation_value = "true", .expected_allow_system_installation_value = "true", @@ -1108,6 +1167,7 @@ test_app_filter_bus_set (BusFixture *fixture, mct_app_filter_builder_blacklist_path (&builder, "/usr/bin/false"); mct_app_filter_builder_blacklist_path (&builder, "/usr/bin/banned"); mct_app_filter_builder_blacklist_flatpak_ref (&builder, "app/org.gnome.Nasty/x86_64/stable"); + mct_app_filter_builder_blacklist_content_type (&builder, "x-scheme-handler/http"); mct_app_filter_builder_set_oars_value (&builder, "violence-fantasy", MCT_APP_FILTER_OARS_VALUE_INTENSE); mct_app_filter_builder_set_allow_user_installation (&builder, TRUE); mct_app_filter_builder_set_allow_system_installation (&builder, TRUE); From 6c7c386ce2f493be23d8936afdd04c34959d9f48 Mon Sep 17 00:00:00 2001 From: Andre Moreira Magalhaes Date: Thu, 13 Jun 2019 01:44:51 +0000 Subject: [PATCH 4/7] malcontent-client: Disambiguate usage of path The cmdline arguments may refer to both paths or flatpak refs so lets disambiguate here for clarity. Signed-off-by: Andre Moreira Magalhaes --- malcontent-client/malcontent-client.py | 34 ++++++++++++++------------ 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/malcontent-client/malcontent-client.py b/malcontent-client/malcontent-client.py index 6544d3f..d78c3f8 100644 --- a/malcontent-client/malcontent-client.py +++ b/malcontent-client/malcontent-client.py @@ -194,36 +194,36 @@ def command_monitor(user, quiet=False, interactive=True): break -def command_check(user, path, quiet=False, interactive=True): - """Check the given path or flatpak ref is runnable by the given user, - according to their app filter.""" +def command_check(user, arg, quiet=False, interactive=True): + """Check the given path or flatpak ref is runnable by the + given user, according to their app filter.""" user_id = __lookup_user_id_or_error(user) app_filter = __get_app_filter_or_error(user_id, interactive) - if path.startswith('app/') and path.count('/') < 3: + if arg.startswith('app/') and arg.count('/') < 3: # Flatpak app ID - path = path[4:] - is_allowed = app_filter.is_flatpak_app_allowed(path) + arg = arg[4:] + is_allowed = app_filter.is_flatpak_app_allowed(arg) noun = 'Flatpak app ID' - elif path.startswith('app/') or path.startswith('runtime/'): + elif arg.startswith('app/') or arg.startswith('runtime/'): # Flatpak ref - is_allowed = app_filter.is_flatpak_ref_allowed(path) + is_allowed = app_filter.is_flatpak_ref_allowed(arg) noun = 'Flatpak ref' else: # File system path - path = os.path.abspath(path) - is_allowed = app_filter.is_path_allowed(path) + arg = os.path.abspath(arg) + is_allowed = app_filter.is_path_allowed(arg) noun = 'Path' if is_allowed: if not quiet: print('{} {} is allowed by app filter for user {}'.format( - noun, path, user_id)) + noun, arg, user_id)) return else: if not quiet: print('{} {} is not allowed by app filter for user {}'.format( - noun, path, user_id)) + noun, arg, user_id)) raise SystemExit(EXIT_PATH_NOT_ALLOWED) @@ -312,14 +312,15 @@ def main(): # ‘check’ command parser_check = subparsers.add_parser('check', parents=[common_parser], - help='check whether a path is ' + help='check whether a path or ' + 'flatpak ref is ' 'allowed by app filter') parser_check.set_defaults(function=command_check) parser_check.add_argument('user', default='', nargs='?', help='user ID or username to get the app filter ' 'for (default: current user)') - parser_check.add_argument('path', - help='path to a program to check') + parser_check.add_argument('arg', + help='path to a program or flatpak ref to check') # ‘oars-section’ command parser_oars_section = subparsers.add_parser('oars-section', @@ -362,7 +363,8 @@ def main(): help='unconditionally disallow installation to ' 'the system flatpak repo') parser_set.add_argument('app_filter_args', nargs='*', - help='paths to blacklist and OARS section=value ' + help='paths or flatpak refs to ' + 'blacklist and OARS section=value ' 'pairs to store') parser_set.set_defaults(allow_user_installation=True, allow_system_installation=False) From 2846db65291674f683365f1bc2b87c99066a658f Mon Sep 17 00:00:00 2001 From: Andre Moreira Magalhaes Date: Fri, 14 Jun 2019 01:00:51 +0000 Subject: [PATCH 5/7] malcontent-client: Refactor logic to determine type of argument for check/set Note that this change breaks backward compatibility when handling flatpak refs/IDs if the passed argument also resolves to a valid path, in which case an exception will be raised. Signed-off-by: Andre Moreira Magalhaes --- malcontent-client/malcontent-client.py | 61 +++++++++++++++++++++----- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/malcontent-client/malcontent-client.py b/malcontent-client/malcontent-client.py index d78c3f8..25fdf05 100644 --- a/malcontent-client/malcontent-client.py +++ b/malcontent-client/malcontent-client.py @@ -194,26 +194,50 @@ def command_monitor(user, quiet=False, interactive=True): break +# Simple check to check whether @arg is a valid flatpak ref - it uses the +# same logic as 'MctAppFilter' to determine it and should be kept in sync +# with its implementation +def is_valid_flatpak_ref(arg): + parts = arg.split('/') + return (len(parts) == 4 and \ + (parts[0] == 'app' or parts[0] == 'runtime') and \ + parts[1] != '' and parts[2] != '' and parts[3] != '') + + def command_check(user, arg, quiet=False, interactive=True): - """Check the given path or flatpak ref is runnable by the + """Check the given path, content type or flatpak ref is runnable by the given user, according to their app filter.""" user_id = __lookup_user_id_or_error(user) app_filter = __get_app_filter_or_error(user_id, interactive) - if arg.startswith('app/') and arg.count('/') < 3: + is_maybe_flatpak_id = arg.startswith('app/') and arg.count('/') < 3 + is_maybe_flatpak_ref = is_valid_flatpak_ref(arg) + is_maybe_path = os.path.exists(arg) + + recognised_types = sum([is_maybe_flatpak_id, is_maybe_flatpak_ref, + is_maybe_path]) + if recognised_types == 0: + print('Unknown argument ‘{}’'.format(arg), file=sys.stderr) + raise SystemExit(EXIT_INVALID_OPTION) + elif recognised_types > 1: + print('Ambiguous argument ‘{}’ recognised as multiple types'.format(arg), + file=sys.stderr) + raise SystemExit(EXIT_INVALID_OPTION) + elif is_maybe_flatpak_id: # Flatpak app ID arg = arg[4:] is_allowed = app_filter.is_flatpak_app_allowed(arg) noun = 'Flatpak app ID' - elif arg.startswith('app/') or arg.startswith('runtime/'): + elif is_maybe_flatpak_ref: # Flatpak ref is_allowed = app_filter.is_flatpak_ref_allowed(arg) noun = 'Flatpak ref' - else: - # File system path - arg = os.path.abspath(arg) - is_allowed = app_filter.is_path_allowed(arg) + elif is_maybe_path: + path = os.path.abspath(arg) + is_allowed = app_filter.is_path_allowed(path) noun = 'Path' + else: + raise AssertionError('code should not be reached') if is_allowed: if not quiet: @@ -257,10 +281,27 @@ def command_set(user, allow_user_installation=True, file=sys.stderr) raise SystemExit(EXIT_INVALID_OPTION) builder.set_oars_value(section, value) - elif arg.startswith('app/') or arg.startswith('runtime/'): - builder.blacklist_flatpak_ref(arg) else: - builder.blacklist_path(arg) + is_maybe_flatpak_ref = is_valid_flatpak_ref(arg) + is_maybe_path = os.path.exists(arg) + + recognised_types = sum([is_maybe_flatpak_ref, + is_maybe_path]) + if recognised_types == 0: + print('Unknown argument ‘{}’'.format(arg), file=sys.stderr) + raise SystemExit(EXIT_INVALID_OPTION) + elif recognised_types > 1: + print('Ambiguous argument ‘{}’ recognised as multiple types'.format(arg), + file=sys.stderr) + raise SystemExit(EXIT_INVALID_OPTION) + elif is_maybe_flatpak_ref: + builder.blacklist_flatpak_ref(arg) + elif is_maybe_path: + path = os.path.abspath(arg) + builder.blacklist_path(path) + else: + raise AssertionError('code should not be reached') + app_filter = builder.end() __set_app_filter_or_error(user_id, app_filter, interactive) From 9f39878c934d9674bb2e02890625cbbcf954775b Mon Sep 17 00:00:00 2001 From: Andre Moreira Magalhaes Date: Thu, 13 Jun 2019 01:48:29 +0000 Subject: [PATCH 6/7] malcontent-client: Allow getting/setting filters by content type Note that this change breaks backward compatibility when handling content types if the passed argument also resolves to a valid path, in which case an exception will be raised. Signed-off-by: Andre Moreira Magalhaes --- malcontent-client/malcontent-client.py | 33 +++++++++++++++++++++----- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/malcontent-client/malcontent-client.py b/malcontent-client/malcontent-client.py index 25fdf05..5be8eb5 100644 --- a/malcontent-client/malcontent-client.py +++ b/malcontent-client/malcontent-client.py @@ -204,6 +204,15 @@ def is_valid_flatpak_ref(arg): parts[1] != '' and parts[2] != '' and parts[3] != '') +# Simple check to check whether @arg is a valid content type - it uses the +# same logic as 'MctAppFilter' to determine it and should be kept in sync +# with its implementation +def is_valid_content_type(arg): + parts = arg.split('/') + return (len(parts) == 2 and \ + parts[0] != '' and parts[1] != '') + + def command_check(user, arg, quiet=False, interactive=True): """Check the given path, content type or flatpak ref is runnable by the given user, according to their app filter.""" @@ -212,10 +221,14 @@ def command_check(user, arg, quiet=False, interactive=True): is_maybe_flatpak_id = arg.startswith('app/') and arg.count('/') < 3 is_maybe_flatpak_ref = is_valid_flatpak_ref(arg) + # Only check if arg is a valid content type if not already considered a + # valid flatpak id, otherwise we always get multiple types recognised + # when passing flatpak IDs as argument + is_maybe_content_type = not is_maybe_flatpak_id and is_valid_content_type(arg) is_maybe_path = os.path.exists(arg) recognised_types = sum([is_maybe_flatpak_id, is_maybe_flatpak_ref, - is_maybe_path]) + is_maybe_content_type, is_maybe_path]) if recognised_types == 0: print('Unknown argument ‘{}’'.format(arg), file=sys.stderr) raise SystemExit(EXIT_INVALID_OPTION) @@ -232,6 +245,10 @@ def command_check(user, arg, quiet=False, interactive=True): # Flatpak ref is_allowed = app_filter.is_flatpak_ref_allowed(arg) noun = 'Flatpak ref' + elif is_maybe_content_type: + # Content type + is_allowed = app_filter.is_content_type_allowed(arg) + noun = 'Content type' elif is_maybe_path: path = os.path.abspath(arg) is_allowed = app_filter.is_path_allowed(path) @@ -283,10 +300,11 @@ def command_set(user, allow_user_installation=True, builder.set_oars_value(section, value) else: is_maybe_flatpak_ref = is_valid_flatpak_ref(arg) + is_maybe_content_type = is_valid_content_type(arg) is_maybe_path = os.path.exists(arg) recognised_types = sum([is_maybe_flatpak_ref, - is_maybe_path]) + is_maybe_content_type, is_maybe_path]) if recognised_types == 0: print('Unknown argument ‘{}’'.format(arg), file=sys.stderr) raise SystemExit(EXIT_INVALID_OPTION) @@ -296,6 +314,8 @@ def command_set(user, allow_user_installation=True, raise SystemExit(EXIT_INVALID_OPTION) elif is_maybe_flatpak_ref: builder.blacklist_flatpak_ref(arg) + elif is_maybe_content_type: + builder.blacklist_content_type(arg) elif is_maybe_path: path = os.path.abspath(arg) builder.blacklist_path(path) @@ -353,15 +373,16 @@ def main(): # ‘check’ command parser_check = subparsers.add_parser('check', parents=[common_parser], - help='check whether a path or ' - 'flatpak ref is ' + help='check whether a path, content ' + 'type or flatpak ref is ' 'allowed by app filter') parser_check.set_defaults(function=command_check) parser_check.add_argument('user', default='', nargs='?', help='user ID or username to get the app filter ' 'for (default: current user)') parser_check.add_argument('arg', - help='path to a program or flatpak ref to check') + help='path to a program, content type or ' + 'flatpak ref to check') # ‘oars-section’ command parser_oars_section = subparsers.add_parser('oars-section', @@ -404,7 +425,7 @@ def main(): help='unconditionally disallow installation to ' 'the system flatpak repo') parser_set.add_argument('app_filter_args', nargs='*', - help='paths or flatpak refs to ' + help='paths, content types or flatpak refs to ' 'blacklist and OARS section=value ' 'pairs to store') parser_set.set_defaults(allow_user_installation=True, From 2c40458cd50aa6a424c7d15df8c20264ffff0ff8 Mon Sep 17 00:00:00 2001 From: Andre Moreira Magalhaes Date: Thu, 13 Jun 2019 17:58:08 +0000 Subject: [PATCH 7/7] docs: Update malcontent-client manpage to mention new supported types Mention content types and flatpak refs are also valid for 'check' command arguments. Signed-off-by: Andre Moreira Magalhaes --- malcontent-client/docs/malcontent-client.8 | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/malcontent-client/docs/malcontent-client.8 b/malcontent-client/docs/malcontent-client.8 index 041d243..c6bb70b 100644 --- a/malcontent-client/docs/malcontent-client.8 +++ b/malcontent-client/docs/malcontent-client.8 @@ -1,7 +1,7 @@ .\" Manpage for malcontent\-client. .\" Documentation is under the same licence as the malcontent .\" package. -.TH man 8 "03 Oct 2018" "1.0" "malcontent\-client man page" +.TH man 8 "13 Jun 2019" "1.1" "malcontent\-client man page" .\" .SH NAME .IX Header "NAME" @@ -12,7 +12,7 @@ malcontent\-client — Parental Controls Access Utility .\" \fBmalcontent\-client get [\-q] [\-n] [\fPUSER\fB] .PP -\fBmalcontent\-client check [\-q] [\-n] [\fPUSER\fB] \fPPATH\fB +\fBmalcontent\-client check [\-q] [\-n] [\fPUSER\fB] \fPARG\fB .\" .SH DESCRIPTION .IX Header "DESCRIPTION" @@ -50,9 +50,9 @@ authorization.) Username or ID of the user to get the app filter for. If not specified, the current user will be used by default. .\" -.IP "\fBPATH\fP" -Path to a program to check against the app filter, to see if it can be run by -the specified user. +.IP "\fBARG\fP" +Path to a program, content type or flatpak ref to check against the app filter, +to see if it is allowed for the specified user. .\" .IP "\fB\-q\fP, \fB\-\-quiet\fP" Only output error messages, and no informational messages, as the operation @@ -85,8 +85,8 @@ encounters problems. .IP "0" 4 .IX Item "0" No problems occurred. The utility ran and successfully queried the app filter. -If running the \fBcheck\fP command, the given path was allowed to be run by the -given user. +If running the \fBcheck\fP command, the given path, content type or flatpak ref +was allowed for the given user. .\" .IP "1" 4 .IX Item "1" @@ -99,8 +99,8 @@ The current user was not authorized to query the app filter for the given user. .\" .IP "3" 4 .IX Item "3" -If running the \fBcheck\fP command, the given path was \fInot\fP allowed to be -run by the given user. +If running the \fBcheck\fP command, the given path, content type or flatpak ref +was \fInot\fP allowed for the given user. .\" .SH BUGS .IX Header "BUGS"