flatpak/common/flatpak-parental-controls.c
Philip Withnall 9758968cc4 dir: Support filtering app installs/upgrades by user’s OARS settings
Use the user’s OARS filter to prevent installation or upgrade of
apps which have more extreme content than the user is allowed to see.

This uses libmalcontent to load the user’s enforced OARS filter, which
describes the extremeness of each type of content the user is allowed to
see. If an app they are trying to install exceeds the filter value in
any OARS section, installation is disallowed and an error is returned.

libmalcontent stores the parental controls policy per-user in
accountsservice, which enforces access control on the policies.

The app filter is also allowed to prevent app installation entirely,
which overrides the OARS values. This is independent from the app-install
polkit action, which determines whether an unprivileged user may install
an app system-wide. Being stored in accountsservice, the new boolean is
also easier to set per-user without having to programmatically write a
polkit JS policy file which handles multiple users (and parse it back
again).

The parental controls checks are done at deploy time, either in the
`flatpak` process (for user repositories) or in the
`flatpak-system-helper` (for system repositories). The checks use
content rating data extracted from the app’s AppData XML and stored in
the `FlatpakDeploy` cache. The checks are passed through polkit (even
for user repositories) so that users can get an admin override to
install apps which would otherwise be too extreme. This uses the new
`org.freedesktop.Flatpak.parental-controls` polkit rule.

The checks have to be done at deploy time, as that’s when the AppData
XML for the app is parsed. The downside of this arrangement is that an
app must be entirely downloaded before the parental checks can be done.
This won’t be much of an issue on normal desktops, however, since we can
assume that gnome-software will check an app’s appropriateness before
showing it to the user in the first place.

Parental controls are not enforced for non-apps/runtimes, which includes
the ostree-metadata and appstream/* refs.

One thorny issue is that flatpak unit tests may be run in an environment
with no system D-Bus available to connect to (a Jenkins instance, for
example), which means the call to `mct_manager_get_app_filter()` in
`flatpak_dir_check_parental_controls()` fails.

So this commit skips the parental controls check if the system bus is
unavailable and the environment variable
`FLATPAK_SYSTEM_HELPER_ON_SESSION` is set, since the testlibrary already
sets that variable so that the system-helper will be started on the
session bus.

The feature can be tested using something like:
```
   $ malcontent-client set philip \
       violence-realistic=none app/org.freedesktop.Bustle/x86_64/stable
   App filter for user 1000 set
   $ flatpak run org.freedesktop.Bustle
   error: Running app/org.freedesktop.Bustle/x86_64/stable is not allowed by the policy set by your administrator
   $ flatpak --user install flathub io.github.FreeDM
   error: Failed to install io.github.FreeDM: Installing app/io.github.FreeDM/x86_64/stable is not allowed by the policy set by your administrator
```

Includes work by André Magalhães and Umang Jain.

Signed-off-by: Philip Withnall <withnall@endlessm.com>
2019-10-03 10:42:04 +02:00

142 lines
5.4 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright © 2018 Endless Mobile, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Philip Withnall <withnall@endlessm.com>
*/
#include "config.h"
#include <glib.h>
#include <gio/gio.h>
#include <libmalcontent/app-filter.h>
#include "flatpak-parental-controls-private.h"
/*
* See https://www.freedesktop.org/software/appstream/docs/chap-Metadata.html#tag-content_rating
* for details of the appstream content rating specification.
*
* See https://hughsie.github.io/oars/ for details of OARS. Specifically,
* https://github.com/hughsie/oars/tree/master/specification/.
*/
/* Convert an appstream <content_attribute/> value to #MctAppFilterOarsValue.
* https://www.freedesktop.org/software/appstream/docs/chap-Metadata.html#tag-content_rating
*/
static MctAppFilterOarsValue
app_filter_oars_value_from_appdata (const gchar *appdata_value)
{
g_return_val_if_fail (appdata_value != NULL, MCT_APP_FILTER_OARS_VALUE_UNKNOWN);
if (g_str_equal (appdata_value, "intense"))
return MCT_APP_FILTER_OARS_VALUE_INTENSE;
else if (g_str_equal (appdata_value, "moderate"))
return MCT_APP_FILTER_OARS_VALUE_MODERATE;
else if (g_str_equal (appdata_value, "mild"))
return MCT_APP_FILTER_OARS_VALUE_MILD;
else if (g_str_equal (appdata_value, "none"))
return MCT_APP_FILTER_OARS_VALUE_NONE;
else if (g_str_equal (appdata_value, "unknown"))
return MCT_APP_FILTER_OARS_VALUE_UNKNOWN;
else
return MCT_APP_FILTER_OARS_VALUE_UNKNOWN;
}
static const gchar *
app_filter_oars_value_to_string (MctAppFilterOarsValue oars_value)
{
switch (oars_value)
{
case MCT_APP_FILTER_OARS_VALUE_UNKNOWN: return "unknown";
case MCT_APP_FILTER_OARS_VALUE_INTENSE: return "intense";
case MCT_APP_FILTER_OARS_VALUE_MODERATE: return "moderate";
case MCT_APP_FILTER_OARS_VALUE_MILD: return "mild";
case MCT_APP_FILTER_OARS_VALUE_NONE: return "none";
default: return "unknown";
}
}
/**
* flatpak_oars_check_rating:
* @content_rating: (nullable) (transfer none): OARS ratings for the app,
* or %NULL if none are known
* @content_rating_type: (nullable): scheme used in @content_rating, such as
* `oars-1.0` or `oars-1.1`, or %NULL if @content_rating is %NULL
* @filter: users parental controls settings
*
* Check whether the OARS rating in @content_rating is as, or less, extreme than
* the users preferences in @filter. If so (i.e. if the app is suitable for
* this user to use), return %TRUE; otherwise return %FALSE.
*
* @content_rating may be %NULL if no OARS ratings are provided for the app. If
* so, we have to assume the most restrictive ratings.
*
* Returns: %TRUE if the app is safe to install, %FALSE otherwise
*/
gboolean
flatpak_oars_check_rating (GHashTable *content_rating,
const gchar *content_rating_type,
MctAppFilter *filter)
{
const gchar * const supported_rating_types[] = { "oars-1.0", "oars-1.1", NULL };
g_autofree const gchar **oars_sections = mct_app_filter_get_oars_sections (filter);
MctAppFilterOarsValue default_rating_value;
if (content_rating_type != NULL &&
!g_strv_contains (supported_rating_types, content_rating_type))
return FALSE;
/* If the app has a <content_rating/> element, even if it has no OARS sections
* in it, use a default value of `none` for any missing sections. Otherwise,
* if the app has no <content_rating/> element, use `unknown`. */
if (content_rating != NULL)
default_rating_value = MCT_APP_FILTER_OARS_VALUE_NONE;
else
default_rating_value = MCT_APP_FILTER_OARS_VALUE_UNKNOWN;
for (gsize i = 0; oars_sections[i] != NULL; i++)
{
MctAppFilterOarsValue rating_value;
MctAppFilterOarsValue filter_value = mct_app_filter_get_oars_value (filter,
oars_sections[i]);
const gchar *appdata_value;
if (content_rating != NULL)
appdata_value = g_hash_table_lookup (content_rating, oars_sections[i]);
if (appdata_value != NULL)
rating_value = app_filter_oars_value_from_appdata (appdata_value);
else
rating_value = default_rating_value;
if (filter_value < rating_value ||
(rating_value == MCT_APP_FILTER_OARS_VALUE_UNKNOWN &&
filter_value != MCT_APP_FILTER_OARS_VALUE_UNKNOWN) ||
(rating_value != MCT_APP_FILTER_OARS_VALUE_UNKNOWN &&
filter_value == MCT_APP_FILTER_OARS_VALUE_UNKNOWN))
{
g_debug ("%s: Comparing rating %s: app has %s but policy has %s unknown: OARS check failed",
G_STRFUNC, oars_sections[i],
app_filter_oars_value_to_string (rating_value),
app_filter_oars_value_to_string (filter_value));
return FALSE;
}
}
return TRUE;
}