diff --git a/data/dbus-interfaces/org.freedesktop.login1.Manager.xml b/data/dbus-interfaces/org.freedesktop.login1.Manager.xml index f40d498dc..ae7fca6d9 100644 --- a/data/dbus-interfaces/org.freedesktop.login1.Manager.xml +++ b/data/dbus-interfaces/org.freedesktop.login1.Manager.xml @@ -20,6 +20,10 @@ + + + + diff --git a/data/dbus-interfaces/org.freedesktop.login1.User.xml b/data/dbus-interfaces/org.freedesktop.login1.User.xml index d6f10b072..485188d24 100644 --- a/data/dbus-interfaces/org.freedesktop.login1.User.xml +++ b/data/dbus-interfaces/org.freedesktop.login1.User.xml @@ -2,5 +2,7 @@ + + diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js index 8a46559d9..14e708997 100644 --- a/js/gdm/authPrompt.js +++ b/js/gdm/authPrompt.js @@ -52,6 +52,7 @@ export const AuthPrompt = GObject.registerClass({ 'next': {}, 'prompted': {}, 'reset': {param_types: [GObject.TYPE_UINT]}, + 'verified': {}, }, }, class AuthPrompt extends St.BoxLayout { _init(gdmClient, mode) { @@ -404,6 +405,7 @@ export const AuthPrompt = GObject.registerClass({ this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED; this.cancelButton.reactive = false; this.cancelButton.can_focus = false; + this.emit('verified'); } _onReset() { diff --git a/js/misc/loginManager.js b/js/misc/loginManager.js index 1515fc9e3..edf451915 100644 --- a/js/misc/loginManager.js +++ b/js/misc/loginManager.js @@ -97,13 +97,12 @@ class LoginManagerSystemd extends Signals.EventEmitter { this._proxy = new SystemdLoginManager(Gio.DBus.system, 'org.freedesktop.login1', '/org/freedesktop/login1'); - this._userProxy = new SystemdLoginUser(Gio.DBus.system, - 'org.freedesktop.login1', - '/org/freedesktop/login1/user/self'); this._proxy.connectSignal('PrepareForSleep', this._prepareForSleep.bind(this)); this._proxy.connectSignal('SessionRemoved', this._sessionRemoved.bind(this)); + + this._userProxy = this._initUserProxy(); } async getCurrentSessionProxy() { @@ -113,19 +112,20 @@ class LoginManagerSystemd extends Signals.EventEmitter { let sessionId = GLib.getenv('XDG_SESSION_ID'); if (!sessionId) { log('Unset XDG_SESSION_ID, getCurrentSessionProxy() called outside a user session. Asking logind directly.'); - let [session, objectPath] = this._userProxy.Display; + let userProxy = await this._userProxy; + let [session, objectPath] = userProxy.Display; if (session) { log(`Will monitor session ${session}`); sessionId = session; } else { log('Failed to find "Display" session; are we the greeter?'); - for ([session, objectPath] of this._userProxy.Sessions) { + for ([session, objectPath] of userProxy.Sessions) { let sessionProxy = new SystemdLoginSession(Gio.DBus.system, 'org.freedesktop.login1', objectPath); log(`Considering ${session}, class=${sessionProxy.Class}`); - if (sessionProxy.Class === 'greeter') { + if (sessionProxy.Class === 'greeter' || sessionProxy.Class === "lock-screen") { log(`Yes, will monitor session ${session}`); sessionId = session; break; @@ -217,6 +217,27 @@ class LoginManagerSystemd extends Signals.EventEmitter { _sessionRemoved(proxy, sender, [sessionId]) { this.emit('session-removed', sessionId); } + + async _initUserProxy() { + let [objectPath] = await this._proxy.GetUserByPIDAsync(0) + + let proxy = new SystemdLoginUser(Gio.DBus.system, + 'org.freedesktop.login1', + objectPath); + + return proxy; + } + + async canSecureLock() { + const proxy = await this._userProxy; + return proxy.CanSecureLock; + } + + async secureLock() { + const proxy = await this._userProxy; + if (proxy.CanSecureLock) + await proxy.SecureLockAsync(0); + } } class LoginManagerDummy extends Signals.EventEmitter { @@ -261,4 +282,11 @@ class LoginManagerDummy extends Signals.EventEmitter { async inhibit() { return null; } + + async canSecureLock() { + return false; + } + + async secureLock() { + } } diff --git a/js/misc/systemActions.js b/js/misc/systemActions.js index efb875bac..45d9a2d0e 100644 --- a/js/misc/systemActions.js +++ b/js/misc/systemActions.js @@ -21,6 +21,7 @@ const ALWAYS_SHOW_LOG_OUT_KEY = 'always-show-log-out'; const POWER_OFF_ACTION_ID = 'power-off'; const RESTART_ACTION_ID = 'restart'; const LOCK_SCREEN_ACTION_ID = 'lock-screen'; +const SECURE_LOCK_ACTION_ID = 'secure-lock' const LOGOUT_ACTION_ID = 'logout'; const SUSPEND_ACTION_ID = 'suspend'; const SWITCH_USER_ACTION_ID = 'switch-user'; @@ -57,6 +58,10 @@ const SystemActions = GObject.registerClass({ 'can-lock-screen', 'can-lock-screen', 'can-lock-screen', GObject.ParamFlags.READABLE, false), + 'can-secure-lock': GObject.ParamSpec.boolean( + 'can-secure-lock', 'can-secure-lock', 'can-secure-lock', + GObject.ParamFlags.READABLE, + false), 'can-switch-user': GObject.ParamSpec.boolean( 'can-switch-user', 'can-switch-user', 'can-switch-user', GObject.ParamFlags.READABLE, @@ -110,6 +115,14 @@ const SystemActions = GObject.registerClass({ keywords: tokenizeKeywords(_('lock screen')), available: false, }); + this._actions.set(SECURE_LOCK_ACTION_ID, { + // Translators: The name of the secure lock action in search + name: C_('search-result', 'Secure Lock'), + iconName: 'security-high-symbolic', + // Translators: A list of keywords that match the secure lock action, separated by semicolons + keywords: tokenizeKeywords(_('encrypt;lock down;lockdown')), + available: false, + }); this._actions.set(LOGOUT_ACTION_ID, { // Translators: The name of the logout action in search name: C_('search-result', 'Log Out'), @@ -176,8 +189,10 @@ const SystemActions = GObject.registerClass({ global.settings.connect(`changed::${ALWAYS_SHOW_LOG_OUT_KEY}`, () => this._updateLogout()); - this._lockdownSettings.connect(`changed::${DISABLE_LOCK_SCREEN_KEY}`, - () => this._updateLockScreen()); + this._lockdownSettings.connect(`changed::${DISABLE_LOCK_SCREEN_KEY}`, () => { + this._updateLockScreen(); + this._updateSecureLock(); + }); this._lockdownSettings.connect(`changed::${DISABLE_LOG_OUT_KEY}`, () => this._updateHaveShutdown()); @@ -215,6 +230,10 @@ const SystemActions = GObject.registerClass({ return this._actions.get(LOCK_SCREEN_ACTION_ID).available; } + get canSecureLock() { + return this._actions.get(SECURE_LOCK_ACTION_ID).available; + } + get canSwitchUser() { return this._actions.get(SWITCH_USER_ACTION_ID).available; } @@ -271,6 +290,9 @@ const SystemActions = GObject.registerClass({ // latter, so their value may be outdated; force an update now this._updateHaveShutdown(); this._updateHaveSuspend(); + + // CanSecureLock changes w/o notification in logind + this._updateSecureLock(); } getMatchingActions(terms) { @@ -311,6 +333,9 @@ const SystemActions = GObject.registerClass({ case LOCK_SCREEN_ACTION_ID: this.activateLockScreen(); break; + case SECURE_LOCK_ACTION_ID: + this.activateSecureLock(); + break; case LOGOUT_ACTION_ID: this.activateLogout(); break; @@ -330,12 +355,21 @@ const SystemActions = GObject.registerClass({ } _updateLockScreen() { - let showLock = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter; - let allowLockScreen = !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY); - this._actions.get(LOCK_SCREEN_ACTION_ID).available = showLock && allowLockScreen && LoginManager.canLock(); + let showLock = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter && + !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY); + let canLock = LoginManager.canLock(); + this._actions.get(LOCK_SCREEN_ACTION_ID).available = showLock && canLock; this.notify('can-lock-screen'); } + async _updateSecureLock() { + let showLock = !Main.sessionMode.isGreeter && + !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY); + let canSecureLock = await this._loginManager.canSecureLock(); + this._actions.get(SECURE_LOCK_ACTION_ID).available = showLock && canSecureLock; + this.notify('can-secure-lock'); + } + async _updateHaveShutdown() { try { const [canShutdown] = await this._session.CanShutdownAsync(); @@ -423,6 +457,13 @@ const SystemActions = GObject.registerClass({ Main.screenShield.lock(true); } + activateSecureLock() { + if (!this._actions.get(SECURE_LOCK_ACTION_ID).available) + throw new Error('The secure-lock action is not available!'); + + this._loginManager.secureLock(); + } + activateSwitchUser() { if (!this._actions.get(SWITCH_USER_ACTION_ID).available) throw new Error('The switch-user action is not available!'); diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js index d2236fb90..f1e0c9a01 100644 --- a/js/ui/screenShield.js +++ b/js/ui/screenShield.js @@ -178,7 +178,7 @@ export class ScreenShield extends Signals.EventEmitter { _activateDialog() { if (this._isLocked) { - this._ensureUnlockDialog(true /* allowCancel */); + this._ensureUnlockDialog(); this._dialog.activate(); } else { this.deactivate(true /* animate */); @@ -357,7 +357,8 @@ export class ScreenShield extends Signals.EventEmitter { this.actor.show(); this._isGreeter = Main.sessionMode.isGreeter; this._isLocked = true; - this._ensureUnlockDialog(true); + this._lockScreenState = MessageTray.State.SHOWN; + this._ensureUnlockDialog(); } _hideLockScreenComplete() { @@ -416,7 +417,7 @@ export class ScreenShield extends Signals.EventEmitter { this._showPointer(); } - _ensureUnlockDialog(allowCancel) { + _ensureUnlockDialog() { if (!this._dialog) { let constructor = Main.sessionMode.unlockDialog; if (!constructor) { @@ -440,7 +441,6 @@ export class ScreenShield extends Signals.EventEmitter { 'wake-up-screen', this._wakeUpScreen.bind(this)); } - this._dialog.allowCancel = allowCancel; this._dialog.grab_key_focus(); return true; } @@ -605,7 +605,7 @@ export class ScreenShield extends Signals.EventEmitter { if (this._activationTime === 0) this._activationTime = GLib.get_monotonic_time(); - if (!this._ensureUnlockDialog(true)) + if (!this._ensureUnlockDialog()) return; this.actor.show(); @@ -620,6 +620,7 @@ export class ScreenShield extends Signals.EventEmitter { animateLockScreen: animate, fadeToBlack: true, }); + // On wayland, a crash brings down the entire session, so we don't // need to defend against being restarted unlocked if (!Meta.is_wayland_compositor()) diff --git a/js/ui/sessionMode.js b/js/ui/sessionMode.js index 895b507e1..bbfbdc054 100644 --- a/js/ui/sessionMode.js +++ b/js/ui/sessionMode.js @@ -78,6 +78,19 @@ const _modes = { panelStyle: 'unlock-screen', }, + 'lock-screen': { + isGreeter: true, + isPrimary: true, + unlockDialog: UnlockDialog, + components: ['polkitAgent'], + panel: { + left: [], + center: [], + right: ['dwellClick', 'a11y', 'keyboard', 'quickSettings'], + }, + panelStyle: 'unlock-screen', + }, + 'user': { hasOverview: true, showCalendarEvents: true, diff --git a/js/ui/status/system.js b/js/ui/status/system.js index ff9fa5624..dbdb68ea0 100644 --- a/js/ui/status/system.js +++ b/js/ui/status/system.js @@ -192,6 +192,11 @@ class ShutdownItem extends QuickSettingsItem { this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + this._addSystemAction(_('Secure Lock'), 'can-secure-lock', () => { + this._systemActions.activateSecureLock(); + Main.panel.closeQuickSettings(); + }); + this._addSystemAction(_('Log Out…'), 'can-logout', () => { this._systemActions.activateLogout(); Main.panel.closeQuickSettings(); diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js index f741b20a6..6083a74af 100644 --- a/js/ui/unlockDialog.js +++ b/js/ui/unlockDialog.js @@ -535,21 +535,38 @@ export const UnlockDialog = GObject.registerClass({ this.add_action(tapAction); // Background - this._backgroundGroup = new Clutter.Actor(); - this.add_child(this._backgroundGroup); + if (!Main.sessionMode.isGreeter) { + // We don't currently support storing a background picture for + // user accounts, so for now the secure lock screen will just have + // a blank background. - this._bgManagers = []; + this._backgroundGroup = new Clutter.Actor(); + this.add_child(this._backgroundGroup); - const themeContext = St.ThemeContext.get_for_stage(global.stage); - themeContext.connectObject('notify::scale-factor', - () => this._updateBackgroundEffects(), this); + this._bgManagers = []; - this._updateBackgrounds(); - Main.layoutManager.connectObject('monitors-changed', - this._updateBackgrounds.bind(this), this); + const themeContext = St.ThemeContext.get_for_stage(global.stage); + themeContext.connectObject('notify::scale-factor', + () => this._updateBackgroundEffects(), this); + + this._updateBackgrounds(); + Main.layoutManager.connectObject('monitors-changed', + this._updateBackgrounds.bind(this), this); + } this._userManager = AccountsService.UserManager.get_default(); - this._userName = GLib.get_user_name(); + if (!Main.sessionMode.isGreeter) { + this._userName = GLib.get_user_name(); + } else { + this._userName = GLib.getenv('GDM_LOCK_SCREEN_USER'); + if (!this._userName) { + // In the secure lock screen, this is a hard error. Fail-whale + const error = new GLib.Error( + Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED, + 'Could not get user for GDM lock screen'); + global.context.terminate_with_error(error); + } + } this._user = this._userManager.get_user(this._userName); // Authentication & Clock stack @@ -565,8 +582,6 @@ export const UnlockDialog = GObject.registerClass({ this._stack.add_child(this._clock); this._showClock(); - this.allowCancel = false; - Main.ctrlAltTabManager.addGroup(this, _('Unlock Window'), 'dialog-password-symbolic'); // Notifications @@ -706,6 +721,7 @@ export const UnlockDialog = GObject.registerClass({ this._authPrompt.connect('failed', this._fail.bind(this)); this._authPrompt.connect('cancelled', this._fail.bind(this)); this._authPrompt.connect('reset', this._onReset.bind(this)); + this._authPrompt.connect('verified', this._onVerified.bind(this)); this._promptBox.add_child(this._authPrompt); } @@ -802,8 +818,16 @@ export const UnlockDialog = GObject.registerClass({ this._authPrompt.begin({userName}); } + _onVerified() { + // Make sure we don't get stuck at the successfully authenticated + // prompt, when we're running as the secure lock screen in GDM and + // the user immediately re-triggers a secure-lock + if (Main.sessionMode.isGreeter) + this._showClock(); + } + _escape() { - if (this._authPrompt && this.allowCancel) + if (this._authPrompt && !Main.sessionMode.isGreeter) this._authPrompt.cancel(); } @@ -862,10 +886,11 @@ export const UnlockDialog = GObject.registerClass({ } _updateUserSwitchVisibility() { - this._otherUserButton.visible = this._userManager.can_switch() && - this._userManager.has_multiple_users && - this._screenSaverSettings.get_boolean('user-switch-enabled') && - !this._lockdownSettings.get_boolean('disable-user-switching'); + let needed = this._userManager.can_switch() && + this._userManager.has_multiple_users; + let enabled = this._screenSaverSettings.get_boolean('user-switch-enabled') && + !this._lockdownSettings.get_boolean('disable-user-switching'); + this._otherUserButton.visible = needed && (enabled || Main.sessionMode.isGreeter); } cancel() {