Spicy Media sah die Zeilen der Modis -- drei Tabellen, ein Loch
GEMESSEN, NICHT VERMUTET, und es war live: Aufgaben, Bereichs-Eintraege
und Dateien eines Modis waren fuer Spicy Media sichtbar. Die NAMEN der
Modis waren ueberall sauber verborgen -- ihre ZEILEN nicht. Eine halbe
Verborgenheit ist keine.
WARUM ES PASSIEREN KONNTE: Fuer Personen gibt es die Regel EINMAL
zentral (verborgeneIds). Fuer Zeilen gibt es sie DREIMAL -- in
workspace-aufgaben.js, workspace-bereiche.js und workspace-dateien.js --
und alle drei geben Spicy Media dasselbe: "alles ausser dem, was
DogFather gehoert" (ohneDogFather). Ein Modi-Eintrag gehoert ihm nicht,
also fiel er durch.
Manager, Scout und Creator waren nie betroffen, ihre Regeln sind enger.
Der Kalender auch nicht: termineSichtbar() gibt jedem nur Eigenes.
Beides nachgesehen, nicht angenommen.
GEFUNDEN HAT ES KEINE UEBERLEGUNG, sondern eine Pruefung, die etwas
ANLEGT und danach mit fremden Augen nachsieht. Vorher hatte ich nur
Namenslisten geprueft -- und die waren die ganze Zeit gruen. Der Anlass
war nicht einmal Misstrauen gegen diese Stelle: Ich wollte ein
Ideen-Board auf die Eintraege setzen und dabei wissen, wer sie sieht.
BEHOBEN mit ohneModi() als Gegenstueck zu ohneDogFather -- und zwar als
UMHUELLUNG um die drei Regeln, nicht als Flicken darin. Ein Flicken
haette den einen bekannten Zweig geschlossen und den naechsten
Rollenzweig wieder offen gelassen; gemerkt haette es niemand, weil an
der geaenderten Stelle nichts davon steht.
Dazu die `fuerAlle`-Ausnahme bei den Eintraegen: Sie haengt ein ODER an
und haette die Bedingung sonst wieder aufgemacht. Heute hat kein Modi
eine Kachel in einen solchen Bereich -- ein Aufruf an der Oberflaeche
vorbei braucht sie aber nicht. Eine Regel, die nur im Formular gilt,
ist keine Regel.
Nachgesehen, dass die Umhuellung nirgends das falsche Tabellenkuerzel
setzt: Alle Aufrufstellen in workspace-hinweise.js und workspace-suche.js
fuehren die Tabellen als a, d und e -- genau so, wie es dasteht.
NEBENBEI: Das Modi-Team teilt sich jetzt auch die Bereichs-Eintraege,
nicht nur die Aufgaben (Entscheidung Filipe, 09.09.2026: "sie sind
untereinander ein Team"). Ohne diesen Zweig saehe jeder Modi nur, was er
selbst geschrieben hat -- eine gemeinsame Sammlung waere keine.
ZWEI EIGENE FEHLER AUF DEM WEG DAHIN, beide festgehalten:
* Die neue Messung stand HINTER der Gegenprobe. Die macht eine Person
absichtlich zur Creatorin -- die Messung bekam 403 und meldete
"kann nichts anlegen". Gemessen wurde ein Zustand, den es im
Betrieb nicht gibt. Genau davor warnt der Kommentar, den ich selbst
zwei Tage vorher an diese Gegenprobe geschrieben hatte.
* Das "konnte nicht nachsehen" nannte KEINEN Grund. Damit ist der
dritte Ausgang nur dem Namen nach da -- man weiss danach so wenig
wie vorher. Erst mit der Fehlermeldung im Text kam ich auf die Spur.
GEPRUEFT: pruef-modi-verborgen (75, davon 18 neu ueber drei Tabellen und
fuenf Rollen), pruef-spicy (60), pruef-bereiche-lesend,
pruef-aufgabenbrett, pruef-modi-katalog (29).
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -393,13 +393,87 @@ console.log("\n=== 7. Dieselbe Seite mit einem echten Browser ===\n");
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
/* ---------- 8. Die Gegenprobe ----------------------------------------
|
||||
/* ---------- 8. Was einem Modi GEHOERT, sieht sonst niemand ----------- */
|
||||
console.log("\n=== 8. Aufgaben, Einträge und Dateien eines Modis ===\n");
|
||||
{
|
||||
/* DREI TABELLEN, DREI EIGENE SICHTBARKEITSREGELN. Fuer Personen ist
|
||||
die Regel zentral (verborgeneIds); fuer ZEILEN gibt es sie dreimal
|
||||
-- in workspace-aufgaben.js, workspace-bereiche.js und
|
||||
workspace-dateien.js. Alle drei geben Spicy Media "alles ausser dem,
|
||||
was DogFather gehoert" -- und ein Modi-Eintrag gehoert ihm nicht.
|
||||
|
||||
Der Kalender ist bewusst nicht dabei: termineSichtbar() gibt jedem
|
||||
nur Eigenes, dort kann nichts durchsickern. Nachgesehen, nicht
|
||||
angenommen. */
|
||||
const an = await anmelden("creator", "CODE-MODI-0001");
|
||||
const MERKMAL = "GEHEIMER MODI-EINTRAG";
|
||||
|
||||
/* (a) Eine Aufgabe -- ueber den normalen Weg. */
|
||||
const t1 = await fetch(BASIS + "/workspace/api/aufgaben", {
|
||||
method: "POST", headers: { "Content-Type": "application/json", cookie: an.keks },
|
||||
body: JSON.stringify({ titel: MERKMAL + " Aufgabe" }),
|
||||
});
|
||||
ok(t1.status === 201, `der Modi legt eine Aufgabe an (${t1.status})`);
|
||||
|
||||
/* (b) Ein Bereichs-Eintrag -- ebenfalls ueber den normalen Weg. */
|
||||
const t2 = await fetch(BASIS + "/workspace/api/bereich/live", {
|
||||
method: "POST", headers: { "Content-Type": "application/json", cookie: an.keks },
|
||||
body: JSON.stringify({ art: "vorbereitung", titel: MERKMAL + " Eintrag", text: "nur für das Team" }),
|
||||
});
|
||||
const t2daten = await t2.json().catch(() => ({}));
|
||||
if (t2.status !== 201 && t2.status !== 200) {
|
||||
unklar(`kein Bereichs-Eintrag moeglich (${t2.status}: ${JSON.stringify(t2daten)})`);
|
||||
} else {
|
||||
ok(true, `und einen Bereichs-Eintrag (${t2.status})`);
|
||||
}
|
||||
|
||||
/* (c) Eine Datei -- direkt in die Tabelle. Der normale Weg braeuchte
|
||||
einen echten Datei-Upload; hier geht es nicht um das Hochladen,
|
||||
sondern allein um die Frage, WER die Zeile danach sieht. */
|
||||
{
|
||||
const dd = new DatabaseSync(process.env.WORKSPACE_DB);
|
||||
dd.prepare(`INSERT INTO dateien
|
||||
(name_original, name_datei, groesse, typ, status, creator_id, hochgeladen_von, erstellt)
|
||||
VALUES (?,?,?,?,'entwurf',NULL,?,?)`)
|
||||
.run(MERKMAL + " Datei", "modi-probe.txt", 12, "text/plain", idModi1, jetzt);
|
||||
dd.close();
|
||||
ok(true, "und eine Datei liegt auf seinen Namen");
|
||||
}
|
||||
|
||||
const WEGE2 = [
|
||||
["Aufgaben", "/workspace/api/aufgaben", (x) => x.aufgaben],
|
||||
["Einträge", "/workspace/api/bereich/live", (x) => x.eintraege || x.liste],
|
||||
["Dateien", "/workspace/api/dateien", (x) => x.dateien],
|
||||
];
|
||||
for (const [name, kachel, code, darf] of [
|
||||
["DogFather", "admin", "CODE-DOGI-0001", true],
|
||||
["Spicy Media", "spicy", "CODE-SPIC-0001", false],
|
||||
["Manager", "manager", "CODE-MAN-0001", false],
|
||||
["Scout", "scout", "CODE-SCOU-0001", false],
|
||||
["Creator", "creator", "CODE-ANNA-0001", false],
|
||||
]) {
|
||||
const w = await anmelden(kachel, code);
|
||||
for (const [was, pfad, ziehen] of WEGE2) {
|
||||
const r = await hole(w.keks, pfad);
|
||||
if (r.status !== 200) { unklar(`${name.padEnd(12)} ${was.padEnd(9)} kein Zugriff (${r.status})`); continue; }
|
||||
let liste = [];
|
||||
try { liste = ziehen(r.daten) || []; } catch { liste = []; }
|
||||
const drin = liste.some((z) =>
|
||||
String(z.titel || z.name_original || z.name || "").includes(MERKMAL));
|
||||
if (darf) ok(true, `${name.padEnd(12)} ${was.padEnd(9)} sieht es (${drin ? "ja" : "nein"})`);
|
||||
else ok(!drin, `${name.padEnd(12)} ${was.padEnd(9)} `
|
||||
+ (drin ? "SIEHT ES -- LECK" : "sieht es nicht"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- 9. Die Gegenprobe ----------------------------------------
|
||||
Sie steht ABSICHTLICH GANZ UNTEN: Sie haengt eine Person
|
||||
absichtlich aus der Regel aus. Lief sie weiter oben, tauchte
|
||||
diese Person in jeder Liste DANACH auf -- und jeder spaetere
|
||||
Abschnitt maesse einen Zustand, den es im Betrieb nicht gibt.
|
||||
-------------------------------------------------------------------- */
|
||||
console.log("\n=== 8. Gegenprobe: kann diese Pruefung ueberhaupt anschlagen? ===\n");
|
||||
console.log("\n=== 9. Gegenprobe: kann diese Pruefung ueberhaupt anschlagen? ===\n");
|
||||
{
|
||||
const d2 = new DatabaseSync(process.env.WORKSPACE_DB);
|
||||
d2.prepare("UPDATE personen SET rolle = 'creator' WHERE id = ?").run(idModi1);
|
||||
@@ -423,6 +497,7 @@ console.log("\n=== 8. Gegenprobe: kann diese Pruefung ueberhaupt anschlagen? ===
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log(`\n${fehler ? "FEHLGESCHLAGEN" : "BESTANDEN"} -- `
|
||||
+ `${geprueft} geprueft, ${fehler} Fehler, ${offen} nicht nachsehbar`);
|
||||
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
db, protokolliere, echteIp, sitzungLesen, betreutWo, istLeitung, ohneDogFather, heuteLokal, ROLLEN_SORTIERUNG, betreuteIds,
|
||||
externPruefen, externSql, sichtbarePersonenIds, sichtbareCreatorIds,
|
||||
MODI_KATEGORIEN, kategorienFuer, kategoriePersonen,
|
||||
ohneModi, siehtModis,
|
||||
} from "./workspace.js";
|
||||
|
||||
export const aufgabenRouter = express.Router();
|
||||
@@ -51,9 +52,29 @@ aufgabenRouter.use("/workspace/api", angemeldet);
|
||||
|
||||
/* ---------- Sichtbarkeit ------------------------------------------------ */
|
||||
|
||||
/* =====================================================================
|
||||
DIE UMHUELLUNG: was einem Modi gehoert, faellt hier heraus.
|
||||
|
||||
Sie sitzt UM die eigentliche Regel und nicht darin -- damit sie auch
|
||||
fuer Regeln gilt, die es heute noch nicht gibt. Beim ersten Anlauf
|
||||
war nur der eine bekannte Fall geflickt (Spicy Media); das haette den
|
||||
naechsten Rollenzweig wieder offen gelassen, und niemand haette es
|
||||
gemerkt, weil an der geaenderten Stelle nichts davon steht.
|
||||
|
||||
DogFather und die Modis gehen unveraendert durch.
|
||||
===================================================================== */
|
||||
export function sichtbar(person) {
|
||||
const regel = sichtbarRoh(person);
|
||||
if (!regel || siehtModis(person)) return regel;
|
||||
return {
|
||||
wo: `(${regel.wo}) AND ${ohneModi("a", ["creator_id", "verantwortlich_id", "erstellt_von"])}`,
|
||||
werte: regel.werte,
|
||||
};
|
||||
}
|
||||
|
||||
/* Liefert WHERE-Bedingung und Werte, passend zur Rolle. An genau einer
|
||||
Stelle definiert, damit keine Abfrage sie versehentlich vergisst. */
|
||||
export function sichtbar(person) {
|
||||
function sichtbarRoh(person) {
|
||||
switch (person.rolle) {
|
||||
/* NUR DogFather sieht alles. Ausdruecklich so gewuenscht
|
||||
(01.09.2026) -- und ausdruecklich NUR er. */
|
||||
|
||||
@@ -18,6 +18,7 @@ import express from "express";
|
||||
import {
|
||||
db, protokolliere, echteIp, sitzungLesen, betreutWo, darfCreator, betreuteIds, istLeitung, istDogFather, siehtAlles, istSpicy, ohneDogFather,
|
||||
externPruefen, externSql,
|
||||
ohneModi, siehtModis,
|
||||
} from "./workspace.js";
|
||||
|
||||
export const bereicheRouter = express.Router();
|
||||
@@ -249,9 +250,32 @@ for (const weg of ["post", "patch", "delete", "put"]) {
|
||||
bereicheRouter[weg]("/workspace/api/bereich/*", nichtSchreibendFuerCreator);
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
DIE UMHUELLUNG: was einem Modi gehoert, faellt hier heraus.
|
||||
|
||||
Sie sitzt UM die eigentliche Regel und nicht darin -- damit sie auch
|
||||
fuer Regeln gilt, die es heute noch nicht gibt. Beim ersten Anlauf
|
||||
war nur der eine bekannte Fall geflickt (Spicy Media); das haette den
|
||||
naechsten Rollenzweig wieder offen gelassen, und niemand haette es
|
||||
gemerkt, weil an der geaenderten Stelle nichts davon steht.
|
||||
|
||||
DogFather und die Modis gehen unveraendert durch. Fuer alle anderen
|
||||
kommt die Bedingung dazu -- auch fuer die, deren Regel ohnehin nichts
|
||||
Fremdes trifft. Das kostet eine Unterabfrage und spart die Frage,
|
||||
ob es diesmal wirklich niemand treffen kann.
|
||||
===================================================================== */
|
||||
export function sichtbar(person) {
|
||||
const regel = sichtbarRoh(person);
|
||||
if (!regel || siehtModis(person)) return regel;
|
||||
return {
|
||||
wo: `(${regel.wo}) AND ${ohneModi("e", ["creator_id", "erstellt_von"])}`,
|
||||
werte: regel.werte,
|
||||
};
|
||||
}
|
||||
|
||||
/* Scouts haben mit der Creator-Betreuung nichts zu tun -- sie sehen hier
|
||||
nichts. Creator sehen ihren eigenen Bereich, das Management alles. */
|
||||
export function sichtbar(person) {
|
||||
function sichtbarRoh(person) {
|
||||
/* NUR DogFather sieht alles (01.09.2026). Vorher stand hier
|
||||
istLeitung() -- damit sah auch jeder Manager jeden Creator. Ein
|
||||
Manager faellt jetzt in dieselbe Regel wie ein Scout: nur die
|
||||
@@ -277,6 +301,14 @@ export function sichtbar(person) {
|
||||
}
|
||||
/* Ein Scout sieht die Bereiche der Creator, die er betreut -- dazu,
|
||||
was er selbst eingetragen hat. */
|
||||
/* DAS MODI-TEAM TEILT SICH SEINE EINTRAEGE -- wie die Aufgaben
|
||||
(Entscheidung Filipe, 09.09.2026: "sie sind untereinander ein
|
||||
Team"). Ohne diesen Zweig saehe jeder Modi nur, was er selbst
|
||||
geschrieben hat, und eine gemeinsame Ideensammlung waere keine. */
|
||||
if (person.rolle === "modi") {
|
||||
return { wo: `(e.erstellt_von IN (SELECT id FROM personen WHERE rolle = 'modi')`
|
||||
+ ` OR e.creator_id IN (SELECT id FROM personen WHERE rolle = 'modi'))`, werte: [] };
|
||||
}
|
||||
const b = betreutWo(person, "e.creator_id");
|
||||
return b
|
||||
? { wo: `(e.erstellt_von = ? OR ${b.wo})`, werte: [person.id, ...b.werte] }
|
||||
@@ -307,10 +339,22 @@ export function sichtbarEintrag(person, praefix = "e") {
|
||||
if (regel.wo === "1=1") return regel; // DogFather sieht ohnehin alles
|
||||
if (!BEREICHE_FUER_ALLE.length) return regel;
|
||||
const liste = BEREICHE_FUER_ALLE.map(() => "?").join(", ");
|
||||
return {
|
||||
const mitAllen = {
|
||||
wo: `(${praefix}.bereich IN (${liste}) OR ${regel.wo})`,
|
||||
werte: [...BEREICHE_FUER_ALLE, ...regel.werte],
|
||||
};
|
||||
/* UND DANACH NOCH EINMAL ZU (10.09.2026).
|
||||
|
||||
sichtbar() haengt die Modi-Bedingung bereits an -- aber das ODER
|
||||
hier oeffnet sie wieder: Ein Eintrag in einem `fuerAlle`-Bereich
|
||||
kaeme durch, egal wem er gehoert. Heute hat kein Modi eine Kachel
|
||||
dorthin; ein Aufruf an der Oberflaeche vorbei braucht sie aber
|
||||
nicht. Eine Regel, die nur im Formular gilt, ist keine Regel. */
|
||||
if (siehtModis(person)) return mitAllen;
|
||||
return {
|
||||
wo: `(${mitAllen.wo}) AND ${ohneModi(praefix)}`,
|
||||
werte: mitAllen.werte,
|
||||
};
|
||||
}
|
||||
|
||||
const SPALTEN = `
|
||||
|
||||
@@ -31,6 +31,7 @@ import { mkdirSync, createReadStream, writeFileSync, unlinkSync, statSync } from
|
||||
import {
|
||||
db, protokolliere, echteIp, sitzungLesen, DATEN_ORDNER, betreutWo, darfCreator, betreuteIds, istLeitung, istDogFather, siehtAlles, istSpicy, ohneDogFather, ROLLEN_SORTIERUNG,
|
||||
externSql, EXTERN_MAX, sichtbarePersonenIds,
|
||||
ohneModi, siehtModis,
|
||||
} from "./workspace.js";
|
||||
|
||||
export const dateienRouter = express.Router();
|
||||
@@ -66,9 +67,29 @@ function gleicheHerkunft(req, res, next) {
|
||||
|
||||
dateienRouter.use("/workspace/api/dateien", angemeldet);
|
||||
|
||||
/* =====================================================================
|
||||
DIE UMHUELLUNG: was einem Modi gehoert, faellt hier heraus.
|
||||
|
||||
Sie sitzt UM die eigentliche Regel und nicht darin -- damit sie auch
|
||||
fuer Regeln gilt, die es heute noch nicht gibt. Beim ersten Anlauf
|
||||
war nur der eine bekannte Fall geflickt (Spicy Media); das haette den
|
||||
naechsten Rollenzweig wieder offen gelassen, und niemand haette es
|
||||
gemerkt, weil an der geaenderten Stelle nichts davon steht.
|
||||
|
||||
DogFather und die Modis gehen unveraendert durch.
|
||||
===================================================================== */
|
||||
export function sichtbar(person) {
|
||||
const regel = sichtbarRoh(person);
|
||||
if (!regel || siehtModis(person)) return regel;
|
||||
return {
|
||||
wo: `(${regel.wo}) AND ${ohneModi("d", ["creator_id", "hochgeladen_von"])}`,
|
||||
werte: regel.werte,
|
||||
};
|
||||
}
|
||||
|
||||
/* Sichtbarkeit wie überall: an einer Stelle, damit keine Abfrage sie
|
||||
vergisst. */
|
||||
export function sichtbar(person) {
|
||||
function sichtbarRoh(person) {
|
||||
/* NUR DogFather sieht alles (01.09.2026). Vorher stand hier
|
||||
istLeitung() -- damit sah auch jeder Manager jeden Creator. Ein
|
||||
Manager faellt jetzt in dieselbe Regel wie ein Scout: nur die
|
||||
|
||||
@@ -165,6 +165,51 @@ export function ohneDogFather(praefix, spalten = ["creator_id", "erstellt_von"])
|
||||
+ `(SELECT id FROM personen WHERE rolle = 'admin'))`)
|
||||
.join(" AND ");
|
||||
}
|
||||
/* =====================================================================
|
||||
WAS EINEM MODI GEHOERT, SIEHT SONST NIEMAND (10.09.2026)
|
||||
|
||||
Das Gegenstueck zu ohneDogFather() -- und es entstand aus einem
|
||||
gemessenen Leck, nicht aus einer Ueberlegung.
|
||||
|
||||
WAS PASSIERT WAR: Fuer PERSONEN ist die Regel zentral
|
||||
(verborgeneIds). Fuer ZEILEN gibt es sie dreimal -- in
|
||||
workspace-aufgaben.js, workspace-bereiche.js und
|
||||
workspace-dateien.js -- und alle drei geben Spicy Media dasselbe:
|
||||
"alles ausser dem, was DogFather gehoert". Ein Modi-Eintrag gehoert
|
||||
ihm aber nicht. Also sah Spicy Media die Aufgaben, die Eintraege und
|
||||
die Dateien der Modis, waehrend die Modis selbst in jeder Namensliste
|
||||
sauber verborgen waren. Die halbe Verborgenheit ist keine.
|
||||
|
||||
Aufgefallen ist es nicht beim Lesen des Codes, sondern durch eine
|
||||
Pruefung, die etwas ANLEGT und danach mit fremden Augen nachsieht.
|
||||
Manager, Scout und Creator waren nie betroffen -- ihre Regeln sind
|
||||
ohnehin enger. Der Kalender auch nicht: termineSichtbar() gibt jedem
|
||||
nur Eigenes. Nachgemessen, nicht angenommen.
|
||||
|
||||
SPALTEN MUESSEN BEIDE RICHTUNGEN ABDECKEN. `creator_id` sagt, UM WEN
|
||||
es geht; `erstellt_von`/`hochgeladen_von`/`verantwortlich_id` sagen,
|
||||
an WEM es haengt. Eine Modi-Aufgabe hat gar keine creator_id -- sie
|
||||
haengt allein an verantwortlich_id. Wer nur eine Spalte prueft, hat
|
||||
nichts geprueft.
|
||||
|
||||
`IS NULL OR NOT IN` und nicht bloss `NOT IN`: In SQL ist
|
||||
`NULL NOT IN (...)` weder wahr noch falsch, sondern NULL -- die Zeile
|
||||
fiele stillschweigend heraus. Genau so verschwinden Daten, ohne dass
|
||||
jemand einen Fehler sieht. (Dieselbe Falle steht schon im Kommentar
|
||||
von ohneDogFather; sie ist es wert, zweimal dazustehen.)
|
||||
===================================================================== */
|
||||
export function ohneModi(praefix, spalten = ["creator_id", "erstellt_von"]) {
|
||||
return spalten
|
||||
.map((sp) => `(${praefix}.${sp} IS NULL OR ${praefix}.${sp} NOT IN `
|
||||
+ `(SELECT id FROM personen WHERE rolle = 'modi'))`)
|
||||
.join(" AND ");
|
||||
}
|
||||
|
||||
/** Darf diese Person ueberhaupt etwas sehen, das einem Modi gehoert?
|
||||
* Nur die DogFather-Rolle und die Modis selbst. */
|
||||
export const siehtModis = (person) => !!person
|
||||
&& (person.rolle === "admin" || person.rolle === "modi");
|
||||
|
||||
export const siehtAlles = (person) => !!person
|
||||
&& (person.rolle === "admin" || person.rolle === "spicy");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user