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

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