[gnome-shell/eos3.8: 119/255] js: Add support for parental controls filtering to the desktop



commit 063fe74070eda091de44115aef483ee436580377
Author: Philip Withnall <withnall endlessm com>
Date:   Thu Nov 22 15:03:41 2018 +0000

    js: Add support for parental controls filtering to the desktop
    
    Filter the apps shown on the desktop and in search results according to
    whether they are blacklisted by the user’s parental controls.
    
    This will not dynamically update the filter during the user’s session,
    as we assume that cannot happen — see parentalControlsManager.js for the
    rationale.
    
    Note that since the desktop layout is serialised to a GSetting, if an
    app is present on the desktop, is then filtered (due to the user’s
    parental controls being changed, for example), and then the user updates
    their desktop in some other way, the app will need to be manually
    re-added to the desktop if it’s ever removed from the parental controls
    blacklist.
    
    Signed-off-by: Philip Withnall <withnall endlessm com>
    
    https://phabricator.endlessm.com/T24016
    https://phabricator.endlessm.com/T26997
    
     * 2020-03-23:
          + Squash with 53f510b28
          + Squash with 1b988603c
          + Partially squashed with bb0fcec82
          + Squashed with 93a30132d
     * 2019-10-09: Squash with "414f009ae5 js: Migrate parental controls backend
                to malcontent"

 js/js-resources.gresource.xml      |   1 +
 js/misc/parentalControlsManager.js | 118 +++++++++++++++++++++++++++++++++++++
 js/ui/appDisplay.js                |   6 ++
 js/ui/iconGridLayout.js            |  17 ++++++
 js/ui/main.js                      |   4 ++
 meson.build                        |   4 ++
 6 files changed, 150 insertions(+)
---
diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
index bba1a6acf0..d4e7915888 100644
--- a/js/js-resources.gresource.xml
+++ b/js/js-resources.gresource.xml
@@ -141,6 +141,7 @@
 
     <!-- Endless-specific files beyond this point -->
 
+    <file>misc/parentalControlsManager.js</file>
     <file>ui/appActivation.js</file>
     <file>ui/appIconBar.js</file>
     <file>ui/components/appStore.js</file>
diff --git a/js/misc/parentalControlsManager.js b/js/misc/parentalControlsManager.js
new file mode 100644
index 0000000000..8a1cd80552
--- /dev/null
+++ b/js/misc/parentalControlsManager.js
@@ -0,0 +1,118 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+//
+// Copyright (C) 2018, 2019 Endless Mobile, Inc.
+//
+// This is a GNOME Shell component to wrap the interactions over
+// D-Bus with the malcontent library.
+//
+// Licensed under the GNU General Public License Version 2
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+/* exported getDefault */
+
+const { Malcontent, Gio, GObject, Shell } = imports.gi;
+
+let _singleton = null;
+
+function getDefault() {
+    if (_singleton === null)
+        _singleton = new ParentalControlsManager();
+
+    return _singleton;
+}
+
+// A manager class which provides cached access to the constructing user’s
+// parental controls settings. There are currently no notifications of changes
+// to those settings, as it’s assumed that only an administrator can change a
+// user’s parental controls, the administrator themselves doesn’t have any
+// parental controls, and two users cannot be logged in at the same time (we
+// don’t allow fast user switching).
+var ParentalControlsManager = GObject.registerClass({
+    Signals: {
+        'app-filter-changed': {},
+    },
+}, class ParentalControlsManager extends GObject.Object {
+    _init() {
+        super._init();
+
+        this._disabled = false;
+        this._appFilter = null;
+
+        log(`Getting parental controls for user ${Shell.util_get_uid()}`);
+        let connection = Gio.bus_get_sync(Gio.BusType.SYSTEM, null);
+        this._manager = new Malcontent.Manager({ connection });
+
+        try {
+            this._appFilter = this._manager.get_app_filter(
+                Shell.util_get_uid(),
+                Malcontent.ManagerGetValueFlags.NONE,
+                null);
+        } catch (e) {
+            if (e.matches(Malcontent.ManagerError, Malcontent.ManagerError.DISABLED)) {
+                log('Parental controls globally disabled');
+                this._disabled = true;
+            } else {
+                logError(e, 'Failed to get parental controls settings');
+            }
+        }
+
+        this._manager.connect('app-filter-changed', (manager, uid) => {
+            let current_uid = Shell.util_get_uid();
+            // Emit 'changed' signal only if app-filter is changed for currently logged-in user.
+            if (current_uid === uid) {
+                this._manager.get_app_filter_async(
+                    current_uid,
+                    Malcontent.ManagerGetValueFlags.NONE,
+                    null,
+                    this._onAppFilterChanged.bind(this));
+            }
+        });
+    }
+
+    _onAppFilterChanged(object, res) {
+        try {
+            this._appFilter = this._manager.get_app_filter_finish(res);
+            this.emit('app-filter-changed');
+        } catch (e) {
+            logError(e, `Failed to get new MctAppFilter for uid ${Shell.util_get_uid()} on 
app-filter-changed`);
+        }
+    }
+    // Calculate whether the given app (a Gio.DesktopAppInfo) should be shown
+    // on the desktop, in search results, etc. The app should be shown if:
+    //  - The .desktop file doesn’t say it should be hidden.
+    //  - The executable from the .desktop file’s Exec line isn’t blacklisted in
+    //    the user’s parental controls.
+    //  - None of the flatpak app IDs from the X-Flatpak and the
+    //    X-Flatpak-RenamedFrom lines are blacklisted in the user’s parental
+    //    controls.
+    shouldShowApp(appInfo) {
+        // Quick decision?
+        if (!appInfo.should_show())
+            return false;
+
+        // Are parental controls disabled at runtime?
+        if (this._disabled)
+            return true;
+
+        // Have we finished initialising yet?
+        if (!this._appFilter) {
+            log(`Warning: Hiding app because parental controls not yet initialised: ${appInfo.get_id()}`);
+            return false;
+        }
+
+        return this._appFilter.is_appinfo_allowed(appInfo);
+    }
+});
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index e712719973..d0bf322b81 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -13,6 +13,7 @@ const IconGrid = imports.ui.iconGrid;
 const IconGridLayout = imports.ui.iconGridLayout;
 const Main = imports.ui.main;
 const PageIndicators = imports.ui.pageIndicators;
+const ParentalControlsManager = imports.misc.parentalControlsManager;
 const PopupMenu = imports.ui.popupMenu;
 const Search = imports.ui.search;
 const SwipeTracker = imports.ui.swipeTracker;
@@ -1049,6 +1050,7 @@ var AppSearchProvider = class AppSearchProvider {
         let usage = Shell.AppUsage.get_default();
         let results = [];
         let replacementMap = {};
+        let parentalControlsManager = ParentalControlsManager.getDefault();
 
         groups.forEach(group => {
             group = group.filter(appID => {
@@ -1060,6 +1062,10 @@ var AppSearchProvider = class AppSearchProvider {
                     return false;
 
                 const app = this._appSys.lookup_app(appID);
+
+                if (!parentalControlsManager.shouldShowApp(app.app_info))
+                    return false;
+
                 if (app && app.app_info.should_show()) {
                     let replacedByID = app.app_info.get_string(EOS_REPLACED_BY_KEY);
                     if (replacedByID)
diff --git a/js/ui/iconGridLayout.js b/js/ui/iconGridLayout.js
index b9d87f81bb..4a36777052 100644
--- a/js/ui/iconGridLayout.js
+++ b/js/ui/iconGridLayout.js
@@ -6,6 +6,7 @@ const { EosMetrics, Gio, GLib, GObject, Json, Shell } = imports.gi;
 
 const Config = imports.misc.config;
 const Main = imports.ui.main;
+const ParentalControlsManager = imports.misc.parentalControlsManager;
 
 var DESKTOP_GRID_ID = 'desktop';
 
@@ -51,6 +52,12 @@ var IconGridLayout = GObject.registerClass({
     _init() {
         super._init();
 
+        this._parentalControlsManager = ParentalControlsManager.getDefault();
+        this._parentalControlsManager.connect('app-filter-changed', () => {
+            this._updateIconTree();
+            this.emit('layout-changed');
+        });
+
         this._updateIconTree();
 
         this._removeUndone = false;
@@ -69,6 +76,16 @@ var IconGridLayout = GObject.registerClass({
             let context = allIcons.get_child_value(i);
             let [folder] = context.get_child_value(0).get_string();
             let children = context.get_child_value(1).get_strv();
+
+            children = children.filter(appId => {
+                let app = appSys.lookup_alias(appId);
+                if (!app)
+                    return true;
+
+                // Ensure the app is not blacklisted.
+                return this._parentalControlsManager.shouldShowApp(app.get_app_info());
+            });
+
             iconTree[folder] = children.map(appId => {
                 // Some older versions of eos-app-store incorrectly added eos-app-*.desktop
                 // files to the icon grid layout, instead of the proper unprefixed .desktop
diff --git a/js/ui/main.js b/js/ui/main.js
index 0df23c0ef4..4a2e2e27bb 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -29,6 +29,7 @@ const Overview = imports.ui.overview;
 const PadOsd = imports.ui.padOsd;
 const Panel = imports.ui.panel;
 const Params = imports.misc.params;
+const ParentalControlsManager = imports.misc.parentalControlsManager;
 const RunDialog = imports.ui.runDialog;
 const Layout = imports.ui.layout;
 const LoginManager = imports.misc.loginManager;
@@ -143,6 +144,9 @@ function start() {
     sessionMode = new SessionMode.SessionMode();
     sessionMode.connect('updated', _sessionUpdated);
 
+    // Initialize ParentalControlsManager before the UI
+    ParentalControlsManager.getDefault();
+
     St.Settings.get().connect('notify::gtk-theme', _loadDefaultStylesheet);
     _initializeUI();
 
diff --git a/meson.build b/meson.build
index d1e6e75535..e4166addb2 100644
--- a/meson.build
+++ b/meson.build
@@ -35,6 +35,7 @@ gnome_desktop_req = '>= 3.35.90'
 bt_req = '>= 3.9.0'
 gst_req = '>= 0.11.92'
 nm_req = '>= 1.10.4'
+malcontent_req = '>= 0.3.0'
 secret_req = '>= 0.18'
 
 gnome = import('gnome')
@@ -114,6 +115,9 @@ endif
 # Endless-specific: Metrics
 eosmetrics_dep = dependency('eosmetrics-0')
 
+# Endless-specific: parental controls
+malcontent_dep = dependency('malcontent-0', version: malcontent_req, required: true)
+
 nm_deps = []
 if get_option('networkmanager')
   nm_deps += dependency('libnm', version: nm_req)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]