Files
tesm/srv/tesm/create_db.py
T
alientimandClaude Sonnet 5 09b222615a Fileshare-Feature: LDAP-gruppengesteuerte Netzlaufwerke im Browser (v1.1.0)
- Neue ldap_fileshare_mappings-Tabelle (AD-Gruppe -> Freigabe-UNC+Label),
  verwaltet in Einstellungen -> LDAP wie die bestehende App-Gruppenzuordnung
  (inkl. AD-Gruppen-Dropdown per "Gruppen laden", UNC-Backslash/Slash-
  Normalisierung).
- Login-Gate: optionale "Erforderliche AD-Gruppe fuer Login" (Dropdown,
  gleiches Lade-Muster) -- ohne Mitgliedschaft scheitert der Login wie bei
  falschen Zugangsdaten, ohne Hinweis auf den eigentlichen Grund.
- Sessionabhaengiges Mounten: beim LDAP-Login werden alle per AD-Gruppe
  zugeordneten Freigaben mit den eigenen (nie gespeicherten) Zugangsdaten
  per mount.cifs unter /mnt/tesm-shares/<session-token>/ gemountet, beim
  Logout wieder ausgehaengt. Passwort läuft ausschliesslich ueber die
  PASSWD-Umgebungsvariable von mount.cifs. Aufraeum-Sweep (30 Min-Takt,
  12h-Schwelle) und Cleanup-on-Startup als Sicherheitsnetz.
- Zwei unabhaengige Gates: TESM-Rechtesystem (fileshare.view/.create/.edit,
  neue PERMISSIONS-Gruppe fileshare_group) UND AD-Gruppenmitgliedschaft
  muessen beide zutreffen, sonst wird nicht gemountet und der Menuepunkt
  "Dateifreigaben" bleibt verborgen.
- Neuer Dateibrowser (/fileshare, .../download, .../upload, .../mkdir,
  .../delete, .../rename) mit Breadcrumbs, Upload/Download, Anlegen/
  Umbenennen/Loeschen -- Pfadaufloesung ausschliesslich ueber
  os.path.realpath-Vergleich gegen die eigene Mount-Root (Path-Traversal-
  Schutz), zusaetzlich secure_filename() fuer Datei-/Ordnernamen.
- KRITISCHER Bugfix waehrend Live-Test gefunden: generate_ips.py importiert
  app.py als Hilfsmodul (u.a. aus poe.sh/tesm-check.service heraus, alle
  paar Sekunden) -- das fuehrte Modul-Level-Code erneut aus und hat aktive
  Fileshare-Mounts anderer Sitzungen sofort wieder ausgehaengt. Neues
  TESM_WEB_PROCESS-Env-Flag (nur in tesm.service gesetzt) schuetzt
  Cleanup-on-Startup und den Sweep-Thread davor, bei einem reinen
  Modul-Import erneut zu laufen.
- install.sh: cifs-utils ergaenzt (liefert mount.cifs).
- SCHEMA_VERSION 1 -> 2, VERSION 1.0.10 -> 1.1.0.

Live auf Testbox (192.168.82.51) und POETEST (192.168.80.202) getestet:
Login-Gate (erlauben/verbieten/reset), Rechte-Gate (Mount+Menuepunkt nur
mit fileshare.view), vollstaendiger Datei-Workflow (Upload/Download/
Umbenennen/Anlegen/Loeschen) gegen eine echte SMB-Freigabe, Path-Traversal
gegen alle 5 Routen (serverseitig verifiziert, kein Escape), sauberes
Unmount bei /logout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 16:34:28 +02:00

221 lines
5.9 KiB
Python

#!/usr/bin/env python3
"""Initialisiert die SQLite-Datenbank für TESM.
WICHTIG: Wer hier eine neue Tabelle oder Spalte hinzufügt, muss auch die
Datei SCHEMA_VERSION (daneben, im selben Verzeichnis) um 1 erhöhen.
update.sh vergleicht diese Zahl zwischen der aktuell installierten und der
neuen Version, um zu entscheiden, ob ein Update in-place laufen darf
(bestehende Datenbank bleibt erhalten) oder eine frische Installation
nötig ist (Datenbank wird beiseitegesichert). Ein vergessenes Hochzählen
würde dazu führen, dass ein Host mit altem Schema faelschlich per
In-Place-Update aktualisiert wird, statt frisch installiert zu werden --
_ensure_schema() faengt die meisten Faelle zwar zur Laufzeit ab, ist aber
kein Ersatz fuer eine korrekte Versionsnummer hier."""
import os
import sqlite3
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DB_PATH = os.environ.get("TESM_DB_PATH", os.path.join(BASE_DIR, "sqlite.db"))
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute("""
CREATE TABLE IF NOT EXISTS credentials (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
username TEXT NOT NULL,
password TEXT NOT NULL,
category TEXT NOT NULL DEFAULT 'switch',
deleted_at TEXT
);
""")
c.execute("""
CREATE TABLE IF NOT EXISTS switches (
hostname TEXT PRIMARY KEY,
ip TEXT NOT NULL,
ssh_port INTEGER,
credential_id INTEGER,
last_modified_by TEXT,
last_modified_at TEXT,
deleted_at TEXT,
FOREIGN KEY (credential_id) REFERENCES credentials(id)
);
""")
c.execute("""
CREATE TABLE IF NOT EXISTS devices (
mac TEXT PRIMARY KEY,
ip TEXT NOT NULL,
switch_hostname TEXT,
port TEXT,
name TEXT NOT NULL,
is_active INTEGER DEFAULT 1,
last_modified_by TEXT,
last_modified_at TEXT,
credential_id INTEGER,
ssh_port INTEGER,
deleted_at TEXT,
FOREIGN KEY (switch_hostname) REFERENCES switches(hostname)
);
""")
c.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
is_admin INTEGER DEFAULT 0,
first_name TEXT,
last_name TEXT,
avatar_filename TEXT,
auth_source TEXT NOT NULL DEFAULT 'local',
email TEXT,
is_locked INTEGER NOT NULL DEFAULT 0,
deleted_at TEXT
);
""")
c.execute("""
CREATE TABLE IF NOT EXISTS groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
is_default INTEGER DEFAULT 0,
is_system INTEGER DEFAULT 0,
deleted_at TEXT
);
""")
c.execute("""
CREATE TABLE IF NOT EXISTS group_permissions (
group_id INTEGER NOT NULL,
permission TEXT NOT NULL,
PRIMARY KEY (group_id, permission),
FOREIGN KEY (group_id) REFERENCES groups(id)
);
""")
c.execute("""
CREATE TABLE IF NOT EXISTS user_groups (
user_id INTEGER NOT NULL,
group_id INTEGER NOT NULL,
PRIMARY KEY (user_id, group_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (group_id) REFERENCES groups(id)
);
""")
c.execute("""
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
""")
c.execute("""
CREATE TABLE IF NOT EXISTS dhcp_option_defs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code INTEGER NOT NULL UNIQUE,
name TEXT NOT NULL UNIQUE,
type TEXT NOT NULL,
description TEXT,
is_standard INTEGER NOT NULL DEFAULT 0
);
""")
c.execute("""
CREATE TABLE IF NOT EXISTS dhcp_option_values (
id INTEGER PRIMARY KEY AUTOINCREMENT,
option_def_id INTEGER NOT NULL,
device_mac TEXT NOT NULL DEFAULT '',
value TEXT NOT NULL,
FOREIGN KEY (option_def_id) REFERENCES dhcp_option_defs(id),
UNIQUE (option_def_id, device_mac)
);
""")
c.execute("""
CREATE TABLE IF NOT EXISTS dhcp_subnets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
interface TEXT NOT NULL,
range_start TEXT NOT NULL,
range_end TEXT NOT NULL,
gateway TEXT,
dns TEXT,
sort_order INTEGER NOT NULL DEFAULT 0,
enabled INTEGER NOT NULL DEFAULT 1
);
""")
c.execute("""
CREATE TABLE IF NOT EXISTS dhcp_manual_reservations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
mac TEXT NOT NULL UNIQUE,
ip TEXT NOT NULL,
name TEXT NOT NULL
);
""")
c.execute("""
CREATE TABLE IF NOT EXISTS service_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
purpose TEXT UNIQUE NOT NULL,
username TEXT NOT NULL,
password TEXT NOT NULL
);
""")
c.execute("""
CREATE TABLE IF NOT EXISTS ldap_group_mappings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ad_group_dn TEXT UNIQUE NOT NULL,
ad_group_name TEXT NOT NULL,
app_group_id INTEGER NOT NULL,
FOREIGN KEY (app_group_id) REFERENCES groups(id)
);
""")
c.execute("""
CREATE TABLE IF NOT EXISTS ldap_fileshare_mappings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ad_group_dn TEXT NOT NULL,
ad_group_name TEXT NOT NULL,
share_label TEXT NOT NULL,
share_unc TEXT NOT NULL,
UNIQUE(ad_group_dn, share_unc)
);
""")
c.execute("""
CREATE TABLE IF NOT EXISTS audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts TEXT NOT NULL,
username TEXT NOT NULL,
action TEXT NOT NULL,
target TEXT,
details TEXT
);
""")
c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("interval", "5"))
c.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("check_interval", "300"))
cur = c.execute("INSERT OR IGNORE INTO groups (name, is_default, is_system) VALUES (?, 1, 1)", ("Benutzer",))
if cur.rowcount > 0:
default_group_id = cur.lastrowid
c.executemany(
"INSERT OR IGNORE INTO group_permissions (group_id, permission) VALUES (?, ?)",
[
(default_group_id, "devices_group.view"),
(default_group_id, "devices.view"),
(default_group_id, "switches.view"),
(default_group_id, "logs_group.view"),
(default_group_id, "logs_live.view"),
],
)
conn.commit()
conn.close()
print(f"Datenbank '{DB_PATH}' wurde initialisiert inklusive Settings.")