diff --git a/data/org.gnome.shell.gschema.xml.in b/data/org.gnome.shell.gschema.xml.in index cd6a2356ddf8ad86f1dd89f5f33812e24d9f08a1..a805d5262c698f4e41b6f034c82f28801e6ff655 100644 --- a/data/org.gnome.shell.gschema.xml.in +++ b/data/org.gnome.shell.gschema.xml.in @@ -104,14 +104,6 @@ number can be used to effectively disable the dialog. - - false - Enable introspection API - - Enables a D-Bus API that allows to introspect the application state of - the shell. - - { @@ -49,14 +44,7 @@ var IntrospectService = class { this._syncRunningApplications(); - this._allowlistMap = new Map(); - APP_ALLOWLIST.forEach(appName => { - Gio.DBus.watch_name(Gio.BusType.SESSION, - appName, - Gio.BusNameWatcherFlags.NONE, - (conn, name, owner) => this._allowlistMap.set(name, owner), - (conn, name) => this._allowlistMap.delete(name)); - }); + this._senderChecker = new DBusSenderChecker(APP_ALLOWLIST); this._settings = St.Settings.get(); this._settings.connect('notify::enable-animations', @@ -73,14 +61,6 @@ var IntrospectService = class { return app.get_windows().some(w => w.transient_for == null); } - _isIntrospectEnabled() { - return this._introspectSettings.get_boolean(INTROSPECT_KEY); - } - - _isSenderAllowed(sender) { - return [...this._allowlistMap.values()].includes(sender); - } - _getSandboxedAppId(app) { let ids = app.get_windows().map(w => w.get_sandboxed_app_id()); return ids.find(id => id != null); @@ -137,21 +117,11 @@ var IntrospectService = class { type == Meta.WindowType.UTILITY; } - _isInvocationAllowed(invocation) { - if (this._isIntrospectEnabled()) - return true; - - if (this._isSenderAllowed(invocation.get_sender())) - return true; - - return false; - } - GetRunningApplicationsAsync(params, invocation) { - if (!this._isInvocationAllowed(invocation)) { - invocation.return_error_literal(Gio.DBusError, - Gio.DBusError.ACCESS_DENIED, - 'App introspection not allowed'); + try { + this._senderChecker.checkInvocation(invocation); + } catch (e) { + invocation.return_gerror(e); return; } @@ -163,10 +133,10 @@ var IntrospectService = class { let apps = this._appSystem.get_running(); let windowsList = {}; - if (!this._isInvocationAllowed(invocation)) { - invocation.return_error_literal(Gio.DBusError, - Gio.DBusError.ACCESS_DENIED, - 'App introspection not allowed'); + try { + this._senderChecker.checkInvocation(invocation); + } catch (e) { + invocation.return_gerror(e); return; } diff --git a/js/misc/util.js b/js/misc/util.js index 8139d3f47cfcce2200f72731d5768210dd96ff96..bd5718472835672bc48b8761d9b401c94f41bb70 100644 --- a/js/misc/util.js +++ b/js/misc/util.js @@ -1,7 +1,8 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported findUrls, spawn, spawnCommandLine, spawnApp, trySpawnCommandLine, formatTime, formatTimeSpan, createTimeLabel, insertSorted, - ensureActorVisibleInScrollView, wiggle, lerp, GNOMEversionCompare */ + ensureActorVisibleInScrollView, wiggle, lerp, GNOMEversionCompare, + DBusSenderChecker */ const { Clutter, Gio, GLib, Shell, St, GnomeDesktop } = imports.gi; const Gettext = imports.gettext; @@ -477,3 +478,57 @@ function GNOMEversionCompare(version1, version2) { return 0; } + +var DBusSenderChecker = class { + /** + * @param {string[]} allowList - list of allowed well-known names + */ + constructor(allowList) { + this._allowlistMap = new Map(); + + this._watchList = allowList.map(name => { + return Gio.DBus.watch_name(Gio.BusType.SESSION, + name, + Gio.BusNameWatcherFlags.NONE, + (conn_, name_, owner) => this._allowlistMap.set(name, owner), + () => this._allowlistMap.delete(name)); + }); + } + + /** + * @param {string} sender - the bus name that invoked the checked method + * @returns {bool} + */ + _isSenderAllowed(sender) { + return [...this._allowlistMap.values()].includes(sender); + } + + /** + * Check whether the bus name that invoked @invocation maps + * to an entry in the allow list. + * + * @throws + * @param {Gio.DBusMethodInvocation} invocation - the invocation + * @returns {void} + */ + checkInvocation(invocation) { + if (global.context.unsafe_mode) + return; + + if (this._isSenderAllowed(invocation.get_sender())) + return; + + throw new GLib.Error(Gio.DBusError, + Gio.DBusError.ACCESS_DENIED, + '%s is not allowed'.format(invocation.get_method_name())); + } + + /** + * @returns {void} + */ + destroy() { + for (const id in this._watchList) + Gio.DBus.unwatch_name(id); + this._watchList = []; + } +}; diff --git a/js/ui/panel.js b/js/ui/panel.js index 89b082ad05f86c68b928e931a22243ff3486dce7..dafba690a57075d293201cf919d15c02451d0d9e 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -507,6 +507,20 @@ class PanelCorner extends St.DrawingArea { } }); +const UnsafeModeIndicator = GObject.registerClass( +class UnsafeModeIndicator extends PanelMenu.SystemIndicator { + _init() { + super._init(); + + this._indicator = this._addIndicator(); + this._indicator.icon_name = 'channel-insecure-symbolic'; + + global.context.bind_property('unsafe-mode', + this._indicator, 'visible', + GObject.BindingFlags.SYNC_CREATE); + } +}); + var AggregateLayout = GObject.registerClass( class AggregateLayout extends Clutter.BoxLayout { _init(params = {}) { @@ -568,6 +582,7 @@ class AggregateMenu extends PanelMenu.Button { this._location = new imports.ui.status.location.Indicator(); this._nightLight = new imports.ui.status.nightLight.Indicator(); this._thunderbolt = new imports.ui.status.thunderbolt.Indicator(); + this._unsafeMode = new UnsafeModeIndicator(); this._indicators.add_child(this._remoteAccess); this._indicators.add_child(this._thunderbolt); @@ -579,6 +594,7 @@ class AggregateMenu extends PanelMenu.Button { this._indicators.add_child(this._bluetooth); this._indicators.add_child(this._rfkill); this._indicators.add_child(this._volume); + this._indicators.add_child(this._unsafeMode); this._indicators.add_child(this._power); this._indicators.add_child(this._powerProfiles); diff --git a/js/ui/screenshot.js b/js/ui/screenshot.js index 81ab516b171483c8f8300e58bea72b753218c500..bf537b7d6d083326d3f8cce2449a57821c20cc73 100644 --- a/js/ui/screenshot.js +++ b/js/ui/screenshot.js @@ -15,6 +15,7 @@ Gio._promisify(Shell.Screenshot.prototype, 'screenshot_area', 'screenshot_area_finish'); const { loadInterfaceXML } = imports.misc.fileUtils; +const { DBusSenderChecker } = imports.misc.util; const ScreenshotIface = loadInterfaceXML('org.gnome.Shell.Screenshot'); @@ -24,6 +25,12 @@ var ScreenshotService = class { this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/Screenshot'); this._screenShooter = new Map(); + this._senderChecker = new DBusSenderChecker([ + 'org.gnome.SettingsDaemon.MediaKeys', + 'org.freedesktop.impl.portal.desktop.gtk', + 'org.freedesktop.impl.portal.desktop.gnome', + 'org.gnome.Screenshot', + ]); this._lockdownSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.lockdown' }); @@ -46,6 +53,13 @@ var ScreenshotService = class { Gio.IOErrorEnum, Gio.IOErrorEnum.PERMISSION_DENIED, 'Saving to disk is disabled'); return null; + } else { + try { + this._senderChecker.checkInvocation(invocation); + } catch (e) { + invocation.return_gerror(e); + return null; + } } let shooter = new Shell.Screenshot(); @@ -254,6 +268,13 @@ var ScreenshotService = class { } async SelectAreaAsync(params, invocation) { + try { + this._senderChecker.checkInvocation(invocation); + } catch (e) { + invocation.return_gerror(e); + return; + } + let selectArea = new SelectArea(); try { let areaRectangle = await selectArea.selectAsync(); @@ -269,6 +290,13 @@ var ScreenshotService = class { } FlashAreaAsync(params, invocation) { + try { + this._senderChecker.checkInvocation(invocation); + } catch (e) { + invocation.return_gerror(e); + return; + } + let [x, y, width, height] = params; [x, y, width, height] = this._scaleArea(x, y, width, height); if (!this._checkArea(x, y, width, height)) { diff --git a/js/ui/shellDBus.js b/js/ui/shellDBus.js index 6574cc5288a897df9efb437be359c5eb139d424c..a8070eb925cc7348b8467e413be7f301278bd911 100644 --- a/js/ui/shellDBus.js +++ b/js/ui/shellDBus.js @@ -10,6 +10,7 @@ const Main = imports.ui.main; const Screenshot = imports.ui.screenshot; const { loadInterfaceXML } = imports.misc.fileUtils; +const { DBusSenderChecker } = imports.misc.util; const { ControlsState } = imports.ui.overviewControls; const GnomeShellIface = loadInterfaceXML('org.gnome.Shell'); @@ -20,6 +21,11 @@ var GnomeShell = class { this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellIface, this); this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell'); + this._senderChecker = new DBusSenderChecker([ + 'org.gnome.ControlCenter', + 'org.gnome.SettingsDaemon.MediaKeys', + ]); + this._extensionsService = new GnomeShellExtensions(); this._screenshotService = new Screenshot.ScreenshotService(); @@ -54,7 +60,7 @@ var GnomeShell = class { * */ Eval(code) { - if (!global.settings.get_boolean('development-tools')) + if (!global.context.unsafe_mode) return [false, '']; let returnValue; @@ -72,11 +78,40 @@ var GnomeShell = class { return [success, returnValue]; } - FocusSearch() { + /** + * Focus the overview's search entry + * + * @param {...any} params - method parameters + * @param {Gio.DBusMethodInvocation} invocation - the invocation + * @returns {void} + */ + FocusSearchAsync(params, invocation) { + try { + this._senderChecker.checkInvocation(invocation); + } catch (e) { + invocation.return_gerror(e); + return; + } + Main.overview.focusSearch(); + invocation.return_value(null); } - ShowOSD(params) { + /** + * Show OSD with the specified parameters + * + * @param {...any} params - method parameters + * @param {Gio.DBusMethodInvocation} invocation - the invocation + * @returns {void} + */ + ShowOSDAsync([params], invocation) { + try { + this._senderChecker.checkInvocation(invocation); + } catch (e) { + invocation.return_gerror(e); + return; + } + for (let param in params) params[param] = params[param].deep_unpack(); @@ -97,17 +132,55 @@ var GnomeShell = class { icon = Gio.Icon.new_for_string(serializedIcon); Main.osdWindowManager.show(monitorIndex, icon, label, level, maxLevel); + invocation.return_value(null); } - FocusApp(id) { + /** + * Focus specified app in the overview's app grid + * + * @param {string} id - an application ID + * @param {Gio.DBusMethodInvocation} invocation - the invocation + * @returns {void} + */ + FocusAppAsync([id], invocation) { + try { + this._senderChecker.checkInvocation(invocation); + } catch (e) { + invocation.return_gerror(e); + return; + } + Main.overview.selectApp(id); + invocation.return_value(null); } - ShowApplications() { + /** + * Show the overview's app grid + * + * @param {...any} params - method parameters + * @param {Gio.DBusMethodInvocation} invocation - the invocation + * @returns {void} + */ + ShowApplicationsAsync(params, invocation) { + try { + this._senderChecker.checkInvocation(invocation); + } catch (e) { + invocation.return_gerror(e); + return; + } + Main.overview.show(ControlsState.APP_GRID); + invocation.return_value(null); } GrabAcceleratorAsync(params, invocation) { + try { + this._senderChecker.checkInvocation(invocation); + } catch (e) { + invocation.return_gerror(e); + return; + } + let [accel, modeFlags, grabFlags] = params; let sender = invocation.get_sender(); let bindingAction = this._grabAcceleratorForSender(accel, modeFlags, grabFlags, sender); @@ -115,6 +188,13 @@ var GnomeShell = class { } GrabAcceleratorsAsync(params, invocation) { + try { + this._senderChecker.checkInvocation(invocation); + } catch (e) { + invocation.return_gerror(e); + return; + } + let [accels] = params; let sender = invocation.get_sender(); let bindingActions = []; @@ -126,6 +206,13 @@ var GnomeShell = class { } UngrabAcceleratorAsync(params, invocation) { + try { + this._senderChecker.checkInvocation(invocation); + } catch (e) { + invocation.return_gerror(e); + return; + } + let [action] = params; let sender = invocation.get_sender(); let ungrabSucceeded = this._ungrabAcceleratorForSender(action, sender); @@ -134,6 +221,13 @@ var GnomeShell = class { } UngrabAcceleratorsAsync(params, invocation) { + try { + this._senderChecker.checkInvocation(invocation); + } catch (e) { + invocation.return_gerror(e); + return; + } + let [actions] = params; let sender = invocation.get_sender(); let ungrabSucceeded = true; @@ -214,6 +308,13 @@ var GnomeShell = class { } ShowMonitorLabelsAsync(params, invocation) { + try { + this._senderChecker.checkInvocation(invocation); + } catch (e) { + invocation.return_gerror(e); + return; + } + let sender = invocation.get_sender(); let [dict] = params; Main.osdMonitorLabeler.show(sender, dict); @@ -221,6 +322,13 @@ var GnomeShell = class { } HideMonitorLabelsAsync(params, invocation) { + try { + this._senderChecker.checkInvocation(invocation); + } catch (e) { + invocation.return_gerror(e); + return; + } + let sender = invocation.get_sender(); Main.osdMonitorLabeler.hide(sender); invocation.return_value(null);