Blame SOURCES/0003-extensionSystem-Allow-extensions-to-run-on-the-login.patch

477568
From 7300ae2eac743fa06f40f6459ac8fbf739ab28ea Mon Sep 17 00:00:00 2001
477568
From: Ray Strode <rstrode@redhat.com>
477568
Date: Tue, 10 Aug 2021 15:03:50 -0400
477568
Subject: [PATCH 3/4] extensionSystem: Allow extensions to run on the login
477568
 screen
477568
477568
At the moment it's not realy possible to extend the login screen to do
477568
things it doesn't have built-in support for. This means in order
477568
to support niche use cases, those cases have to change the main
477568
code base. For instance, oVirt and Vmware deployments want to be able
477568
to automaticaly log in guest VMs when a user pre-authenticates through a
477568
console on a management host. To support those use cases, we added
477568
code to the login screen directly, even though most machines will never
477568
be associated with oVirt or Vmware management hosts.
477568
477568
We also get requests from e.g. government users that need certain features
477568
at the login screen that wouldn't get used much outside of government
477568
deployments. For instance, we've gotten requests that a machine contains
477568
prominently displays that it has "Top Secret" information.
477568
477568
All of these use cases seem like they would better handled via
477568
extensions that could be installed in the specific deployments. The
477568
problem is extensions only run in the user session, and get
477568
disabled at the login screen automatically.
477568
477568
This commit changes that. Now extensions can specify in their metadata
477568
via a new sessionModes property, which modes that want to run in. For
477568
backward compatibility, if an extension doesn't specify which session
477568
modes it works in, its assumed the extension only works in the user
477568
session.
477568
---
477568
 js/ui/extensionSystem.js | 43 ++++++++++++++++++++++++++++++++++++----
477568
 1 file changed, 39 insertions(+), 4 deletions(-)
477568
477568
diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js
477568
index 05630ed54..dfe82821e 100644
477568
--- a/js/ui/extensionSystem.js
477568
+++ b/js/ui/extensionSystem.js
477568
@@ -21,119 +21,147 @@ var ExtensionManager = class {
477568
     constructor() {
477568
         this._initted = false;
477568
         this._updateNotified = false;
477568
 
477568
         this._extensions = new Map();
477568
         this._enabledExtensions = [];
477568
         this._extensionOrder = [];
477568
 
477568
         Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
477568
     }
477568
 
477568
     init() {
477568
         this._installExtensionUpdates();
477568
         this._sessionUpdated();
477568
 
477568
         GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, UPDATE_CHECK_TIMEOUT, () => {
477568
             ExtensionDownloader.checkForUpdates();
477568
             return GLib.SOURCE_CONTINUE;
477568
         });
477568
         ExtensionDownloader.checkForUpdates();
477568
     }
477568
 
477568
     lookup(uuid) {
477568
         return this._extensions.get(uuid);
477568
     }
477568
 
477568
     getUuids() {
477568
         return [...this._extensions.keys()];
477568
     }
477568
 
477568
+    _extensionSupportsSessionMode(uuid) {
477568
+        let extension = this.lookup(uuid);
477568
+
477568
+        if (!extension)
477568
+            return false;
477568
+
477568
+        if (extension.sessionModes.includes(Main.sessionMode.currentMode))
477568
+            return true;
477568
+
477568
+        if (extension.sessionModes.includes(Main.sessionMode.parentMode))
477568
+            return true;
477568
+
477568
+        return false;
477568
+    }
477568
+
477568
+    _sessionModeCanUseExtension(uuid) {
477568
+        if (!Main.sessionMode.allowExtensions)
477568
+            return false;
477568
+
477568
+        if (!this._extensionSupportsSessionMode(uuid))
477568
+            return false;
477568
+
477568
+        return true;
477568
+    }
477568
+
477568
     _callExtensionDisable(uuid) {
477568
         let extension = this.lookup(uuid);
477568
         if (!extension)
477568
             return;
477568
 
477568
         if (extension.state != ExtensionState.ENABLED)
477568
             return;
477568
 
477568
         // "Rebase" the extension order by disabling and then enabling extensions
477568
         // in order to help prevent conflicts.
477568
 
477568
         // Example:
477568
         //   order = [A, B, C, D, E]
477568
         //   user disables C
477568
         //   this should: disable E, disable D, disable C, enable D, enable E
477568
 
477568
         let orderIdx = this._extensionOrder.indexOf(uuid);
477568
         let order = this._extensionOrder.slice(orderIdx + 1);
477568
         let orderReversed = order.slice().reverse();
477568
 
477568
         for (let i = 0; i < orderReversed.length; i++) {
477568
             let uuid = orderReversed[i];
477568
             try {
477568
                 this.lookup(uuid).stateObj.disable();
477568
             } catch (e) {
477568
                 this.logExtensionError(uuid, e);
477568
             }
477568
         }
477568
 
477568
         if (extension.stylesheet) {
477568
             let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
477568
             theme.unload_stylesheet(extension.stylesheet);
477568
             delete extension.stylesheet;
477568
         }
477568
 
477568
         try {
477568
             extension.stateObj.disable();
477568
         } catch(e) {
477568
             this.logExtensionError(uuid, e);
477568
         }
477568
 
477568
         for (let i = 0; i < order.length; i++) {
477568
             let uuid = order[i];
477568
             try {
477568
                 this.lookup(uuid).stateObj.enable();
477568
             } catch (e) {
477568
                 this.logExtensionError(uuid, e);
477568
             }
477568
         }
477568
 
477568
         this._extensionOrder.splice(orderIdx, 1);
477568
 
477568
         if (extension.state != ExtensionState.ERROR) {
477568
             extension.state = ExtensionState.DISABLED;
477568
             this.emit('extension-state-changed', extension);
477568
         }
477568
     }
477568
 
477568
     _callExtensionEnable(uuid) {
477568
+        if (!this._sessionModeCanUseExtension(uuid))
477568
+            return;
477568
+
477568
         let extension = this.lookup(uuid);
477568
         if (!extension)
477568
             return;
477568
 
477568
         if (extension.state == ExtensionState.INITIALIZED)
477568
             this._callExtensionInit(uuid);
477568
 
477568
         if (extension.state != ExtensionState.DISABLED)
477568
             return;
477568
 
477568
         this._extensionOrder.push(uuid);
477568
 
477568
         let stylesheetNames = [global.session_mode + '.css', 'stylesheet.css'];
477568
         let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
477568
         for (let i = 0; i < stylesheetNames.length; i++) {
477568
             try {
477568
                 let stylesheetFile = extension.dir.get_child(stylesheetNames[i]);
477568
                 theme.load_stylesheet(stylesheetFile);
477568
                 extension.stylesheet = stylesheetFile;
477568
                 break;
477568
             } catch (e) {
477568
                 if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
477568
                     continue; // not an error
477568
                 log(`Failed to load stylesheet for extension ${uuid}: ${e.message}`);
477568
                 return;
477568
             }
477568
         }
477568
 
477568
         try {
477568
             extension.stateObj.enable();
477568
@@ -231,61 +259,62 @@ var ExtensionManager = class {
477568
             throw new Error(`Failed to load metadata.json: ${e}`);
477568
         }
477568
         let meta;
477568
         try {
477568
             meta = JSON.parse(metadataContents);
477568
         } catch (e) {
477568
             throw new Error(`Failed to parse metadata.json: ${e}`);
477568
         }
477568
 
477568
         let requiredProperties = ['uuid', 'name', 'description', 'shell-version'];
477568
         for (let i = 0; i < requiredProperties.length; i++) {
477568
             let prop = requiredProperties[i];
477568
             if (!meta[prop]) {
477568
                 throw new Error(`missing "${prop}" property in metadata.json`);
477568
             }
477568
         }
477568
 
477568
         if (uuid != meta.uuid) {
477568
             throw new Error(`uuid "${meta.uuid}" from metadata.json does not match directory name "${uuid}"`);
477568
         }
477568
 
477568
         let extension = {
477568
             metadata: meta,
477568
             uuid: meta.uuid,
477568
             type,
477568
             dir,
477568
             path: dir.get_path(),
477568
             error: '',
477568
             hasPrefs: dir.get_child('prefs.js').query_exists(null),
477568
             hasUpdate: false,
477568
-            canChange: false
477568
+            canChange: false,
477568
+            sessionModes: meta['session-modes'] ? meta['session-modes'] : [ 'user' ],
477568
         };
477568
         this._extensions.set(uuid, extension);
477568
 
477568
         return extension;
477568
     }
477568
 
477568
     loadExtension(extension) {
477568
         // Default to error, we set success as the last step
477568
         extension.state = ExtensionState.ERROR;
477568
 
477568
         let checkVersion = !global.settings.get_boolean(EXTENSION_DISABLE_VERSION_CHECK_KEY);
477568
 
477568
         if (checkVersion && ExtensionUtils.isOutOfDate(extension)) {
477568
             extension.state = ExtensionState.OUT_OF_DATE;
477568
         } else {
477568
             let enabled = this._enabledExtensions.includes(extension.uuid);
477568
             if (enabled) {
477568
                 if (!this._callExtensionInit(extension.uuid))
477568
                     return;
477568
                 if (extension.state == ExtensionState.DISABLED)
477568
                     this._callExtensionEnable(extension.uuid);
477568
             } else {
477568
                 extension.state = ExtensionState.INITIALIZED;
477568
             }
477568
         }
477568
 
477568
         this._updateCanChange(extension);
477568
         this.emit('extension-state-changed', extension);
477568
     }
477568
 
477568
@@ -296,60 +325,63 @@ var ExtensionManager = class {
477568
         this._callExtensionDisable(extension.uuid);
477568
 
477568
         extension.state = ExtensionState.UNINSTALLED;
477568
         this.emit('extension-state-changed', extension);
477568
 
477568
         this._extensions.delete(extension.uuid);
477568
         return true;
477568
     }
477568
 
477568
     reloadExtension(oldExtension) {
477568
         // Grab the things we'll need to pass to createExtensionObject
477568
         // to reload it.
477568
         let { uuid: uuid, dir: dir, type: type } = oldExtension;
477568
 
477568
         // Then unload the old extension.
477568
         this.unloadExtension(oldExtension);
477568
 
477568
         // Now, recreate the extension and load it.
477568
         let newExtension;
477568
         try {
477568
             newExtension = this.createExtensionObject(uuid, dir, type);
477568
         } catch (e) {
477568
             this.logExtensionError(uuid, e);
477568
             return;
477568
         }
477568
 
477568
         this.loadExtension(newExtension);
477568
     }
477568
 
477568
     _callExtensionInit(uuid) {
477568
+        if (!this._sessionModeCanUseExtension(uuid))
477568
+            return false;
477568
+
477568
         let extension = this.lookup(uuid);
477568
         let dir = extension.dir;
477568
 
477568
         if (!extension)
477568
             throw new Error("Extension was not properly created. Call loadExtension first");
477568
 
477568
         let extensionJs = dir.get_child('extension.js');
477568
         if (!extensionJs.query_exists(null)) {
477568
             this.logExtensionError(uuid, new Error('Missing extension.js'));
477568
             return false;
477568
         }
477568
 
477568
         let extensionModule;
477568
         let extensionState = null;
477568
 
477568
         ExtensionUtils.installImporter(extension);
477568
         try {
477568
             extensionModule = extension.imports.extension;
477568
         } catch(e) {
477568
             this.logExtensionError(uuid, e);
477568
             return false;
477568
         }
477568
 
477568
         if (extensionModule.init) {
477568
             try {
477568
                 extensionState = extensionModule.init(extension);
477568
             } catch (e) {
477568
                 this.logExtensionError(uuid, e);
477568
                 return false;
477568
             }
477568
@@ -377,69 +409,72 @@ var ExtensionManager = class {
477568
 
477568
         let isMode = this._getModeExtensions().includes(extension.uuid);
477568
         let modeOnly = global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY);
477568
 
477568
         extension.canChange =
477568
             !hasError &&
477568
             global.settings.is_writable(ENABLED_EXTENSIONS_KEY) &&
477568
             (isMode || !modeOnly);
477568
     }
477568
 
477568
     _getEnabledExtensions() {
477568
         let extensions = this._getModeExtensions();
477568
 
477568
         if (global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY))
477568
             return extensions;
477568
 
477568
         return extensions.concat(global.settings.get_strv(ENABLED_EXTENSIONS_KEY));
477568
     }
477568
 
477568
     _onUserExtensionsEnabledChanged() {
477568
         this._onEnabledExtensionsChanged();
477568
         this._onSettingsWritableChanged();
477568
     }
477568
 
477568
     _onEnabledExtensionsChanged() {
477568
         let newEnabledExtensions = this._getEnabledExtensions();
477568
 
477568
         // Find and enable all the newly enabled extensions: UUIDs found in the
477568
         // new setting, but not in the old one.
477568
         newEnabledExtensions.filter(
477568
-            uuid => !this._enabledExtensions.includes(uuid)
477568
+            uuid => !this._enabledExtensions.includes(uuid) &&
477568
+                    this._extensionSupportsSessionMode(uuid)
477568
         ).forEach(uuid => {
477568
             this._callExtensionEnable(uuid);
477568
         });
477568
 
477568
         // Find and disable all the newly disabled extensions: UUIDs found in the
477568
-        // old setting, but not in the new one.
477568
+        // old setting, but not in the new one, and extensions that don't work with
477568
+        // the current session mode.
477568
         this._enabledExtensions.filter(
477568
-            item => !newEnabledExtensions.includes(item)
477568
+            item => !newEnabledExtensions.includes(item) ||
477568
+                    !this._extensionSupportsSessionMode(item)
477568
         ).forEach(uuid => {
477568
             this._callExtensionDisable(uuid);
477568
         });
477568
 
477568
         this._enabledExtensions = newEnabledExtensions;
477568
     }
477568
 
477568
     _onSettingsWritableChanged() {
477568
         for (let extension of this._extensions.values()) {
477568
             this._updateCanChange(extension);
477568
             this.emit('extension-state-changed', extension);
477568
         }
477568
     }
477568
 
477568
     _onVersionValidationChanged() {
477568
         // we want to reload all extensions, but only enable
477568
         // extensions when allowed by the sessionMode, so
477568
         // temporarily disable them all
477568
         this._enabledExtensions = [];
477568
 
477568
         // The loop modifies the extensions map, so iterate over a copy
477568
         let extensions = [...this._extensions.values()];
477568
         for (let extension of extensions)
477568
             this.reloadExtension(extension);
477568
         this._enabledExtensions = this._getEnabledExtensions();
477568
 
477568
         if (Main.sessionMode.allowExtensions) {
477568
             this._enabledExtensions.forEach(uuid => {
477568
                 this._callExtensionEnable(uuid);
477568
             });
477568
-- 
477568
2.27.0
477568