92 lines
5.4 KiB
SQL
92 lines
5.4 KiB
SQL
-- 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);
|
|
|
|
-- WICHTIG zu ALTER TABLE ADD COLUMN + REFERENCES in SQLite: eine per ALTER
|
|
-- TABLE nachtraeglich hinzugefuegte Spalte mit REFERENCES-Klausel darf NUR
|
|
-- einen NULL-Default haben ("Cannot add a REFERENCES column with non-NULL
|
|
-- default value") -- UND eine NOT NULL-Klausel wiederum erfordert einen
|
|
-- Default ungleich NULL. Beides gleichzeitig (NOT NULL DEFAULT 1 REFERENCES
|
|
-- ...) ist beim nachtraeglichen Hinzufuegen einer Spalte also grundsaetzlich
|
|
-- unmoeglich (nur bei CREATE TABLE erlaubt) -- ein vollstaendiger
|
|
-- Tabellen-Rebuild (SQLite-12-Schritte-Verfahren) waere die einzige
|
|
-- Alternative, wird hier aber bewusst vermieden, um bestehende
|
|
-- Produktivdaten nicht bei jedem Deployment einem Rebuild-Risiko
|
|
-- auszusetzen. Stattdessen: Spalte NULLABLE mit REFERENCES hinzufuegen,
|
|
-- bestehende Zeilen sofort auf den Standard-Mandanten (id=1) zurueckfuellen.
|
|
-- Die Nicht-NULL-Garantie wird stattdessen ausschliesslich anwendungsseitig
|
|
-- durchgesetzt: JEDER Insert-Pfad in app/admin/routes.py ermittelt
|
|
-- tenant_id ueber _resolve_write_tenant() (nie NULL, siehe app/tenancy.py) --
|
|
-- bestehende Zeilen sind durch das Backfill unten ebenfalls nie NULL.
|
|
ALTER TABLE host_groups ADD COLUMN tenant_id INTEGER REFERENCES tenants(id);
|
|
UPDATE host_groups SET tenant_id = 1 WHERE tenant_id IS NULL;
|
|
|
|
ALTER TABLE user_groups ADD COLUMN tenant_id INTEGER REFERENCES tenants(id);
|
|
UPDATE user_groups SET tenant_id = 1 WHERE tenant_id IS NULL;
|
|
|
|
ALTER TABLE ssh_keys ADD COLUMN tenant_id INTEGER REFERENCES tenants(id);
|
|
UPDATE ssh_keys SET tenant_id = 1 WHERE tenant_id IS NULL;
|
|
|
|
-- api_tokens: konzeptionell 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). Schema-technisch aus obigem Grund dennoch nullable + Backfill;
|
|
-- app/admin/routes.py::create_api_token() setzt tenant_id auf jedem
|
|
-- Insert-Pfad immer explizit.
|
|
ALTER TABLE api_tokens ADD COLUMN tenant_id INTEGER REFERENCES tenants(id);
|
|
UPDATE api_tokens SET tenant_id = 1 WHERE tenant_id IS NULL;
|
|
|
|
-- 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);
|