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:
co-authored by
Claude Opus 5
parent
8db5e8dd9f
commit
23dff0b7b4
@@ -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
|
||||
@@ -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;
|
||||
});
|
||||
Reference in New Issue
Block a user