diff --git a/config/dbus/org.kde.LogoutPrompt.service b/config/dbus/org.kde.LogoutPrompt.service new file mode 100644 index 0000000..ea511fb --- /dev/null +++ b/config/dbus/org.kde.LogoutPrompt.service @@ -0,0 +1,28 @@ +[D-BUS Service] +# Plasma's session screen, made unstartable. +# +# The third and last layer over Ctrl+Alt+Delete, and the one that does not +# depend on knowing which key is bound to what. +# +# Ctrl+Alt+Delete does not draw anything itself. It asks D-Bus for +# org.kde.LogoutPrompt, and D-Bus starts ksmserver-logout-greeter, which +# is the screen with Log Out, Restart, Shut Down, Sleep and Hibernate on +# it. Unbinding the chord in kglobalshortcutsrc takes away the caller; +# this takes away the callee, so any other caller — a Plasma release that +# binds the prompt to something new, an applet that ends up on the panel +# by accident, a script — finds nothing to start either. +# +# The same shadowing mechanism the classroom already uses for the old +# notification daemon: the session bus searches XDG_DATA_DIRS in order and +# /usr/local/share comes before /usr/share, so no file belonging to +# plasma-workspace is touched and Plasma can be upgraded underneath this +# without anything here changing. +# +# What this does NOT do: end a session. Logging out, restarting and +# shutting down are all still there for anyone with an account on the +# machine, through systemctl and loginctl, which is how a technician does +# it already. The greeter is a teacher-facing screen in a room that has no +# other teacher-facing way to do any of it, and that is the only thing +# taken away. +Name=org.kde.LogoutPrompt +Exec=/bin/false diff --git a/config/plasma/kglobalshortcutsrc b/config/plasma/kglobalshortcutsrc new file mode 100644 index 0000000..fa6578a --- /dev/null +++ b/config/plasma/kglobalshortcutsrc @@ -0,0 +1,86 @@ +# The keys that end a classroom session, taken away. +# +# A teacher pressed Ctrl+Alt+Delete, got Plasma's session screen with +# Hibernate on it, and hibernated the room. This file is what closes that, +# and it is worth being precise about why the lockdown already in +# kdeglobals did not. +# +# [KDE Action Restrictions] logout=false stops *Plasma's own* logout +# actions — the ones plasmashell draws in a menu or an applet. The +# Ctrl+Alt+Delete chord is not one of those. It is a KGlobalAccel +# shortcut registered by ksmserver, and pressing it calls the logout +# greeter over D-Bus without ever asking a Plasma action whether it is +# allowed. A restriction on a menu cannot see a key. +# +# WHY THIS FILE IS NOT IN THE XDG DIRECTORY WITH THE OTHERS. +# +# Every other classroom setting is installed into +# /etc/ekklesia-classroom/xdg and reaches Plasma through KConfig's +# cascade. That was tried here first and it does not work, which was +# measured rather than reasoned about: with this file in the cascade, +# kreadconfig6 reported the chord as "none" while kglobalacceld went on +# answering Ctrl+Alt+Del. kglobalacceld opens kglobalshortcutsrc as a +# SimpleConfig — no cascade, no system directories, no immutability from +# anywhere but the file itself — so it reads exactly one file, +# ~/.config/kglobalshortcutsrc, and nothing else. +# +# So this is installed to /etc/ekklesia-classroom/kglobalshortcutsrc and +# classroom-desktop-reset copies it into the teacher's home before the +# session's shortcut server starts. That is the same thing the classroom +# already does with the panel: the file in the home directory is not +# state, it is a copy of the package, replaced at every login so that an +# update can always reach a room. +# +# KGlobalAccel stores every shortcut as +# +# Name=active,default,friendly name +# +# and repopulates whatever is missing from each component's registered +# defaults when that component starts — which is why replacing the whole +# file costs nothing: everything not named below comes straight back. +# Measured in the lab, including the maintenance chord, which is +# registered from its desktop entry and is bound again after the replace. +# +# Each key is marked [$i] — immutable — and that is what makes the +# replacement stick: KGlobalAccel writes this file constantly, and +# without the marker it would put the chord back the moment ksmserver +# registered its actions again. +# +# Per key rather than per group, deliberately. Marking +# [org_kde_powerdevil] immutable as a whole would also freeze the +# brightness and keyboard backlight keys, which a classroom has no reason +# to take away. +# +# The default column is set to none as well as the active one, so that +# "reset to default" is not a way back to the chord. +# +# This is one of three layers. The other two are the polkit rule that +# makes the sleep states unavailable to the classroom user at all, and the +# D-Bus service file that leaves the greeter itself with nothing to start. + +[ksmserver] +# The chord itself. +Log Out[$i]=none,none,Show Logout Screen +# And every other way ksmserver offers to end the session. All of these +# ship unbound; they are written out so that a future Plasma release +# cannot introduce a default binding into a room without this file being +# updated too. +Log Out Without Confirmation[$i]=none,none,Log Out Without Confirmation +LogOut[$i]=none,none,Log Out +Halt Without Confirmation[$i]=none,none,Shut Down Without Confirmation +Reboot[$i]=none,none,Reboot +Reboot Without Confirmation[$i]=none,none,Reboot Without Confirmation +Shut Down[$i]=none,none,Shut Down + +[org_kde_powerdevil] +# Sleep and Hibernate are bound by default to the XF86Sleep and +# XF86Hibernate keys, which a classroom keyboard may well have. PowerDown +# and PowerOff are the same story for XF86PowerDown and XF86PowerOff. +# +# The power *button* is already ignored at two layers; these are the +# keyboard keys of the same name, which are a different path and were +# never covered. +Sleep[$i]=none,none,Suspend +Hibernate[$i]=none,none,Hibernate +PowerDown[$i]=none,none,Power Down +PowerOff[$i]=none,none,Power Off diff --git a/config/polkit/org.ekklesia.classroom.sleep.rules b/config/polkit/org.ekklesia.classroom.sleep.rules new file mode 100644 index 0000000..3c6fd31 --- /dev/null +++ b/config/polkit/org.ekklesia.classroom.sleep.rules @@ -0,0 +1,59 @@ +// The classroom user cannot put the room to sleep. +// +// The second layer under the shortcut that was removed from +// kglobalshortcutsrc, and the one that matters: a key can be unbound, but +// anything that can reach logind can still ask for a suspend. PowerDevil +// does, the logout greeter does, `systemctl suspend` from the maintenance +// terminal does, and a future Plasma applet would too. They all go through +// the same polkit actions, so this is the one place that answers all of +// them at once. +// +// Answering here rather than masking sleep.target is deliberate. Masking +// is machine-wide and would take suspend away from a technician's own +// account as well; worse, it would be invisible to Plasma, which would go +// on offering a Hibernate button that silently failed. logind is asked +// whether this *user* may sleep, so a NO here is both enforcement and the +// supported way to tell the desktop not to offer it: CanSuspend() and +// CanHibernate() come back "no" and Plasma stops drawing the buttons. +// +// Scoped to the classroom user by name. Every other account — a +// technician's, root's — is left entirely alone, and nothing here touches +// power-off or reboot for anybody. A tech standing at the machine, or on +// SSH, can still shut it down and start it up. +// +// To lift this for maintenance, drop a file of the same name into +// /etc/polkit-1/rules.d/, which polkit reads ahead of this one. + +polkit.addRule(function (action, subject) { + if (subject.user !== "classroom") { + return polkit.Result.NOT_HANDLED; + } + + // Every sleep state logind exposes, present and future. systemd maps + // HybridSleep() and SuspendThenHibernate() onto the hibernate + // actions today rather than giving them action ids of their own; the + // two spellings are matched anyway so that a systemd release which + // splits them out does not quietly reopen this. + // + // The -multiple-sessions and -ignore-inhibit variants are the same + // operation asked for in different circumstances, so the prefixes are + // matched rather than the exact ids. + var sleeping = [ + "org.freedesktop.login1.suspend", + "org.freedesktop.login1.hibernate", + "org.freedesktop.login1.hybrid-sleep", + "org.freedesktop.login1.suspend-then-hibernate", + ]; + + for (var i = 0; i < sleeping.length; i++) { + if (action.id.indexOf(sleeping[i]) === 0) { + polkit.log( + "ekklesia-classroom: refusing " + action.id + + " for the classroom user" + ); + return polkit.Result.NO; + } + } + + return polkit.Result.NOT_HANDLED; +}); diff --git a/docs/kiosk.md b/docs/kiosk.md new file mode 100644 index 0000000..196cd80 --- /dev/null +++ b/docs/kiosk.md @@ -0,0 +1,118 @@ +# Lockdown + +What a teacher can and cannot do to the machine, and where each of those +answers lives. + +Most of it is KDE's own Kiosk mechanism, in +`/etc/ekklesia-classroom/xdg/kdeglobals`: no shell, no command line, no +lock screen, no editable panel. That file is the first place to look, and +lifting a restriction for maintenance is a one-line edit there followed by +a session restart. + +This page is about the part that is *not* in that file: ending or +suspending the session. + +## The escape this closed + +A teacher pressed **Ctrl+Alt+Delete**, got Plasma's session screen, and +hibernated the classroom computer from it. A hibernated room cannot be +recovered by anybody standing in front of it who does not know where the +power button is, which on a Sunday morning is everybody. + +`[KDE Action Restrictions] logout=false` was already set and did not stop +it, and the reason is worth keeping written down: that restriction governs +**Plasma's own actions** — the entries plasmashell draws in a menu or an +applet. Ctrl+Alt+Delete is not one of them. It is a KGlobalAccel shortcut +registered by ksmserver, and pressing it asks D-Bus for the logout greeter +without ever consulting a Plasma action. A restriction on a menu cannot +see a key. + +## Three layers + +### 1. The key + +`/etc/ekklesia-classroom/kglobalshortcutsrc` unbinds every ksmserver +session action and PowerDevil's Sleep, Hibernate, PowerDown and PowerOff +keys, with each entry marked `[$i]` so nothing writes them back. + +It is **not** in `/etc/ekklesia-classroom/xdg` with the other settings, and +that is deliberate. `kglobalacceld` opens `kglobalshortcutsrc` as a +KConfig `SimpleConfig`: no cascade, no system directories. Measured — with +the file in the cascade, `kreadconfig6` reported the chord unbound while +the shortcut server went on answering Ctrl+Alt+Del. + +So `classroom-desktop-reset` copies the package's copy into +`~/.config/kglobalshortcutsrc` before the session's shortcut server +starts, exactly as it does with the panel. Replacing the whole file costs +nothing: KGlobalAccel puts every shortcut the file does not name back from +each component's own defaults, and the entries the package does name are +immutable, which is what stops it putting those back too. + +The room's own chords are registered from their desktop entries and are +unaffected — `converged-check` asserts both of them by key code after the +replace. + +### 2. The policy + +`/usr/share/polkit-1/rules.d/49-ekklesia-classroom-sleep.rules` refuses +`org.freedesktop.login1.suspend*` and `.hibernate*` **for the `classroom` +user only**. + +This is the layer that matters, because a key can be unbound and anything +that reaches logind can still ask. PowerDevil, the greeter, `systemctl +suspend` from the maintenance terminal and any future applet all go +through these actions. + +Hybrid sleep and suspend-then-hibernate have no polkit action of their own +in systemd 257: logind authorises both through the hibernate action, so +denying hibernate denies all three. The rule names the other two spellings +anyway, in case a future systemd splits them out. + +Refusing here rather than masking `sleep.target` is the point. Masking is +machine-wide and would take suspend away from a technician's account too; +worse, it would be invisible to Plasma, which would go on drawing a +Hibernate button that silently failed. logind asks polkit whether *this +user* may sleep, so a NO is both the enforcement and the supported way to +tell the desktop not to offer it — `CanSuspend()` and `CanHibernate()` +come back `no`. + +Nothing here touches power-off or reboot, and nothing here applies to any +other account. + +### 3. The greeter + +`/usr/local/share/dbus-1/services/org.kde.LogoutPrompt.service` shadows +plasma-workspace's own activation file with `Exec=/bin/false`, the same +mechanism the classroom already uses for the old notification daemon. + +Ctrl+Alt+Delete does not draw anything itself; it asks D-Bus to start +`ksmserver-logout-greeter`. Layer 1 removes the caller, this removes the +callee, so any other caller finds nothing to start either. + +## What is deliberately still there + +- **Screen locking stays disabled.** None of this touches + `kscreenlockerrc`, and `action/lock_screen=false` is unchanged. A + classroom must never present a password prompt. +- **The power button still does nothing** on a short press, at both the + logind and PowerDevil layers, and a long firmware hold still cuts the + power. That is the only recovery a person standing at a wedged machine + has. +- **Meta+Ctrl+Alt+Shift+K still opens the maintenance terminal**, and + Meta+Ctrl+Alt+Shift+D still opens the display dialog. +- **Reboot and shut down are not newly denied to anybody.** A teacher + asking for either already lands on an administrator's password prompt, + which they do not have; that behaviour is unchanged. A technician on SSH + or at the maintenance terminal, with their own account, can still do all + of it. + +## Lifting it + +For a technician who needs a classroom machine to suspend — a laptop on a +bench, say — drop a file of the same name into `/etc/polkit-1/rules.d/`, +which polkit reads ahead of the package's copy. Nothing needs rebuilding +and nothing needs reinstalling. + +To put the session screen back, remove the D-Bus shadow and the +`kglobalshortcutsrc` entries; both are package files, so an update will +restore them, which is the intended direction. diff --git a/scripts/classroom-desktop-reset b/scripts/classroom-desktop-reset index 96bac07..5e9a2ec 100755 --- a/scripts/classroom-desktop-reset +++ b/scripts/classroom-desktop-reset @@ -123,6 +123,37 @@ if [[ -f "$CONFIG_HOME/kdeglobals" ]]; then esac fi +# THE KEYS THAT END A SESSION. +# +# Ctrl+Alt+Delete opened Plasma's session screen in a real classroom and a +# teacher hibernated the machine from it. The chord is a KGlobalAccel +# shortcut, and KGlobalAccel is the one part of Plasma the classroom +# cannot reach through KConfig's cascade: kglobalacceld opens +# kglobalshortcutsrc as a SimpleConfig, so it reads this one file in the +# teacher's home and no system directory at all. Measured — with the +# settings in /etc/ekklesia-classroom/xdg, kreadconfig6 reported the chord +# unbound while the shortcut server went on answering Ctrl+Alt+Del. +# +# So the package's copy is put there directly, before the shortcut server +# starts, exactly as the panel is. Nothing of a teacher's is lost: there +# is no settings application in this room, and KGlobalAccel puts every +# shortcut that is not named in the file back from its own defaults when +# each component registers. The entries the package does name are marked +# immutable, which is what stops it putting those back too. +SHORTCUTS=/etc/ekklesia-classroom/kglobalshortcutsrc + +if [[ -r "$SHORTCUTS" ]]; then + if cmp -s "$SHORTCUTS" "$CONFIG_HOME/kglobalshortcutsrc"; then + say "session shortcuts already the package's" + else + install -m 600 "$SHORTCUTS" "$CONFIG_HOME/kglobalshortcutsrc" + say "session shortcuts replaced with the package's" + fi +else + say "WARNING: $SHORTCUTS is missing — Ctrl+Alt+Delete may open the" + say " session screen this room is not supposed to have" +fi + # The room's power behaviour is policy, not preference: never sleep, never # dim, never lock. PowerDevil loads an idle action when it finds a group # for it, and the merged view of this file is where such a group could diff --git a/systemd/user/ekklesia-classroom-desktop.service b/systemd/user/ekklesia-classroom-desktop.service index 0bebb9a..0449b99 100644 --- a/systemd/user/ekklesia-classroom-desktop.service +++ b/systemd/user/ekklesia-classroom-desktop.service @@ -4,6 +4,12 @@ Documentation=file:///usr/local/bin/classroom-desktop-reset # Before the shell, because the shell owns the file this removes. Editing # it underneath a running plasmashell was measured to destroy the panel. Before=plasma-plasmashell.service +# And before the shortcut server, for the same reason: kglobalacceld reads +# ~/.config/kglobalshortcutsrc once, at start, and this writes it. Both +# units are wanted by plasma-workspace.target, so without this line they +# would start together and a room could come up with Ctrl+Alt+Delete still +# bound to the session screen. +Before=plasma-kglobalaccel.service PartOf=graphical-session.target [Service] diff --git a/tools/build-release.sh b/tools/build-release.sh index 85ec0f7..8295886 100755 --- a/tools/build-release.sh +++ b/tools/build-release.sh @@ -225,6 +225,18 @@ install -Dm644 \ "$ROOT/config/environment/90-ekklesia-classroom.conf" \ "$STAGE/etc/environment.d/90-ekklesia-classroom.conf" +# The session shortcuts, and deliberately NOT in the xdg directory above. +# +# kglobalacceld reads ~/.config/kglobalshortcutsrc as a SimpleConfig: no +# cascade, no system directories. Measured — with this file in the +# cascade, kreadconfig6 reported the chord unbound while the shortcut +# server went on answering Ctrl+Alt+Del. classroom-desktop-reset copies +# this into the teacher's home before the shortcut server starts, the +# same way the panel is copied out of the package at every login. +install -Dm644 \ + "$ROOT/config/plasma/kglobalshortcutsrc" \ + "$STAGE/etc/ekklesia-classroom/kglobalshortcutsrc" + # The one that actually reaches a session started by a display manager. # startplasma sources this directory; PAM does not read environment.d, so # without this file none of the settings above are in the cascade at all @@ -310,6 +322,14 @@ install -Dm644 \ "$ROOT/config/dbus/fr.emersion.mako.service" \ "$STAGE/usr/local/share/dbus-1/services/fr.emersion.mako.service" +# Shadows Plasma's session screen. Ctrl+Alt+Delete asks D-Bus to start it; +# with this in front of plasma-workspace's own file there is nothing to +# start. See the file itself for why the unbound chord is not enough on +# its own. +install -Dm644 \ + "$ROOT/config/dbus/org.kde.LogoutPrompt.service" \ + "$STAGE/usr/local/share/dbus-1/services/org.kde.LogoutPrompt.service" + # Two marks the classroom draws itself, because nothing in Breeze says # either thing: a lesson, and fetching a child's grown-up. for icon in ekklesia-wonderink ekklesia-parent-call; do @@ -384,6 +404,13 @@ install -Dm644 \ "$ROOT/config/polkit/org.ekklesia.classroom.display.policy" \ "$STAGE/usr/share/polkit-1/actions/org.ekklesia.classroom.display.policy" +# The classroom user cannot suspend or hibernate the room. A vendor rule +# in /usr/share, not /etc, so a technician can override it by dropping a +# file of the same name into /etc/polkit-1/rules.d. +install -Dm644 \ + "$ROOT/config/polkit/org.ekklesia.classroom.sleep.rules" \ + "$STAGE/usr/share/polkit-1/rules.d/49-ekklesia-classroom-sleep.rules" + install -Dm644 \ "$ROOT/config/plasma/autostart/ekklesia-classroom-scale.desktop" \ "$STAGE/etc/xdg/autostart/ekklesia-classroom-scale.desktop" diff --git a/tools/lab/converged-check b/tools/lab/converged-check index e02544f..f99eef9 100755 --- a/tools/lab/converged-check +++ b/tools/lab/converged-check @@ -48,6 +48,60 @@ notification_owner() { [[ -n "$pid" ]] && in_lab "cat /proc/$pid/comm" } +# How many keys KGlobalAccel has bound to an action, asked of the running +# shortcut server rather than read out of a file. This is the question +# Ctrl+Alt+Delete actually turns on: a file can say anything, and +# kglobalacceld is what answers a key press. +shortcut_of() { + docker exec -u classroom -e XDG_RUNTIME_DIR=/run/user/1000 -e HOME=/home/classroom \ + -e DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus "$NAME" \ + busctl --user --no-pager call org.kde.kglobalaccel /kglobalaccel \ + org.kde.KGlobalAccel shortcut as 4 "$1" "$2" "" "" 2>/dev/null +} + +# How many keys are bound. "ai 0" is an empty array: nothing answers. +shortcut_keys() { shortcut_of "$@" | awk '{print $2}'; } + +# And which key, as Qt's combined key code, for the chords that have to +# keep working. +shortcut_key() { shortcut_of "$@" | awk '{print $3}'; } + +# polkit's own verdict on a running process, which is the exact question +# a teacher's session asks when something offers to suspend the machine. +# +# allowed it would go ahead +# challenge it would ask for an administrator's password, which no +# teacher has — this is what reboot and shut down already do +# denied polkit refuses outright, which is the classroom rule +# +# Asked about a pid rather than by running pkcheck as somebody, because +# the subject polkit judges is the calling process and its session. +polkit_verdict() { + in_lab "/usr/bin/pkcheck --action-id $2 --process $1 >/dev/null 2>&1; + case \$? in 0) echo allowed ;; 1) echo denied ;; *) echo challenge ;; esac" +} + +teacher_session() { in_lab 'pgrep -x plasmashell | head -1'; } + +# What logind tells the classroom user it may do, which is what Plasma +# reads before deciding whether to draw a button. +# +# yes / challenge the operation is on offer +# no policy refuses it +# na this machine cannot do it at all +# +# A teacher must never see the first two. The lab container cannot +# hibernate whatever the policy says, so it answers "na" there and "no" on +# a real machine; both mean refused, and the polkit checks above are what +# actually prove the rule. +logind_offers() { + case "$(in_lab "runuser -u classroom -- busctl --no-pager call org.freedesktop.login1 \ + /org/freedesktop/login1 org.freedesktop.login1.Manager $1" | cut -d'"' -f2)" in + yes|challenge) echo offered ;; + *) echo refused ;; + esac +} + # logind answers for the running configuration, not for a file on disk. logind_property() { in_lab "busctl get-property org.freedesktop.login1 /org/freedesktop/login1 \ @@ -145,6 +199,64 @@ check "logind ignores a long press" ignore "$(logind_property HandlePowerKeyLong check "logind has no idle action" ignore "$(logind_property IdleAction)" check "logind ignores the lid" ignore "$(logind_property HandleLidSwitch)" +section "Ctrl+Alt+Delete, and the sleep states" +# The chord a teacher used to hibernate a room. +check "Ctrl+Alt+Delete is bound to nothing" 0 "$(shortcut_keys ksmserver 'Log Out')" +for action in 'Log Out Without Confirmation' LogOut 'Shut Down' Reboot \ + 'Reboot Without Confirmation' 'Halt Without Confirmation'; do + check "ksmserver/$action unbound" 0 "$(shortcut_keys ksmserver "$action")" +done +for action in Sleep Hibernate PowerDown PowerOff; do + check "powerdevil/$action unbound" 0 "$(shortcut_keys org_kde_powerdevil "$action")" +done +# And the room's own chords are not collateral damage. The shortcut file +# is replaced wholesale at login, so this is the check that says the +# maintenance route survived it. 503316555 is Meta+Ctrl+Alt+Shift+K. +check "the maintenance chord still answers" 503316555 \ + "$(shortcut_key ekklesia-admin-terminal.desktop _launch)" +check "the display chord still answers" 503316548 \ + "$(shortcut_key ekklesia-display-settings.desktop _launch)" +# The greeter itself, with nothing to start it. +check "the session screen cannot be activated" 1 \ + "$(in_lab 'grep -c "^Exec=/bin/false" /usr/local/share/dbus-1/services/org.kde.LogoutPrompt.service 2>/dev/null || true')" +# The policy layer, asked of the teacher's own running session. +# +# Hybrid sleep and suspend-then-hibernate have no polkit action of their +# own in systemd 257: logind authorises both through the hibernate action, +# so denying hibernate denies all three. The rule names the other two +# spellings anyway, in case a future systemd splits them out. +TEACHER="$(teacher_session)" +for action in suspend suspend-multiple-sessions suspend-ignore-inhibit \ + hibernate hibernate-multiple-sessions hibernate-ignore-inhibit; do + check "the teacher's session may not $action" denied \ + "$(polkit_verdict "$TEACHER" "org.freedesktop.login1.$action")" +done +# What the desktop is told, which is why no button is drawn for it. +check "logind offers the classroom no suspend" refused "$(logind_offers CanSuspend)" +check "logind offers the classroom no hibernate" refused "$(logind_offers CanHibernate)" +check "logind offers the classroom no hybrid sleep" refused \ + "$(logind_offers CanHybridSleep)" +check "logind offers the classroom no suspend-then-hibernate" refused \ + "$(logind_offers CanSuspendThenHibernate)" + +# AND THE MACHINE IS NOT CRIPPLED FOR THE PERSON WHO MAINTAINS IT. +# +# The rule is scoped to the classroom user by name, so every other account +# has to be able to do all of it. pid 1 stands in for a root process. +for action in suspend hibernate reboot power-off; do + check "a root process may still $action" allowed \ + "$(polkit_verdict 1 "org.freedesktop.login1.$action")" +done +# And the two the classroom user was deliberately NOT denied: ending up at +# an administrator's password prompt is the existing behaviour, and it is +# not this release's business to change it. +for action in reboot power-off; do + check "the classroom is not newly denied $action" challenge \ + "$(polkit_verdict "$TEACHER" "org.freedesktop.login1.$action")" +done +check "sleep is not masked machine-wide" 0 \ + "$(in_lab 'systemctl is-enabled sleep.target suspend.target hibernate.target 2>/dev/null | grep -c masked || true')" + section "lockdown still in force" for key in run_command shell_access logout; do check "$key" false "$(session_config --file kdeglobals --group 'KDE Action Restrictions' --key "$key")"