first commit
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
"""SQLite-Datenbank (thread-sicher über ein Lock)."""
|
||||
import os
|
||||
import sqlite3
|
||||
import threading
|
||||
import time
|
||||
|
||||
from . import config
|
||||
|
||||
SCHEMA = """
|
||||
CREATE TABLE IF NOT EXISTS users(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL COLLATE NOCASE,
|
||||
password_hash TEXT NOT NULL,
|
||||
is_admin INTEGER NOT NULL DEFAULT 0,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
policy TEXT NOT NULL DEFAULT 'inherit',
|
||||
comment TEXT NOT NULL DEFAULT '',
|
||||
created_at REAL NOT NULL,
|
||||
last_seen REAL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS rules(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
action TEXT NOT NULL CHECK(action IN ('allow','deny')),
|
||||
host TEXT NOT NULL DEFAULT '',
|
||||
ports TEXT NOT NULL DEFAULT '',
|
||||
comment TEXT NOT NULL DEFAULT '',
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
created_at REAL NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_rules_user ON rules(user_id);
|
||||
CREATE TABLE IF NOT EXISTS forwards(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
protocol TEXT NOT NULL CHECK(protocol IN ('tcp','udp')),
|
||||
listen_port INTEGER NOT NULL,
|
||||
target_host TEXT NOT NULL,
|
||||
target_port INTEGER NOT NULL,
|
||||
allowed_sources TEXT NOT NULL DEFAULT '',
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
comment TEXT NOT NULL DEFAULT '',
|
||||
created_at REAL NOT NULL,
|
||||
UNIQUE(protocol, listen_port)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS history(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ts REAL NOT NULL,
|
||||
username TEXT,
|
||||
client_ip TEXT,
|
||||
proto TEXT,
|
||||
method TEXT,
|
||||
host TEXT,
|
||||
port INTEGER,
|
||||
url TEXT,
|
||||
status TEXT,
|
||||
detail TEXT,
|
||||
bytes_up INTEGER NOT NULL DEFAULT 0,
|
||||
bytes_down INTEGER NOT NULL DEFAULT 0,
|
||||
duration REAL NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_history_ts ON history(ts);
|
||||
CREATE INDEX IF NOT EXISTS idx_history_user ON history(username, ts);
|
||||
CREATE INDEX IF NOT EXISTS idx_history_host ON history(host);
|
||||
CREATE TABLE IF NOT EXISTS tokens(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
token_hash TEXT UNIQUE NOT NULL,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
kind TEXT NOT NULL,
|
||||
created_at REAL NOT NULL,
|
||||
expires_at REAL,
|
||||
last_used REAL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS settings(
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
"""
|
||||
|
||||
DEFAULT_SETTINGS = {
|
||||
"default_policy": "allow", # allow = Blacklist-Modus, deny = Whitelist-Modus
|
||||
"log_enabled": "1",
|
||||
"log_retention_days": "90",
|
||||
"anonymous_networks": "", # CIDRs, die ohne Login surfen dürfen
|
||||
"block_loopback": "1", # Zugriff auf 127.0.0.0/8 / ::1 immer sperren
|
||||
"auth_realm": "Proxy",
|
||||
"public_host": "", # Hostname/IP für PAC-Datei
|
||||
}
|
||||
|
||||
|
||||
class Database:
|
||||
def __init__(self, path: str):
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
self.lock = threading.RLock()
|
||||
self.conn = sqlite3.connect(path, check_same_thread=False, isolation_level=None)
|
||||
self.conn.row_factory = sqlite3.Row
|
||||
self.conn.execute("PRAGMA journal_mode=WAL")
|
||||
self.conn.execute("PRAGMA synchronous=NORMAL")
|
||||
self.conn.execute("PRAGMA foreign_keys=ON")
|
||||
with self.lock:
|
||||
self.conn.executescript(SCHEMA)
|
||||
for k, v in DEFAULT_SETTINGS.items():
|
||||
self.conn.execute("INSERT OR IGNORE INTO settings(key,value) VALUES(?,?)", (k, v))
|
||||
|
||||
def query(self, sql: str, params=()) -> list[dict]:
|
||||
with self.lock:
|
||||
return [dict(r) for r in self.conn.execute(sql, params).fetchall()]
|
||||
|
||||
def one(self, sql: str, params=()) -> dict | None:
|
||||
with self.lock:
|
||||
r = self.conn.execute(sql, params).fetchone()
|
||||
return dict(r) if r else None
|
||||
|
||||
def execute(self, sql: str, params=()) -> sqlite3.Cursor:
|
||||
with self.lock:
|
||||
return self.conn.execute(sql, params)
|
||||
|
||||
def executemany(self, sql: str, rows) -> None:
|
||||
with self.lock:
|
||||
self.conn.execute("BEGIN")
|
||||
try:
|
||||
self.conn.executemany(sql, rows)
|
||||
self.conn.execute("COMMIT")
|
||||
except Exception:
|
||||
self.conn.execute("ROLLBACK")
|
||||
raise
|
||||
|
||||
# Einstellungen
|
||||
def settings(self) -> dict:
|
||||
return {r["key"]: r["value"] for r in self.query("SELECT key, value FROM settings")}
|
||||
|
||||
def set_setting(self, key: str, value: str) -> None:
|
||||
self.execute("INSERT INTO settings(key,value) VALUES(?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value",
|
||||
(key, value))
|
||||
|
||||
|
||||
db = Database(config.DB_PATH)
|
||||
|
||||
|
||||
def now() -> float:
|
||||
return time.time()
|
||||
Reference in New Issue
Block a user