- 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>
221 lines
5.9 KiB
Python
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.")
|