Merge branch 'filter-content-type' into 'master'

Add support to filter by content type

See merge request pwithnall/malcontent!15
This commit is contained in:
Philip Withnall 2019-06-14 16:02:04 +00:00
commit 90f961dc74
5 changed files with 326 additions and 59 deletions

View File

@ -1,6 +1,6 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- /* -*- 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 * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public * modify it under the terms of the GNU Lesser General Public
@ -18,6 +18,7 @@
* *
* Authors: * Authors:
* - Philip Withnall <withnall@endlessm.com> * - Philip Withnall <withnall@endlessm.com>
* - Andre Moreira Magalhaes <andre@endlessm.com>
*/ */
#include "config.h" #include "config.h"
@ -124,8 +125,12 @@ mct_app_filter_is_path_allowed (MctAppFilter *filter,
g_return_val_if_fail (g_path_is_absolute (path), FALSE); 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 = 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, gboolean path_in_list = g_strv_contains ((const gchar * const *) filter->app_list,
canonical_path); canonical_path_utf8);
switch (filter->app_list_type) switch (filter->app_list_type)
{ {
@ -138,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: * mct_app_filter_is_flatpak_ref_allowed:
* @filter: an #MctAppFilter * @filter: an #MctAppFilter
@ -157,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 != NULL, FALSE);
g_return_val_if_fail (filter->ref_count >= 1, 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 (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, gboolean ref_in_list = g_strv_contains ((const gchar * const *) filter->app_list,
app_ref); app_ref);
@ -201,9 +235,8 @@ mct_app_filter_is_flatpak_app_allowed (MctAppFilter *filter,
gboolean id_in_list = FALSE; gboolean id_in_list = FALSE;
for (gsize i = 0; filter->app_list[i] != NULL; i++) for (gsize i = 0; filter->app_list[i] != NULL; i++)
{ {
/* Avoid using flatpak_ref_parse() to avoid a dependency on libflatpak if (is_valid_flatpak_ref (filter->app_list[i]) &&
* just for that one function. */ g_str_has_prefix (filter->app_list[i], "app/") &&
if (g_str_has_prefix (filter->app_list[i], "app/") &&
strncmp (filter->app_list[i] + strlen ("app/"), app_id, app_id_len) == 0 && strncmp (filter->app_list[i] + strlen ("app/"), app_id, app_id_len) == 0 &&
filter->app_list[i][strlen ("app/") + app_id_len] == '/') filter->app_list[i][strlen ("app/") + app_id_len] == '/')
{ {
@ -242,6 +275,7 @@ mct_app_filter_is_appinfo_allowed (MctAppFilter *filter,
GAppInfo *app_info) GAppInfo *app_info)
{ {
g_autofree gchar *abs_path = NULL; 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 != NULL, FALSE);
g_return_val_if_fail (filter->ref_count >= 1, FALSE); g_return_val_if_fail (filter->ref_count >= 1, FALSE);
@ -253,6 +287,13 @@ mct_app_filter_is_appinfo_allowed (MctAppFilter *filter,
!mct_app_filter_is_path_allowed (filter, abs_path)) !mct_app_filter_is_path_allowed (filter, abs_path))
return FALSE; 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)) if (G_IS_DESKTOP_APP_INFO (app_info))
{ {
g_autofree gchar *flatpak_app = NULL; g_autofree gchar *flatpak_app = NULL;
@ -292,6 +333,67 @@ mct_app_filter_is_appinfo_allowed (MctAppFilter *filter,
return TRUE; 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 doesnt 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 static gint
strcmp_cb (gconstpointer a, strcmp_cb (gconstpointer a,
gconstpointer b) gconstpointer b)
@ -433,7 +535,7 @@ mct_app_filter_is_system_installation_allowed (MctAppFilter *filter)
*/ */
typedef struct 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) */ GHashTable *oars; /* (nullable) (owned) (element-type utf8 MctAppFilterOarsValue) */
gboolean allow_user_installation; gboolean allow_user_installation;
gboolean allow_system_installation; gboolean allow_system_installation;
@ -471,7 +573,7 @@ mct_app_filter_builder_init (MctAppFilterBuilder *builder)
MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder; MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder;
g_return_if_fail (_builder != NULL); 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); g_return_if_fail (_builder->oars == NULL);
memcpy (builder, &local_builder, sizeof (local_builder)); memcpy (builder, &local_builder, sizeof (local_builder));
@ -497,7 +599,7 @@ mct_app_filter_builder_clear (MctAppFilterBuilder *builder)
g_return_if_fail (_builder != NULL); 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); g_clear_pointer (&_builder->oars, g_hash_table_unref);
} }
@ -547,8 +649,8 @@ mct_app_filter_builder_copy (MctAppFilterBuilder *builder)
_copy = (MctAppFilterBuilderReal *) copy; _copy = (MctAppFilterBuilderReal *) copy;
mct_app_filter_builder_clear (copy); mct_app_filter_builder_clear (copy);
if (_builder->paths_blacklist != NULL) if (_builder->blacklist != NULL)
_copy->paths_blacklist = g_ptr_array_ref (_builder->paths_blacklist); _copy->blacklist = g_ptr_array_ref (_builder->blacklist);
if (_builder->oars != NULL) if (_builder->oars != NULL)
_copy->oars = g_hash_table_ref (_builder->oars); _copy->oars = g_hash_table_ref (_builder->oars);
_copy->allow_user_installation = _builder->allow_user_installation; _copy->allow_user_installation = _builder->allow_user_installation;
@ -598,11 +700,11 @@ mct_app_filter_builder_end (MctAppFilterBuilder *builder)
g_autoptr(GVariant) oars_variant = NULL; g_autoptr(GVariant) oars_variant = NULL;
g_return_val_if_fail (_builder != NULL, 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); g_return_val_if_fail (_builder->oars != NULL, NULL);
/* Ensure the paths list is %NULL-terminated. */ /* 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. */ /* Build the OARS variant. */
g_hash_table_iter_init (&iter, _builder->oars); g_hash_table_iter_init (&iter, _builder->oars);
@ -633,7 +735,7 @@ mct_app_filter_builder_end (MctAppFilterBuilder *builder)
app_filter = g_new0 (MctAppFilter, 1); app_filter = g_new0 (MctAppFilter, 1);
app_filter->ref_count = 1; app_filter->ref_count = 1;
app_filter->user_id = -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->app_list_type = MCT_APP_FILTER_LIST_BLACKLIST;
app_filter->oars_ratings = g_steal_pointer (&oars_variant); app_filter->oars_ratings = g_steal_pointer (&oars_variant);
app_filter->allow_user_installation = _builder->allow_user_installation; app_filter->allow_user_installation = _builder->allow_user_installation;
@ -662,15 +764,18 @@ mct_app_filter_builder_blacklist_path (MctAppFilterBuilder *builder,
MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder; MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder;
g_return_if_fail (_builder != NULL); 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 (path != NULL);
g_return_if_fail (g_path_is_absolute (path)); g_return_if_fail (g_path_is_absolute (path));
g_autofree gchar *canonical_path = g_canonicalize_filename (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, if (!g_ptr_array_find_with_equal_func (_builder->blacklist,
canonical_path, g_str_equal, NULL)) canonical_path_utf8, g_str_equal, NULL))
g_ptr_array_add (_builder->paths_blacklist, g_steal_pointer (&canonical_path)); g_ptr_array_add (_builder->blacklist, g_steal_pointer (&canonical_path_utf8));
} }
/** /**
@ -691,12 +796,44 @@ mct_app_filter_builder_blacklist_flatpak_ref (MctAppFilterBuilder *builder,
MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder; MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder;
g_return_if_fail (_builder != NULL); 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); 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->paths_blacklist, if (!g_ptr_array_find_with_equal_func (_builder->blacklist,
app_ref, g_str_equal, NULL)) 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));
}
/**
* 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 its already been
* added.
*
* Note that this method doesnt 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));
} }
/** /**

View File

@ -1,6 +1,6 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- /* -*- 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 * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public * modify it under the terms of the GNU Lesser General Public
@ -18,6 +18,7 @@
* *
* Authors: * Authors:
* - Philip Withnall <withnall@endlessm.com> * - Philip Withnall <withnall@endlessm.com>
* - Andre Moreira Magalhaes <andre@endlessm.com>
*/ */
#pragma once #pragma once
@ -109,6 +110,8 @@ gboolean mct_app_filter_is_flatpak_app_allowed (MctAppFilter *filter,
const gchar *app_id); const gchar *app_id);
gboolean mct_app_filter_is_appinfo_allowed (MctAppFilter *filter, gboolean mct_app_filter_is_appinfo_allowed (MctAppFilter *filter,
GAppInfo *app_info); 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); const gchar **mct_app_filter_get_oars_sections (MctAppFilter *filter);
MctAppFilterOarsValue mct_app_filter_get_oars_value (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); const gchar *path);
void mct_app_filter_builder_blacklist_flatpak_ref (MctAppFilterBuilder *builder, void mct_app_filter_builder_blacklist_flatpak_ref (MctAppFilterBuilder *builder,
const gchar *app_ref); 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, void mct_app_filter_builder_set_oars_value (MctAppFilterBuilder *builder,
const gchar *oars_section, const gchar *oars_section,
MctAppFilterOarsValue value); MctAppFilterOarsValue value);

View File

@ -155,6 +155,9 @@ test_app_filter_builder_non_empty (BuilderFixture *fixture,
mct_app_filter_builder_blacklist_flatpak_ref (fixture->builder, mct_app_filter_builder_blacklist_flatpak_ref (fixture->builder,
"app/org.doom.Doom/x86_64/master"); "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_builder_set_oars_value (fixture->builder, "drugs-alcohol",
MCT_APP_FILTER_OARS_VALUE_MILD); MCT_APP_FILTER_OARS_VALUE_MILD);
mct_app_filter_builder_set_oars_value (fixture->builder, "language-humor", 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")); "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_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"), ==, g_assert_cmpint (mct_app_filter_get_oars_value (filter, "drugs-alcohol"), ==,
MCT_APP_FILTER_OARS_VALUE_MILD); MCT_APP_FILTER_OARS_VALUE_MILD);
g_assert_cmpint (mct_app_filter_get_oars_value (filter, "language-humor"), ==, 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")); "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_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"), ==, g_assert_cmpint (mct_app_filter_get_oars_value (filter, "drugs-alcohol"), ==,
MCT_APP_FILTER_OARS_VALUE_UNKNOWN); MCT_APP_FILTER_OARS_VALUE_UNKNOWN);
g_assert_cmpint (mct_app_filter_get_oars_value (filter, "language-humor"), ==, 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_init (builder_copy);
mct_app_filter_builder_blacklist_path (builder_copy, "/bin/true"); 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); filter = mct_app_filter_builder_end (builder_copy);
g_assert_true (mct_app_filter_is_path_allowed (filter, "/bin/false")); 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_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_true (mct_app_filter_is_user_installation_allowed (filter));
g_assert_false (mct_app_filter_is_system_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; g_autoptr(MctAppFilter) filter = NULL;
mct_app_filter_builder_blacklist_path (builder, "/bin/true"); 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_user_installation (builder, FALSE);
mct_app_filter_builder_set_allow_system_installation (builder, TRUE); mct_app_filter_builder_set_allow_system_installation (builder, TRUE);
builder_copy = mct_app_filter_builder_copy (builder); 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_true (mct_app_filter_is_path_allowed (filter, "/bin/false"));
g_assert_false (mct_app_filter_is_path_allowed (filter, "/bin/true")); 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_false (mct_app_filter_is_user_installation_allowed (filter));
g_assert_true (mct_app_filter_is_system_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" "Name=Some Name\n"
"Exec=/bin/true\n" "Exec=/bin/true\n"
"Type=Application\n" }, "Type=Application\n" },
/* Allowed by its path and its flatpak ID: */ /* Allowed by its path and its content type: */
{ TRUE, { TRUE,
"[Desktop Entry]\n" "[Desktop Entry]\n"
"Name=Some Name\n" "Name=Some Name\n"
"Exec=/bin/true\n" "Exec=/bin/true\n"
"Type=Application\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" }, "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, { TRUE,
"[Desktop Entry]\n" "[Desktop Entry]\n"
"Name=Some Name\n" "Name=Some Name\n"
"Exec=/bin/true\n" "Exec=/bin/true\n"
"Type=Application\n" "Type=Application\n"
"MimeType=text/plain\n"
"X-Flatpak=org.gnome.Nice\n" "X-Flatpak=org.gnome.Nice\n"
"X-Flatpak-RenamedFrom=\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, { TRUE,
"[Desktop Entry]\n" "[Desktop Entry]\n"
"Name=Some Name\n" "Name=Some Name\n"
"Exec=/bin/true\n" "Exec=/bin/true\n"
"Type=Application\n" "Type=Application\n"
"MimeType=text/plain\n"
"X-Flatpak-RenamedFrom=org.gnome.OldNice\n" }, "X-Flatpak-RenamedFrom=org.gnome.OldNice\n" },
/* Allowed by its path, its flatpak ID and its old flatpak IDs (which /* Allowed by its path, its content type, its flatpak ID and its old
* contain some spurious entries): */ * flatpak IDs (which contain some spurious entries): */
{ TRUE, { TRUE,
"[Desktop Entry]\n" "[Desktop Entry]\n"
"Name=Some Name\n" "Name=Some Name\n"
"Exec=/bin/true\n" "Exec=/bin/true\n"
"Type=Application\n" "Type=Application\n"
"MimeType=text/plain\n"
"X-Flatpak-RenamedFrom=org.gnome.OldNice;;;\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, { TRUE,
"[Desktop Entry]\n" "[Desktop Entry]\n"
"Name=Some Name\n" "Name=Some Name\n"
"Exec=/bin/true\n" "Exec=/bin/true\n"
"Type=Application\n" "Type=Application\n"
"MimeType=text/plain\n"
"X-Flatpak-RenamedFrom=org.gnome.OldNice.desktop\n" }, "X-Flatpak-RenamedFrom=org.gnome.OldNice.desktop\n" },
/* Disallowed by its path: */ /* Disallowed by its path: */
{ FALSE, { FALSE,
@ -333,6 +369,13 @@ test_app_filter_appinfo (void)
"Name=Some Name\n" "Name=Some Name\n"
"Exec=/bin/false\n" "Exec=/bin/false\n"
"Type=Application\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: */ /* Allowed by its path, disallowed by its flatpak ID: */
{ FALSE, { FALSE,
"[Desktop Entry]\n" "[Desktop Entry]\n"
@ -358,10 +401,21 @@ test_app_filter_appinfo (void)
"Type=Application\n" "Type=Application\n"
"X-Flatpak=org.gnome.WasNasty\n" "X-Flatpak=org.gnome.WasNasty\n"
"X-Flatpak-RenamedFrom=org.gnome.Nasty.desktop;\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_path (&builder, "/bin/false");
mct_app_filter_builder_blacklist_flatpak_ref (&builder, "app/org.gnome.Nasty/x86_64/stable"); 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); filter = mct_app_filter_builder_end (&builder);
@ -584,7 +638,8 @@ test_app_filter_bus_get_whitelist (BusFixture *fixture,
"'AppFilter': <(true, [" "'AppFilter': <(true, ["
"'app/org.gnome.Whitelisted1/x86_64/stable'," "'app/org.gnome.Whitelisted1/x86_64/stable',"
"'app/org.gnome.Whitelisted2/x86_64/stable'," "'app/org.gnome.Whitelisted2/x86_64/stable',"
"'/usr/bin/true'" "'/usr/bin/true',"
"'text/plain'"
"])>," "])>,"
"'OarsFilter': <('oars-1.1', @a{ss} {})>" "'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_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_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_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 /* 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 = const SetAppFilterData set_app_filter_data =
{ {
.expected_uid = fixture->valid_uid, .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_oars_filter_value = "('oars-1.1', { 'violence-fantasy': 'intense' })",
.expected_allow_user_installation_value = "true", .expected_allow_user_installation_value = "true",
.expected_allow_system_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/false");
mct_app_filter_builder_blacklist_path (&builder, "/usr/bin/banned"); 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_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_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_user_installation (&builder, TRUE);
mct_app_filter_builder_set_allow_system_installation (&builder, TRUE); mct_app_filter_builder_set_allow_system_installation (&builder, TRUE);

View File

@ -1,7 +1,7 @@
.\" Manpage for malcontent\-client. .\" Manpage for malcontent\-client.
.\" Documentation is under the same licence as the malcontent .\" Documentation is under the same licence as the malcontent
.\" package. .\" 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 .SH NAME
.IX Header "NAME" .IX Header "NAME"
@ -12,7 +12,7 @@ malcontent\-client — Parental Controls Access Utility
.\" .\"
\fBmalcontent\-client get [\-q] [\-n] [\fPUSER\fB] \fBmalcontent\-client get [\-q] [\-n] [\fPUSER\fB]
.PP .PP
\fBmalcontent\-client check [\-q] [\-n] [\fPUSER\fB] \fPPATH\fB \fBmalcontent\-client check [\-q] [\-n] [\fPUSER\fB] \fPARG\fB
.\" .\"
.SH DESCRIPTION .SH DESCRIPTION
.IX Header "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 Username or ID of the user to get the app filter for. If not specified, the
current user will be used by default. current user will be used by default.
.\" .\"
.IP "\fBPATH\fP" .IP "\fBARG\fP"
Path to a program to check against the app filter, to see if it can be run by Path to a program, content type or flatpak ref to check against the app filter,
the specified user. to see if it is allowed for the specified user.
.\" .\"
.IP "\fB\-q\fP, \fB\-\-quiet\fP" .IP "\fB\-q\fP, \fB\-\-quiet\fP"
Only output error messages, and no informational messages, as the operation Only output error messages, and no informational messages, as the operation
@ -85,8 +85,8 @@ encounters problems.
.IP "0" 4 .IP "0" 4
.IX Item "0" .IX Item "0"
No problems occurred. The utility ran and successfully queried the app filter. 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 If running the \fBcheck\fP command, the given path, content type or flatpak ref
given user. was allowed for the given user.
.\" .\"
.IP "1" 4 .IP "1" 4
.IX Item "1" .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 .IP "3" 4
.IX Item "3" .IX Item "3"
If running the \fBcheck\fP command, the given path was \fInot\fP allowed to be If running the \fBcheck\fP command, the given path, content type or flatpak ref
run by the given user. was \fInot\fP allowed for the given user.
.\" .\"
.SH BUGS .SH BUGS
.IX Header "BUGS" .IX Header "BUGS"

View File

@ -194,36 +194,77 @@ def command_monitor(user, quiet=False, interactive=True):
break break
def command_check(user, path, quiet=False, interactive=True): # Simple check to check whether @arg is a valid flatpak ref - it uses the
"""Check the given path or flatpak ref is runnable by the given user, # same logic as 'MctAppFilter' to determine it and should be kept in sync
according to their app filter.""" # 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] != '')
# 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."""
user_id = __lookup_user_id_or_error(user) user_id = __lookup_user_id_or_error(user)
app_filter = __get_app_filter_or_error(user_id, interactive) app_filter = __get_app_filter_or_error(user_id, interactive)
if path.startswith('app/') and path.count('/') < 3: 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_content_type, 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 # Flatpak app ID
path = path[4:] arg = arg[4:]
is_allowed = app_filter.is_flatpak_app_allowed(path) is_allowed = app_filter.is_flatpak_app_allowed(arg)
noun = 'Flatpak app ID' noun = 'Flatpak app ID'
elif path.startswith('app/') or path.startswith('runtime/'): elif is_maybe_flatpak_ref:
# Flatpak ref # Flatpak ref
is_allowed = app_filter.is_flatpak_ref_allowed(path) is_allowed = app_filter.is_flatpak_ref_allowed(arg)
noun = 'Flatpak ref' noun = 'Flatpak ref'
else: elif is_maybe_content_type:
# File system path # Content type
path = os.path.abspath(path) 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) is_allowed = app_filter.is_path_allowed(path)
noun = 'Path' noun = 'Path'
else:
raise AssertionError('code should not be reached')
if is_allowed: if is_allowed:
if not quiet: if not quiet:
print('{} {} is allowed by app filter for user {}'.format( print('{} {} is allowed by app filter for user {}'.format(
noun, path, user_id)) noun, arg, user_id))
return return
else: else:
if not quiet: if not quiet:
print('{} {} is not allowed by app filter for user {}'.format( print('{} {} is not allowed by app filter for user {}'.format(
noun, path, user_id)) noun, arg, user_id))
raise SystemExit(EXIT_PATH_NOT_ALLOWED) raise SystemExit(EXIT_PATH_NOT_ALLOWED)
@ -257,10 +298,30 @@ def command_set(user, allow_user_installation=True,
file=sys.stderr) file=sys.stderr)
raise SystemExit(EXIT_INVALID_OPTION) raise SystemExit(EXIT_INVALID_OPTION)
builder.set_oars_value(section, value) builder.set_oars_value(section, value)
elif arg.startswith('app/') or arg.startswith('runtime/'):
builder.blacklist_flatpak_ref(arg)
else: else:
builder.blacklist_path(arg) 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_content_type, 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_content_type:
builder.blacklist_content_type(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() app_filter = builder.end()
__set_app_filter_or_error(user_id, app_filter, interactive) __set_app_filter_or_error(user_id, app_filter, interactive)
@ -312,14 +373,16 @@ def main():
# check command # check command
parser_check = subparsers.add_parser('check', parents=[common_parser], parser_check = subparsers.add_parser('check', parents=[common_parser],
help='check whether a path is ' help='check whether a path, content '
'type or flatpak ref is '
'allowed by app filter') 'allowed by app filter')
parser_check.set_defaults(function=command_check) parser_check.set_defaults(function=command_check)
parser_check.add_argument('user', default='', nargs='?', parser_check.add_argument('user', default='', nargs='?',
help='user ID or username to get the app filter ' help='user ID or username to get the app filter '
'for (default: current user)') 'for (default: current user)')
parser_check.add_argument('path', parser_check.add_argument('arg',
help='path to a program to check') help='path to a program, content type or '
'flatpak ref to check')
# oars-section command # oars-section command
parser_oars_section = subparsers.add_parser('oars-section', parser_oars_section = subparsers.add_parser('oars-section',
@ -362,7 +425,8 @@ def main():
help='unconditionally disallow installation to ' help='unconditionally disallow installation to '
'the system flatpak repo') 'the system flatpak repo')
parser_set.add_argument('app_filter_args', nargs='*', parser_set.add_argument('app_filter_args', nargs='*',
help='paths to blacklist and OARS section=value ' help='paths, content types or flatpak refs to '
'blacklist and OARS section=value '
'pairs to store') 'pairs to store')
parser_set.set_defaults(allow_user_installation=True, parser_set.set_defaults(allow_user_installation=True,
allow_system_installation=False) allow_system_installation=False)