more admin stuff

This commit is contained in:
2026-08-20 14:49:19 +02:00
parent 91758a2701
commit 0e67092ba0
14 changed files with 3030 additions and 247 deletions

View File

@ -0,0 +1,65 @@
-- Mandantenfaehigkeit (volle Isolation, siehe Konzept-Erweiterung dieser
-- Session): jede Hostgruppe, Benutzergruppe, jeder SSH-Key und jedes
-- API-Token gehoert genau einem Mandanten. Ein Benutzer kann Mandanten-Admin
-- fuer einen oder mehrere Mandanten sein (tenant_admins) -- er sieht/
-- verwaltet dann AUSSCHLIESSLICH Ressourcen seines/seiner Mandanten
-- (Durchsetzung zentral in app/tenancy.py + app/auth/deps.py, angewendet in
-- jedem Endpunkt in app/admin/routes.py). Das bestehende users.is_admin
-- bleibt unveraendert bestehen und wird zum "Super-Admin": sieht/verwaltet
-- weiterhin ALLE Mandanten uebergreifend (volle Abwaertskompatibilitaet zu
-- allen bisherigen Admin-Workflows dieser App).
--
-- Rueckwirkende Kompatibilitaet: ein Standard-Mandant (id=1) wird automatisch
-- angelegt und alle VOR dieser Migration bestehenden Hostgruppen/
-- Benutzergruppen/SSH-Keys/API-Tokens werden ihm zugeordnet -- fuer
-- Super-Admins aendert sich dadurch am sichtbaren Verhalten nichts.
CREATE TABLE IF NOT EXISTS tenants (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
description TEXT,
is_active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
);
INSERT OR IGNORE INTO tenants (id, name, description) VALUES
(1, 'Standard', 'Automatisch angelegter Standard-Mandant (Daten von vor der Einfuehrung der Mandantenfaehigkeit).');
-- Mandanten-Admin-Zuweisung ist bewusst eine eigene Tabelle (statt eines
-- einzelnen users.tenant_id) -- ein User kann Mandanten-Admin fuer MEHRERE
-- Mandanten sein (z.B. externer Dienstleister fuer mehrere Kunden), ohne
-- gleich globaler Super-Admin zu sein. Nur ein Super-Admin darf diese
-- Zuweisung vornehmen (require_global_admin, siehe admin/routes.py).
CREATE TABLE IF NOT EXISTS tenant_admins (
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
tenant_id INTEGER NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
granted_by INTEGER REFERENCES users(id),
granted_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
PRIMARY KEY (user_id, tenant_id)
);
CREATE INDEX IF NOT EXISTS idx_tenant_admins_user ON tenant_admins(user_id);
ALTER TABLE host_groups ADD COLUMN tenant_id INTEGER NOT NULL DEFAULT 1 REFERENCES tenants(id);
ALTER TABLE user_groups ADD COLUMN tenant_id INTEGER NOT NULL DEFAULT 1 REFERENCES tenants(id);
ALTER TABLE ssh_keys ADD COLUMN tenant_id INTEGER NOT NULL DEFAULT 1 REFERENCES tenants(id);
-- api_tokens: bewusst NICHT nullable -- jedes Token (auch von einem
-- Super-Admin erzeugte) ist genau einem Mandanten zugeordnet. Ein Token ist
-- (wie schon vor dieser Migration) nie is_admin=True; ohne verpflichtenden
-- Mandantenbezug waere unklar, welche Mandanten-Ressourcen es sehen darf --
-- lieber explizit pro Mandant ein Token ausstellen als eine mehrdeutige
-- "gilt ueberall"-Sonderregel einzufuehren (Prinzip: fail closed).
ALTER TABLE api_tokens ADD COLUMN tenant_id INTEGER NOT NULL DEFAULT 1 REFERENCES tenants(id);
-- users.home_tenant_id: rein informativ/UX -- der Mandant, "fuer" den ein
-- Benutzer angelegt wurde (automatisch gesetzt, wenn ein Mandanten-Admin den
-- User anlegt), damit ein frisch angelegter User sofort in der Mandanten-
-- Admin-Sicht auftaucht, auch bevor er einer Gruppe zugeordnet oder ihm eine
-- Rolle gewaehrt wurde. Erzwingt KEINE Zugriffsbeschraenkung selbst -- die
-- eigentliche Sichtbarkeit/Isolation ergibt sich weiterhin aus Rollen-
-- Vergabe auf Hostgruppen eines Mandanten (siehe app/tenancy.py).
ALTER TABLE users ADD COLUMN home_tenant_id INTEGER REFERENCES tenants(id);
CREATE INDEX IF NOT EXISTS idx_host_groups_tenant ON host_groups(tenant_id);
CREATE INDEX IF NOT EXISTS idx_user_groups_tenant ON user_groups(tenant_id);
CREATE INDEX IF NOT EXISTS idx_ssh_keys_tenant ON ssh_keys(tenant_id);
CREATE INDEX IF NOT EXISTS idx_api_tokens_tenant ON api_tokens(tenant_id);
CREATE INDEX IF NOT EXISTS idx_users_home_tenant ON users(home_tenant_id);

View File

@ -0,0 +1,14 @@
-- Ergaenzung fuer vollstaendige Loesch-/Editierbarkeit (siehe app/admin/routes.py):
--
-- users.deleted_at: ein Benutzerkonto kann nicht immer per hartem SQL-DELETE
-- entfernt werden, weil audit_log.user_id (bewusst, siehe 0001_initial.sql)
-- OHNE ON DELETE CASCADE auf users(id) verweist -- das manipulationssichere
-- Audit-Log darf durch das Loeschen eines Kontos nicht nachtraeglich
-- veraendert oder seiner Zuordnung beraubt werden (jede Aenderung an
-- audit_log-Zeilen wuerde die Hash-Chain brechen bzw. ist durch DB-Trigger
-- ohnehin hart verboten). Ein Konto mit vorhandener Audit-Historie (praktisch
-- jedes jemals eingeloggte Konto) wird deshalb beim "Loeschen" stattdessen
-- deaktiviert, anonymisiert (Benutzername/Passwort/TOTP-Secret geloescht)
-- und hier markiert; nur ein Konto OHNE jede Audit-Historie wird tatsaechlich
-- per SQL-DELETE entfernt. Siehe delete_user() in app/admin/routes.py.
ALTER TABLE users ADD COLUMN deleted_at TEXT;