feat(selbstauskunft): Mitarbeiter sehen ihre eigenen Geraete (#55)
„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:
co-authored by
Claude Opus 5
parent
a417426f43
commit
0158d0292f
@@ -0,0 +1,86 @@
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- HANDGESCHRIEBEN. Prisma erzeugt hier zusaetzlich Loeschbefehle fuer die
|
||||
-- zusammengesetzten Fremdschluessel (Mandantengrenze), die Trigramm-Indizes
|
||||
-- (Archivsuche), die partiellen eindeutigen Indizes und alle Trigger: es kennt
|
||||
-- sie nicht, weil sie sich im Schema nicht ausdruecken lassen. Diese
|
||||
-- Loeschbefehle sind bewusst gestrichen.
|
||||
--
|
||||
-- tests/datenbankregeln.test.ts prueft, dass alle diese Objekte vorhanden sind –
|
||||
-- ein versehentlich stehengelassener Loeschbefehl faellt dort sofort auf.
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- Mitarbeiter-Selbstauskunft (#035).
|
||||
|
||||
/*
|
||||
* Die fuenfte Rolle.
|
||||
*
|
||||
* `ALTER TYPE ... ADD VALUE` laeuft in PostgreSQL seit 12 auch innerhalb einer
|
||||
* Transaktion — Prisma fuehrt jede Migration in einer. Der neue Wert kommt
|
||||
* **ans Ende**: die Reihenfolge eines Enums ist seine Sortierreihenfolge, und
|
||||
* ein `MITARBEITER` zwischen `ADMIN` und `REDAKTEUR` wuerde jede Liste, die nach
|
||||
* Rolle sortiert, still umstellen.
|
||||
*/
|
||||
ALTER TYPE "Rolle" ADD VALUE 'MITARBEITER';
|
||||
|
||||
ALTER TABLE "Benutzer" ADD COLUMN "mitarbeiterId" TEXT;
|
||||
|
||||
/*
|
||||
* **Eindeutig**: eine Person hat hoechstens ein Konto, mit dem sie sich selbst
|
||||
* ansieht. Ohne das koennten zwei Anmeldungen auf dieselbe Person zeigen, und
|
||||
* ein entzogener Zugang waere nicht entzogen, solange das zweite Konto bleibt.
|
||||
*
|
||||
* `NULL` gilt in Postgres als verschieden, die vielen Konten ohne Zuordnung
|
||||
* stoeren hier also nicht.
|
||||
*/
|
||||
CREATE UNIQUE INDEX "Benutzer_mitarbeiterId_key" ON "Benutzer"("mitarbeiterId");
|
||||
|
||||
ALTER TABLE "Benutzer"
|
||||
ADD CONSTRAINT "Benutzer_mitarbeiterId_fkey" FOREIGN KEY ("mitarbeiterId")
|
||||
REFERENCES "Mitarbeiter"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
/*
|
||||
* **Die eigentliche Zusage dieses Meilensteins, in der Datenbank.**
|
||||
*
|
||||
* Die Rolle `MITARBEITER` und die Zuordnung gehoeren zusammen, in beide
|
||||
* Richtungen:
|
||||
*
|
||||
* - Ein `MITARBEITER` **ohne** `mitarbeiterId` haette ein Recht und niemanden,
|
||||
* auf den es sich bezieht. Die Lesemodelle schraenken auf diese Kennung ein;
|
||||
* fehlte sie, waere die Einschraenkung leer — und „leer“ heisst in SQL je nach
|
||||
* Formulierung „nichts“ oder „alles“. Das ist genau die Art Zweideutigkeit,
|
||||
* die hier nicht entstehen darf.
|
||||
* - Eine `mitarbeiterId` an einem `ADMIN` waere die Umkehrung: ein Konto, das
|
||||
* alles sieht und zusaetzlich als eine bestimmte Person gilt. Wer das braucht,
|
||||
* braucht zwei Konten.
|
||||
*
|
||||
* `SET NULL` am Fremdschluessel oben wuerde diese Bedingung verletzen, wenn ein
|
||||
* Mitarbeiter geloescht wird — deshalb der Trigger darunter, der die Rolle
|
||||
* mitzieht. Geloescht wird ein Mitarbeiter ohnehin nie (CLAUDE.md: nur
|
||||
* `aktiv = false`), aber eine Regel, die sich auf „kommt nicht vor“ verlaesst,
|
||||
* ist keine Regel.
|
||||
*/
|
||||
ALTER TABLE "Benutzer"
|
||||
ADD CONSTRAINT "Benutzer_mitarbeiter_nur_bei_rolle"
|
||||
CHECK (("rolle" = 'MITARBEITER') = ("mitarbeiterId" IS NOT NULL));
|
||||
|
||||
/*
|
||||
* Verliert ein Konto seine Person, verliert es auch die Rolle.
|
||||
*
|
||||
* Ohne das schlaegt das `ON DELETE SET NULL` des Fremdschluessels am CHECK
|
||||
* darueber fehl, und das Loeschen eines Mitarbeiters waere unmoeglich statt
|
||||
* folgerichtig. `LESER` und nicht `aktiv = false`: das Konto soll sich weiter
|
||||
* anmelden koennen, nur nichts Fremdes sehen — und wer sich anmeldet, bekommt
|
||||
* bei der naechsten OIDC-Anmeldung ohnehin eine neue Rolle zugewiesen.
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION laufzettel_konto_ohne_person() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
UPDATE "Benutzer"
|
||||
SET "rolle" = 'LESER', "mitarbeiterId" = NULL
|
||||
WHERE "mitarbeiterId" = OLD."id";
|
||||
RETURN OLD;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER "Mitarbeiter_konto_mitziehen"
|
||||
BEFORE DELETE ON "Mitarbeiter"
|
||||
FOR EACH ROW EXECUTE FUNCTION laufzettel_konto_ohne_person();
|
||||
@@ -31,6 +31,12 @@ enum Rolle {
|
||||
REDAKTEUR
|
||||
TECHNIKER
|
||||
LESER
|
||||
/// Selbstauskunft (#035): sieht die Laeufe zu **seinen** Geraeten und zu
|
||||
/// seiner Person, und nichts sonst. Keine Schritte, keine Notizen, keine
|
||||
/// Kommentare, keine Anhaenge — eine Selbstauskunft ist keine Akteneinsicht.
|
||||
/// Haengt an `Benutzer.mitarbeiterId`; ohne Zuordnung gibt es die Rolle nicht
|
||||
/// (CHECK `Benutzer_mitarbeiter_nur_bei_rolle`).
|
||||
MITARBEITER
|
||||
}
|
||||
|
||||
enum Betriebsmodus {
|
||||
@@ -254,6 +260,15 @@ model Benutzer {
|
||||
/// Ein Konto ohne Mailadresse gibt es nicht — `email` ist Pflicht —, aber ein
|
||||
/// Dienstkonto will keine Post.
|
||||
meldungenAktiv Boolean @default(true)
|
||||
/// Die Person im Verzeichnis, als die dieses Konto sich selbst sehen darf
|
||||
/// (#035). Gesetzt **genau dann**, wenn die Rolle `MITARBEITER` ist.
|
||||
///
|
||||
/// Hier steht **kein** zusammengesetzter Fremdschluessel, anders als sonst bei
|
||||
/// jedem kundenbezogenen Verweis: ein `Benutzer` traegt keine `kundeId`, gegen
|
||||
/// die man zusammensetzen koennte — ein Administrator sieht alle Kunden. Die
|
||||
/// Zusage „sieht nur seine eigenen Laeufe“ haengt an dieser einen Kennung, und
|
||||
/// staerker als ein Fremdschluessel darauf wird sie nicht.
|
||||
mitarbeiterId String? @unique
|
||||
erstelltAm DateTime @default(now())
|
||||
geaendertAm DateTime @default(now()) @updatedAt
|
||||
|
||||
@@ -268,6 +283,7 @@ model Benutzer {
|
||||
veroeffentlichteVersionen VorlagenVersion[] @relation("VersionVeroeffentlichtVon")
|
||||
anhaenge Anhang[]
|
||||
zustellungen Zustellung[]
|
||||
mitarbeiter Mitarbeiter? @relation(fields: [mitarbeiterId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([aktiv])
|
||||
@@index([rolle])
|
||||
@@ -376,6 +392,9 @@ model Mitarbeiter {
|
||||
untergebene Mitarbeiter[] @relation("Vorgesetzter")
|
||||
geraete Geraet[]
|
||||
laeufe Lauf[]
|
||||
/// Das Konto, mit dem diese Person sich selbst ansehen darf (#035).
|
||||
/// Hoechstens eines — `Benutzer.mitarbeiterId` ist eindeutig.
|
||||
konto Benutzer?
|
||||
|
||||
/// Postgres behandelt NULL als verschieden, deshalb stoeren mehrere manuell
|
||||
/// angelegte Mitarbeiter ohne Quelle und externe Id hier nicht.
|
||||
|
||||
Reference in New Issue
Block a user