diff --git a/common/Makefile.am.inc b/common/Makefile.am.inc index bf39b965..bacf390a 100644 --- a/common/Makefile.am.inc +++ b/common/Makefile.am.inc @@ -149,6 +149,13 @@ libflatpak_common_la_SOURCES = \ common/valgrind-private.h \ $(NULL) +if HAVE_LIBMALCONTENT +libflatpak_common_la_SOURCES += \ + common/flatpak-parental-controls.c \ + common/flatpak-parental-controls-private.h \ + $(NULL) +endif + libflatpak_common_la_CFLAGS = \ -DFLATPAK_COMPILATION \ -DLIBEXECDIR=\"$(libexecdir)\" \ @@ -163,6 +170,7 @@ libflatpak_common_la_CFLAGS = \ $(LIBSECCOMP_CFLAGS) \ $(MALCONTENT_CFLAGS) \ $(OSTREE_CFLAGS) \ + $(POLKIT_CFLAGS) \ $(SOUP_CFLAGS) \ $(SYSTEMD_CFLAGS) \ $(XAUTH_CFLAGS) \ @@ -178,6 +186,7 @@ libflatpak_common_la_LIBADD = \ $(LIBSECCOMP_LIBS) \ $(MALCONTENT_LIBS) \ $(OSTREE_LIBS) \ + $(POLKIT_LIBS) \ $(SOUP_LIBS) \ $(SYSTEMD_LIBS) \ $(XAUTH_LIBS) \ diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c index 7644bf5c..2a89c4ef 100644 --- a/common/flatpak-dir.c +++ b/common/flatpak-dir.c @@ -42,15 +42,22 @@ #include "libglnx/libglnx.h" #include "flatpak-error.h" #include +#include #include "flatpak-dir-private.h" #include "flatpak-utils-base-private.h" #include "flatpak-oci-registry-private.h" +#include "flatpak-ref.h" #include "flatpak-run-private.h" #include "flatpak-appdata-private.h" #include "errno.h" +#ifdef HAVE_LIBMALCONTENT +#include +#include "flatpak-parental-controls-private.h" +#endif + #ifdef HAVE_LIBSYSTEMD #define SD_JOURNAL_SUPPRESS_LOCATION #include @@ -67,6 +74,18 @@ #define SYSCONF_REMOTES_DIR "remotes.d" #define SYSCONF_REMOTES_FILE_EXT ".flatpakrepo" +/* This uses a weird Auto prefix to avoid conflicts with later added polkit types. + */ +typedef PolkitAuthority AutoPolkitAuthority; +typedef PolkitAuthorizationResult AutoPolkitAuthorizationResult; +typedef PolkitDetails AutoPolkitDetails; +typedef PolkitSubject AutoPolkitSubject; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (AutoPolkitAuthority, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (AutoPolkitAuthorizationResult, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (AutoPolkitDetails, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (AutoPolkitSubject, g_object_unref) + static FlatpakOciRegistry *flatpak_dir_create_system_child_oci_registry (FlatpakDir *self, GLnxLockFile *file_lock, GError **error); @@ -7608,6 +7627,137 @@ apply_extra_data (FlatpakDir *self, return TRUE; } +/* Check the user’s parental controls allow installation of @ref by looking at + * its cached @deploy_data, which contains its content rating as extracted from + * its AppData when it was originally downloaded. That’s compared to the + * parental controls policy loaded from the #MctManager. + * + * If @ref should not be installed, an error is returned. */ +static gboolean +flatpak_dir_check_parental_controls (FlatpakDir *self, + const char *ref, + GVariant *deploy_data, + GCancellable *cancellable, + GError **error) +{ +#ifdef HAVE_LIBMALCONTENT + g_autoptr(GError) local_error = NULL; + const char *on_session = g_getenv ("FLATPAK_SYSTEM_HELPER_ON_SESSION"); + g_autoptr(GDBusConnection) dbus_connection = NULL; + g_autoptr(MctManager) manager = NULL; + g_autoptr(MctAppFilter) app_filter = NULL; + const char *content_rating_type; + g_autoptr(GHashTable) content_rating = NULL; + g_autoptr(AutoPolkitAuthority) authority = NULL; + g_autoptr(AutoPolkitDetails) details = NULL; + g_autoptr(AutoPolkitSubject) subject = NULL; + gint subject_uid; + g_autoptr(AutoPolkitAuthorizationResult) result = NULL; + gboolean authorized; + gboolean repo_installation_allowed, app_is_appropriate; + + /* The ostree-metadata and appstream/ branches should not have any parental + * controls restrictions. Similarly, for the moment, there is no point in + * restricting runtimes. */ + if (!g_str_has_prefix (ref, "app/")) + return TRUE; + + g_debug ("Getting parental controls details for %s from %s", + ref, flatpak_deploy_data_get_origin (deploy_data)); + + if (on_session != NULL) + { + /* FIXME: Instead of skipping the parental controls check in the test + * environment, make a mock service for it. + * https://github.com/flatpak/flatpak/issues/2993 */ + g_debug ("Skipping parental controls check for %s since the " + "system bus is unavailable in the test environment", ref); + return TRUE; + } + + dbus_connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, cancellable, &local_error); + if (dbus_connection == NULL) + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + if (self->user || self->source_pid == 0) + subject = polkit_unix_process_new_for_owner (getpid (), 0, getuid ()); + else + subject = polkit_unix_process_new_for_owner (self->source_pid, 0, -1); + + /* Get the parental controls for the invoking user. */ + subject_uid = polkit_unix_process_get_uid (POLKIT_UNIX_PROCESS (subject)); + if (subject_uid == -1) + { + g_set_error_literal (error, G_DBUS_ERROR, G_DBUS_ERROR_AUTH_FAILED, + "Failed to get subject UID"); + return FALSE; + } + + manager = mct_manager_new (dbus_connection); + app_filter = mct_manager_get_app_filter (manager, subject_uid, + MCT_GET_APP_FILTER_FLAGS_INTERACTIVE, + cancellable, &local_error); + if (g_error_matches (local_error, MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_DISABLED)) + { + g_debug ("Skipping parental controls check for %s since parental " + "controls are disabled globally", ref); + return TRUE; + } + else if (local_error != NULL) + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + /* Check the content rating against the parental controls. If the app is + * allowed to be installed, return so immediately. */ + repo_installation_allowed = ((self->user && mct_app_filter_is_user_installation_allowed (app_filter)) || + (!self->user && mct_app_filter_is_system_installation_allowed (app_filter))); + + content_rating_type = flatpak_deploy_data_get_appdata_content_rating_type (deploy_data); + content_rating = flatpak_deploy_data_get_appdata_content_rating (deploy_data); + app_is_appropriate = flatpak_oars_check_rating (content_rating, content_rating_type, + app_filter); + + if (repo_installation_allowed && app_is_appropriate) + { + g_debug ("Parental controls policy satisfied for %s", ref); + return TRUE; + } + + /* Otherwise, check polkit to see if the admin is going to allow the user to + * override their parental controls policy. We can’t pass any details to this + * polkit check, since it could be run by the user or by the system helper, + * and non-root users can’t pass details to polkit checks. */ + authority = polkit_authority_get_sync (NULL, error); + if (authority == NULL) + return FALSE; + + result = polkit_authority_check_authorization_sync (authority, subject, + "org.freedesktop.Flatpak.override-parental-controls", + NULL, + POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, + cancellable, error); + if (result == NULL) + return FALSE; + + authorized = polkit_authorization_result_get_is_authorized (result); + + if (!authorized) + return flatpak_fail_error (error, FLATPAK_ERROR_PERMISSION_DENIED, + /* Translators: The placeholder is for an app ref. */ + _("Installing %s is not allowed by the policy set by your administrator"), + ref); + + g_debug ("Parental controls policy overridden by polkit for %s", ref); +#endif /* HAVE_LIBMALCONTENT */ + + return TRUE; +} + /* We create a deploy ref for the currently deployed version of all refs to avoid deployed commits being pruned when e.g. we pull --no-deploy. */ static gboolean @@ -8036,6 +8186,11 @@ flatpak_dir_deploy (FlatpakDir *self, installed_size, previous_ids); + /* Check the app is actually allowed to be used by this user. This can block + * on getting authorisation. */ + if (!flatpak_dir_check_parental_controls (self, ref, deploy_data, cancellable, error)) + return FALSE; + deploy_data_file = g_file_get_child (checkoutdir, "deploy"); if (!flatpak_variant_save (deploy_data_file, deploy_data, cancellable, error)) return FALSE; diff --git a/common/flatpak-parental-controls-private.h b/common/flatpak-parental-controls-private.h new file mode 100644 index 00000000..d1c361db --- /dev/null +++ b/common/flatpak-parental-controls-private.h @@ -0,0 +1,35 @@ +/* + * 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 . + * + * Authors: + * Philip Withnall + */ + +#if !defined(__FLATPAK_H_INSIDE__) && !defined(FLATPAK_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __FLATPAK_PARENTAL_CONTROLS_PRIVATE_H__ +#define __FLATPAK_PARENTAL_CONTROLS_PRIVATE_H__ + +#include +#include + +gboolean flatpak_oars_check_rating (GHashTable *content_rating, + const gchar *content_rating_type, + MctAppFilter *filter); + +#endif /* __FLATPAK_PARENTAL_CONTROLS_PRIVATE_H__ */ diff --git a/common/flatpak-parental-controls.c b/common/flatpak-parental-controls.c new file mode 100644 index 00000000..e1f4c77d --- /dev/null +++ b/common/flatpak-parental-controls.c @@ -0,0 +1,141 @@ +/* + * 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 . + * + * Authors: + * Philip Withnall + */ + +#include "config.h" + +#include +#include +#include + +#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 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: user’s parental controls settings + * + * Check whether the OARS rating in @content_rating is as, or less, extreme than + * the user’s 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 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 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; +} diff --git a/system-helper/org.freedesktop.Flatpak.policy.in b/system-helper/org.freedesktop.Flatpak.policy.in index 6c3c6d20..d68a5d53 100644 --- a/system-helper/org.freedesktop.Flatpak.policy.in +++ b/system-helper/org.freedesktop.Flatpak.policy.in @@ -235,5 +235,62 @@ + + + Override parental controls + Authentication is required to install software which is restricted by your parental controls policy + package-x-generic + + auth_admin + auth_admin + auth_admin + + + diff --git a/system-helper/org.freedesktop.Flatpak.rules.in b/system-helper/org.freedesktop.Flatpak.rules.in index be03f425..f01364c2 100644 --- a/system-helper/org.freedesktop.Flatpak.rules.in +++ b/system-helper/org.freedesktop.Flatpak.rules.in @@ -11,3 +11,11 @@ polkit.addRule(function(action, subject) { return polkit.Result.NOT_HANDLED; }); + +polkit.addRule(function(action, subject) { + if (action.id == "org.freedesktop.Flatpak.override-parental-controls") { + return polkit.Result.AUTH_ADMIN; + } + + return polkit.Result.NOT_HANDLED; +});