feat(selbstauskunft): Mitarbeiter sehen ihre eigenen Geraete (#55)
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s

„Was ist eigentlich mit meinem Notebook passiert?“ musste bisher ein
Techniker nachsehen. Ein Mitarbeiter kann die Arbeiten an **seinen**
Geraeten jetzt selbst ansehen — mit der fuenften Rolle MITARBEITER,
zugeordnet beim Anmelden ueber den UPN.

Was er sieht, sind Titel, Zeitraum und Status. Keine Schritte, keine
Notizen, keine Kommentare, keine Anhaenge: in einem Lauf stehen
Arbeitsnotizen ueber Menschen und Befehle mit Platzhaltern fuer
Zugangsdaten. Eine Selbstauskunft ist keine Akteneinsicht, und das haelt
nicht die Seite fest, sondern das Lesemodell — eine Seite, die filtert,
ist eine Seite, die es vergessen kann.

`selbstauskunft` ist ein **Ersatz** fuer `lesen` und kein Zusatz; auch
ein ADMIN hat es nicht. Ein zweiter Weg zu denselben Daten waere eine
zweite Stelle, an der jemand die Einschraenkung auf die eigene Person
vergisst. Die Datenbank verlangt das sogar: ein CHECK haelt fest, dass
Rolle und Zuordnung zusammengehoeren, in beide Richtungen.

Ein mehrdeutiger UPN wird abgewiesen und protokolliert. Zwei Kunden
koennen dieselbe Person fuehren; ein findFirst entschiede nach
Sortierung darueber, wessen Geraete jemand sieht.

**Verhaltensaenderung:** wer sich anmeldet und in keiner Rollengruppe
steht, bekommt nicht mehr automatisch LESER — also nicht mehr
Lesezugriff auf alle Laeufe des Hauses. Das greift nur mit
konfigurierten OIDC_GROUP_*; ohne sie bleibt alles wie vorher, denn ohne
abgebildete Gruppen laesst sich Techniker nicht von Mitarbeiter
unterscheiden.

Nebenbei behoben: `staerksteRolle` benutzte LESER als Startwert und
damit als Untergrenze — mit einer Rolle darunter lieferte sie mehr
Rechte als uebergeben. Gefunden von einem Test, der seit #007
unveraendert dasteht.

Closes #55

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-10-06 07:32:54 +02:00
co-authored by Claude Opus 5
parent a417426f43
commit 0158d0292f
28 changed files with 1806 additions and 76 deletions
+48 -15
View File
@@ -11,13 +11,20 @@ const ALLE_RECHTE = Object.keys(RECHTE) as Recht[];
* Test, der die Tabelle nur gegen sich selbst haelt, wuerde jede Aenderung mitmachen.
*/
const MATRIX: Record<Recht, Record<Rolle, boolean>> = {
lesen: { ADMIN: true, REDAKTEUR: true, TECHNIKER: true, LESER: true },
laeufeBearbeiten: { ADMIN: true, REDAKTEUR: true, TECHNIKER: true, LESER: false },
geraeteBearbeiten: { ADMIN: true, REDAKTEUR: true, TECHNIKER: true, LESER: false },
mitarbeiterBearbeiten: { ADMIN: true, REDAKTEUR: true, TECHNIKER: true, LESER: false },
vorlagenBearbeiten: { ADMIN: true, REDAKTEUR: true, TECHNIKER: false, LESER: false },
archivVerwalten: { ADMIN: true, REDAKTEUR: false, TECHNIKER: false, LESER: false },
verwaltung: { ADMIN: true, REDAKTEUR: false, TECHNIKER: false, LESER: false },
lesen: { ADMIN: true, REDAKTEUR: true, TECHNIKER: true, LESER: true, MITARBEITER: false },
selbstauskunft: { ADMIN: false, REDAKTEUR: false, TECHNIKER: false, LESER: false, MITARBEITER: true },
laeufeBearbeiten: { ADMIN: true, REDAKTEUR: true, TECHNIKER: true, LESER: false, MITARBEITER: false },
geraeteBearbeiten: { ADMIN: true, REDAKTEUR: true, TECHNIKER: true, LESER: false, MITARBEITER: false },
mitarbeiterBearbeiten: {
ADMIN: true,
REDAKTEUR: true,
TECHNIKER: true,
LESER: false,
MITARBEITER: false,
},
vorlagenBearbeiten: { ADMIN: true, REDAKTEUR: true, TECHNIKER: false, LESER: false, MITARBEITER: false },
archivVerwalten: { ADMIN: true, REDAKTEUR: false, TECHNIKER: false, LESER: false, MITARBEITER: false },
verwaltung: { ADMIN: true, REDAKTEUR: false, TECHNIKER: false, LESER: false, MITARBEITER: false },
};
describe("Rechtematrix", () => {
@@ -26,10 +33,12 @@ describe("Rechtematrix", () => {
);
it("prueft jede Rolle gegen jedes Recht", () => {
// Haelt fest, dass die Tabelle unten wirklich vollstaendig ist: vier Rollen, sieben Rechte.
expect(ALLE_ROLLEN).toHaveLength(4);
expect(ALLE_RECHTE).toHaveLength(7);
expect(faelle).toHaveLength(28);
// Haelt fest, dass die Tabelle oben wirklich vollstaendig ist: fuenf Rollen,
// acht Rechte. Die Zahlen stehen hier ausgeschrieben, damit eine neue Rolle
// oder ein neues Recht **hier** auffaellt und nicht erst im Betrieb (#035).
expect(ALLE_ROLLEN).toHaveLength(5);
expect(ALLE_RECHTE).toHaveLength(8);
expect(faelle).toHaveLength(40);
});
it.each(faelle)("%s: %s -> %s", (recht, rolle, erwartet) => {
@@ -62,10 +71,27 @@ describe("Rollen im Einzelnen", () => {
expect(darf("REDAKTEUR", "archivVerwalten")).toBe(false);
});
it("gibt ADMIN alles", () => {
for (const recht of ALLE_RECHTE) {
it("gibt ADMIN alles – ausser der Selbstauskunft", () => {
for (const recht of ALLE_RECHTE.filter((r) => r !== "selbstauskunft")) {
expect(darf("ADMIN", recht), `ADMIN darf ${recht}`).toBe(true);
}
/*
* Die eine Ausnahme, und sie ist Absicht (#035): `selbstauskunft` ist kein
* zusaetzliches Recht, sondern ein **Ersatz** fuer `lesen`. Ein ADMIN sieht
* ueber `lesen` ohnehin alles; ihm die Selbstauskunft zu geben waere ein
* zweiter Weg zu denselben Daten — und damit eine zweite Stelle, an der
* jemand die Einschraenkung auf die eigene Person vergessen kann.
*/
expect(darf("ADMIN", "selbstauskunft")).toBe(false);
});
it("laesst MITARBEITER genau eines und sonst nichts", () => {
expect(darf("MITARBEITER", "selbstauskunft")).toBe(true);
for (const recht of ALLE_RECHTE.filter((r) => r !== "selbstauskunft")) {
expect(darf("MITARBEITER", recht), `MITARBEITER darf ${recht} nicht`).toBe(false);
}
// Vor allem nicht `lesen`: das waere die Akteneinsicht, die es nicht sein soll.
expect(darf("MITARBEITER", "lesen")).toBe(false);
});
it("behaelt das Zurueckholen aus dem Archiv beim ADMIN", () => {
@@ -103,14 +129,21 @@ describe("Aufbau der Tabelle", () => {
expect(Object.keys(ROLLEN_RANG).sort()).toStrictEqual([...ALLE_ROLLEN].sort());
});
it("gibt jeder Rolle einen eigenen Rang, ADMIN oben und LESER unten", () => {
it("gibt jeder Rolle einen eigenen Rang, ADMIN oben und MITARBEITER unten", () => {
const raenge = ALLE_ROLLEN.map((rolle) => ROLLEN_RANG[rolle]);
expect(new Set(raenge).size).toBe(ALLE_ROLLEN.length);
expect(Math.max(...raenge)).toBe(ROLLEN_RANG.ADMIN);
expect(Math.min(...raenge)).toBe(ROLLEN_RANG.LESER);
expect(Math.min(...raenge)).toBe(ROLLEN_RANG.MITARBEITER);
expect(ROLLEN_RANG.ADMIN).toBeGreaterThan(ROLLEN_RANG.REDAKTEUR);
expect(ROLLEN_RANG.REDAKTEUR).toBeGreaterThan(ROLLEN_RANG.TECHNIKER);
expect(ROLLEN_RANG.TECHNIKER).toBeGreaterThan(ROLLEN_RANG.LESER);
/*
* **Unter LESER.** Der Rang entscheidet in `staerksteRolle`, wer gewinnt,
* wenn jemand in mehreren abgebildeten Gruppen steht. Stuende MITARBEITER
* darueber, verdraengte eine Selbstauskunft-Gruppe die echte Rolle — und ein
* Techniker verlöre seine Rechte, weil er auch Mitarbeiter des Hauses ist.
*/
expect(ROLLEN_RANG.LESER).toBeGreaterThan(ROLLEN_RANG.MITARBEITER);
});
});