feat: close the chord that let a teacher hibernate the room

Ctrl+Alt+Delete opened Plasma's session screen and a teacher hibernated a
classroom computer from it. A hibernated room cannot be recovered by
anybody standing in front of it, which on a Sunday morning is everybody.

logout=false was already set and did not stop it. That restriction
governs Plasma's own actions — what plasmashell draws in a menu or an
applet. The chord is a KGlobalAccel shortcut registered by ksmserver and
it asks D-Bus for the greeter without consulting a Plasma action at all.
A restriction on a menu cannot see a key.

Three layers, because each one alone has a way past it.

The key. kglobalshortcutsrc unbinds every ksmserver session action and
PowerDevil's Sleep, Hibernate, PowerDown and PowerOff, each entry marked
immutable. It is deliberately not in the xdg directory with the other
settings: kglobalacceld opens that file as a SimpleConfig, so it reads
one file in the teacher's home and no system directory at all. Measured —
with it in the cascade, kreadconfig6 reported the chord unbound while the
shortcut server went on answering Ctrl+Alt+Del. classroom-desktop-reset
copies the package's copy into place before the shortcut server starts,
the same way the panel is copied out of the package at every login.

The policy. A polkit rule refuses login1.suspend* and .hibernate* for the
classroom user and for nobody else. This is the layer that matters:
a key can be unbound and anything reaching logind can still ask.
Refusing here rather than masking sleep.target keeps a technician's own
account working, and is also what tells Plasma to stop drawing the
button — CanSuspend and CanHibernate come back no. Hybrid sleep and
suspend-then-hibernate have no action of their own in systemd 257; both
are authorised through hibernate, so denying it denies all three.

The greeter. Its D-Bus activation is shadowed with /bin/false, the same
mechanism already used for the old notification daemon. Layer one removes
the caller, this removes the callee.

Measured in the lab, from the package, after a reboot: the escape
reproduced first with the shadow removed — the greeter really does start
— and then refused, exited 1, with it back. The chord reports nothing
bound. systemctl suspend as the classroom user is denied. The maintenance
chord and the display chord both still answer, by key code, after the
shortcut file is replaced wholesale. A root process may still suspend,
hibernate, reboot and power off. 182 checks pass across a logout and back.

Screen locking stays disabled, the power button still does nothing, and
reboot and shut down are not newly denied to anybody.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
ekklesia
2026-09-19 20:50:36 -05:00
co-authored by Claude Opus 5
parent 8db5e8dd9f
commit 23dff0b7b4
8 changed files with 467 additions and 0 deletions
+28
View File
@@ -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
+86
View File
@@ -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
@@ -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;
});
+118
View File
@@ -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.
+31
View File
@@ -123,6 +123,37 @@ if [[ -f "$CONFIG_HOME/kdeglobals" ]]; then
esac esac
fi 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 # 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 # 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 # for it, and the merged view of this file is where such a group could
@@ -4,6 +4,12 @@ Documentation=file:///usr/local/bin/classroom-desktop-reset
# Before the shell, because the shell owns the file this removes. Editing # Before the shell, because the shell owns the file this removes. Editing
# it underneath a running plasmashell was measured to destroy the panel. # it underneath a running plasmashell was measured to destroy the panel.
Before=plasma-plasmashell.service 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 PartOf=graphical-session.target
[Service] [Service]
+27
View File
@@ -225,6 +225,18 @@ install -Dm644 \
"$ROOT/config/environment/90-ekklesia-classroom.conf" \ "$ROOT/config/environment/90-ekklesia-classroom.conf" \
"$STAGE/etc/environment.d/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. # The one that actually reaches a session started by a display manager.
# startplasma sources this directory; PAM does not read environment.d, so # 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 # 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" \ "$ROOT/config/dbus/fr.emersion.mako.service" \
"$STAGE/usr/local/share/dbus-1/services/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 # Two marks the classroom draws itself, because nothing in Breeze says
# either thing: a lesson, and fetching a child's grown-up. # either thing: a lesson, and fetching a child's grown-up.
for icon in ekklesia-wonderink ekklesia-parent-call; do for icon in ekklesia-wonderink ekklesia-parent-call; do
@@ -384,6 +404,13 @@ install -Dm644 \
"$ROOT/config/polkit/org.ekklesia.classroom.display.policy" \ "$ROOT/config/polkit/org.ekklesia.classroom.display.policy" \
"$STAGE/usr/share/polkit-1/actions/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 \ install -Dm644 \
"$ROOT/config/plasma/autostart/ekklesia-classroom-scale.desktop" \ "$ROOT/config/plasma/autostart/ekklesia-classroom-scale.desktop" \
"$STAGE/etc/xdg/autostart/ekklesia-classroom-scale.desktop" "$STAGE/etc/xdg/autostart/ekklesia-classroom-scale.desktop"
+112
View File
@@ -48,6 +48,60 @@ notification_owner() {
[[ -n "$pid" ]] && in_lab "cat /proc/$pid/comm" [[ -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 answers for the running configuration, not for a file on disk.
logind_property() { logind_property() {
in_lab "busctl get-property org.freedesktop.login1 /org/freedesktop/login1 \ 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 has no idle action" ignore "$(logind_property IdleAction)"
check "logind ignores the lid" ignore "$(logind_property HandleLidSwitch)" 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" section "lockdown still in force"
for key in run_command shell_access logout; do for key in run_command shell_access logout; do
check "$key" false "$(session_config --file kdeglobals --group 'KDE Action Restrictions' --key "$key")" check "$key" false "$(session_config --file kdeglobals --group 'KDE Action Restrictions' --key "$key")"