new file: .gitignore
new file: README.md new file: database/schema.sql new file: paper-plugin/pom.xml new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/PaperLoggerPlugin.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/commands/MCLoggerCommand.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/database/DatabaseManager.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/BlockListener.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/EntityListener.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/InventoryListener.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/LuckPermsListener.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/PlayerChatCommandListener.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/PlayerDeathListener.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/PlayerMiscListener.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/PlayerSessionListener.java new file: paper-plugin/src/main/java/de/simolzimol/mclogger/paper/listeners/WorldListener.java new file: paper-plugin/src/main/resources/config.yml new file: paper-plugin/src/main/resources/plugin.yml new file: paper-plugin/target/classes/config.yml new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/PaperLoggerPlugin.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/commands/MCLoggerCommand$RsConsumer.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/commands/MCLoggerCommand.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/database/DatabaseManager$ThrowingRunnable.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/database/DatabaseManager.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/BlockListener.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/EntityListener.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/InventoryListener.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/LuckPermsListener.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/PlayerChatCommandListener.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/PlayerDeathListener.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/PlayerMiscListener.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/PlayerSessionListener.class new file: paper-plugin/target/classes/de/simolzimol/mclogger/paper/listeners/WorldListener.class new file: paper-plugin/target/classes/plugin.yml new file: paper-plugin/target/maven-archiver/pom.properties new file: paper-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file: paper-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file: paper-plugin/target/mclogger-paper-1.0.0.jar new file: paper-plugin/target/original-mclogger-paper-1.0.0.jar new file: velocity-plugin/pom.xml new file: velocity-plugin/src/main/java/de/simolzimol/mclogger/velocity/VelocityLoggerPlugin.java new file: velocity-plugin/src/main/java/de/simolzimol/mclogger/velocity/database/VelocityDatabaseManager.java new file: velocity-plugin/src/main/java/de/simolzimol/mclogger/velocity/listeners/VelocityEventListener.java new file: velocity-plugin/src/main/resources/velocity-config.yml new file: velocity-plugin/target/classes/de/simolzimol/mclogger/velocity/VelocityLoggerPlugin.class new file: velocity-plugin/target/classes/de/simolzimol/mclogger/velocity/database/VelocityDatabaseManager$ThrowingRunnable.class new file: velocity-plugin/target/classes/de/simolzimol/mclogger/velocity/database/VelocityDatabaseManager.class new file: velocity-plugin/target/classes/de/simolzimol/mclogger/velocity/listeners/VelocityEventListener.class new file: velocity-plugin/target/classes/velocity-config.yml new file: velocity-plugin/target/classes/velocity-plugin.json new file: velocity-plugin/target/maven-archiver/pom.properties new file: velocity-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file: velocity-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file: velocity-plugin/target/mclogger-velocity-1.0.0.jar new file: velocity-plugin/target/original-mclogger-velocity-1.0.0.jar new file: web/Dockerfile new file: web/app.py new file: web/blueprints/__init__.py new file: web/blueprints/auth.py new file: web/blueprints/group_admin.py new file: web/blueprints/panel.py new file: web/blueprints/site_admin.py new file: web/config.py new file: web/crypto.py new file: web/docker-compose.yml new file: web/panel_db.py new file: web/requirements.txt new file: web/static/css/style.css new file: web/static/js/main.js new file: web/templates/_pagination.html new file: web/templates/admin/base.html new file: web/templates/admin/dashboard.html new file: web/templates/admin/group_edit.html new file: web/templates/admin/group_members.html new file: web/templates/admin/groups.html new file: web/templates/admin/user_edit.html new file: web/templates/admin/users.html new file: web/templates/auth/admin_login.html new file: web/templates/auth/login.html new file: web/templates/base.html new file: web/templates/blocks.html new file: web/templates/chat.html new file: web/templates/commands.html new file: web/templates/dashboard.html new file: web/templates/deaths.html new file: web/templates/group_admin/base.html new file: web/templates/group_admin/dashboard.html new file: web/templates/group_admin/database.html new file: web/templates/group_admin/member_edit.html new file: web/templates/group_admin/members.html new file: web/templates/login.html new file: web/templates/panel/blocks.html new file: web/templates/panel/chat.html new file: web/templates/panel/commands.html new file: web/templates/panel/dashboard.html new file: web/templates/panel/deaths.html new file: web/templates/panel/no_db.html new file: web/templates/panel/perms.html new file: web/templates/panel/player_detail.html new file: web/templates/panel/players.html new file: web/templates/panel/proxy.html new file: web/templates/panel/server_events.html new file: web/templates/panel/sessions.html new file: web/templates/perms.html new file: web/templates/player_detail.html new file: web/templates/players.html new file: web/templates/proxy.html new file: web/templates/server_events.html new file: web/templates/sessions.html
This commit is contained in:
349
web/panel_db.py
Normal file
349
web/panel_db.py
Normal file
@@ -0,0 +1,349 @@
|
||||
"""
|
||||
MCLogger – Panel-Datenbank-Operationen
|
||||
Verwaltet Nutzer, Gruppen, Mitgliedschaften (PANEL_DB)
|
||||
und verschlüsselte MC-DB-Zugangsdaten (CREDS_DB).
|
||||
"""
|
||||
import json
|
||||
import pymysql
|
||||
import pymysql.cursors
|
||||
from config import Config
|
||||
from crypto import generate_salt, hash_password, verify_password, encrypt_str, decrypt_str
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Datenbankverbindungen
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
def get_panel_db():
|
||||
return pymysql.connect(
|
||||
host=Config.PANEL_DB_HOST,
|
||||
port=Config.PANEL_DB_PORT,
|
||||
user=Config.PANEL_DB_USER,
|
||||
password=Config.PANEL_DB_PASSWORD,
|
||||
database=Config.PANEL_DB_NAME,
|
||||
charset="utf8mb4",
|
||||
cursorclass=pymysql.cursors.DictCursor,
|
||||
autocommit=True,
|
||||
)
|
||||
|
||||
|
||||
def get_creds_db():
|
||||
return pymysql.connect(
|
||||
host=Config.CREDS_DB_HOST,
|
||||
port=Config.CREDS_DB_PORT,
|
||||
user=Config.CREDS_DB_USER,
|
||||
password=Config.CREDS_DB_PASSWORD,
|
||||
database=Config.CREDS_DB_NAME,
|
||||
charset="utf8mb4",
|
||||
cursorclass=pymysql.cursors.DictCursor,
|
||||
autocommit=True,
|
||||
)
|
||||
|
||||
|
||||
def _panel_query(sql, args=None, fetchone=False, write=False):
|
||||
conn = get_panel_db()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sql, args or ())
|
||||
if write:
|
||||
return cur.lastrowid
|
||||
return cur.fetchone() if fetchone else cur.fetchall()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def _creds_query(sql, args=None, fetchone=False, write=False):
|
||||
conn = get_creds_db()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sql, args or ())
|
||||
if write:
|
||||
return cur.lastrowid
|
||||
return cur.fetchone() if fetchone else cur.fetchall()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Initialisierung – Tabellen anlegen
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
PANEL_SCHEMA = [
|
||||
"""CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) UNIQUE NOT NULL,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
salt VARCHAR(64) NOT NULL,
|
||||
is_site_admin TINYINT(1) DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login TIMESTAMP NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4""",
|
||||
|
||||
"""CREATE TABLE IF NOT EXISTS user_groups (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(100) UNIQUE NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4""",
|
||||
|
||||
"""CREATE TABLE IF NOT EXISTS group_members (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
group_id INT NOT NULL,
|
||||
role ENUM('admin','member') DEFAULT 'member',
|
||||
permissions JSON,
|
||||
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_user_group (user_id, group_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (group_id) REFERENCES user_groups(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4""",
|
||||
]
|
||||
|
||||
CREDS_SCHEMA = [
|
||||
"""CREATE TABLE IF NOT EXISTS group_databases (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
group_id INT UNIQUE NOT NULL,
|
||||
enc_host TEXT NOT NULL,
|
||||
enc_port TEXT NOT NULL,
|
||||
enc_user TEXT NOT NULL,
|
||||
enc_password TEXT NOT NULL,
|
||||
enc_database TEXT NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4""",
|
||||
]
|
||||
|
||||
|
||||
def init_databases():
|
||||
"""Erstellt alle benötigten Tabellen falls nicht vorhanden."""
|
||||
panel = get_panel_db()
|
||||
try:
|
||||
with panel.cursor() as cur:
|
||||
for stmt in PANEL_SCHEMA:
|
||||
cur.execute(stmt)
|
||||
finally:
|
||||
panel.close()
|
||||
|
||||
creds = get_creds_db()
|
||||
try:
|
||||
with creds.cursor() as cur:
|
||||
for stmt in CREDS_SCHEMA:
|
||||
cur.execute(stmt)
|
||||
finally:
|
||||
creds.close()
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Nutzer
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
def create_user(username: str, email: str, password: str, is_site_admin: bool = False) -> int:
|
||||
salt = generate_salt()
|
||||
pw_hash = hash_password(password, salt)
|
||||
return _panel_query(
|
||||
"INSERT INTO users (username, email, password_hash, salt, is_site_admin) VALUES (%s,%s,%s,%s,%s)",
|
||||
(username, email, pw_hash, salt, int(is_site_admin)), write=True
|
||||
)
|
||||
|
||||
|
||||
def get_user_by_username(username: str):
|
||||
return _panel_query("SELECT * FROM users WHERE username=%s", (username,), fetchone=True)
|
||||
|
||||
|
||||
def get_user_by_id(user_id: int):
|
||||
return _panel_query("SELECT * FROM users WHERE id=%s", (user_id,), fetchone=True)
|
||||
|
||||
|
||||
def update_user(user_id: int, username: str, email: str, is_site_admin: bool):
|
||||
_panel_query(
|
||||
"UPDATE users SET username=%s, email=%s, is_site_admin=%s WHERE id=%s",
|
||||
(username, email, int(is_site_admin), user_id), write=True
|
||||
)
|
||||
|
||||
|
||||
def change_password(user_id: int, new_password: str):
|
||||
salt = generate_salt()
|
||||
pw_hash = hash_password(new_password, salt)
|
||||
_panel_query(
|
||||
"UPDATE users SET password_hash=%s, salt=%s WHERE id=%s",
|
||||
(pw_hash, salt, user_id), write=True
|
||||
)
|
||||
|
||||
|
||||
def delete_user(user_id: int):
|
||||
_panel_query("DELETE FROM users WHERE id=%s", (user_id,), write=True)
|
||||
|
||||
|
||||
def check_login(username: str, password: str):
|
||||
"""Prüft Anmeldedaten. Gibt den Nutzer zurück oder None."""
|
||||
user = get_user_by_username(username)
|
||||
if not user:
|
||||
return None
|
||||
if not verify_password(password, user["salt"], user["password_hash"]):
|
||||
return None
|
||||
_panel_query("UPDATE users SET last_login=NOW() WHERE id=%s", (user["id"],), write=True)
|
||||
return user
|
||||
|
||||
|
||||
def list_all_users():
|
||||
return _panel_query(
|
||||
"SELECT u.*, COUNT(gm.group_id) AS group_count "
|
||||
"FROM users u LEFT JOIN group_members gm ON gm.user_id=u.id "
|
||||
"GROUP BY u.id ORDER BY u.username"
|
||||
)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Gruppen
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
def create_group(name: str, description: str = "") -> int:
|
||||
return _panel_query(
|
||||
"INSERT INTO user_groups (name, description) VALUES (%s,%s)",
|
||||
(name, description), write=True
|
||||
)
|
||||
|
||||
|
||||
def get_group_by_id(group_id: int):
|
||||
return _panel_query("SELECT * FROM user_groups WHERE id=%s", (group_id,), fetchone=True)
|
||||
|
||||
|
||||
def get_group_by_name(name: str):
|
||||
return _panel_query("SELECT * FROM user_groups WHERE name=%s", (name,), fetchone=True)
|
||||
|
||||
|
||||
def update_group(group_id: int, name: str, description: str):
|
||||
_panel_query(
|
||||
"UPDATE user_groups SET name=%s, description=%s WHERE id=%s",
|
||||
(name, description, group_id), write=True
|
||||
)
|
||||
|
||||
|
||||
def delete_group(group_id: int):
|
||||
_panel_query("DELETE FROM user_groups WHERE id=%s", (group_id,), write=True)
|
||||
|
||||
|
||||
def list_all_groups():
|
||||
return _panel_query(
|
||||
"SELECT g.*, COUNT(gm.user_id) AS member_count "
|
||||
"FROM user_groups g LEFT JOIN group_members gm ON gm.group_id=g.id "
|
||||
"GROUP BY g.id ORDER BY g.name"
|
||||
)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Gruppenmitgliedschaften
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
def get_user_groups(user_id: int):
|
||||
return _panel_query(
|
||||
"SELECT g.*, gm.role, gm.permissions "
|
||||
"FROM user_groups g "
|
||||
"JOIN group_members gm ON gm.group_id=g.id "
|
||||
"WHERE gm.user_id=%s ORDER BY g.name",
|
||||
(user_id,)
|
||||
)
|
||||
|
||||
|
||||
def get_group_member(user_id: int, group_id: int):
|
||||
return _panel_query(
|
||||
"SELECT * FROM group_members WHERE user_id=%s AND group_id=%s",
|
||||
(user_id, group_id), fetchone=True
|
||||
)
|
||||
|
||||
|
||||
def get_group_members(group_id: int):
|
||||
return _panel_query(
|
||||
"SELECT u.id, u.username, u.email, u.last_login, gm.role, gm.permissions, gm.joined_at "
|
||||
"FROM group_members gm "
|
||||
"JOIN users u ON u.id=gm.user_id "
|
||||
"WHERE gm.group_id=%s ORDER BY gm.role DESC, u.username",
|
||||
(group_id,)
|
||||
)
|
||||
|
||||
|
||||
def add_group_member(user_id: int, group_id: int, role: str = "member", permissions: dict = None):
|
||||
if permissions is None:
|
||||
permissions = Config.DEFAULT_PERMISSIONS
|
||||
_panel_query(
|
||||
"INSERT INTO group_members (user_id, group_id, role, permissions) VALUES (%s,%s,%s,%s) "
|
||||
"ON DUPLICATE KEY UPDATE role=VALUES(role), permissions=VALUES(permissions)",
|
||||
(user_id, group_id, role, json.dumps(permissions)), write=True
|
||||
)
|
||||
|
||||
|
||||
def update_member(user_id: int, group_id: int, role: str, permissions: dict):
|
||||
_panel_query(
|
||||
"UPDATE group_members SET role=%s, permissions=%s WHERE user_id=%s AND group_id=%s",
|
||||
(role, json.dumps(permissions), user_id, group_id), write=True
|
||||
)
|
||||
|
||||
|
||||
def remove_group_member(user_id: int, group_id: int):
|
||||
_panel_query(
|
||||
"DELETE FROM group_members WHERE user_id=%s AND group_id=%s",
|
||||
(user_id, group_id), write=True
|
||||
)
|
||||
|
||||
|
||||
def get_permissions(user_id: int, group_id: int) -> dict:
|
||||
"""Gibt die Berechtigungen des Nutzers in der Gruppe zurück."""
|
||||
member = get_group_member(user_id, group_id)
|
||||
if not member:
|
||||
return {}
|
||||
raw = member.get("permissions")
|
||||
if isinstance(raw, str):
|
||||
return json.loads(raw)
|
||||
if isinstance(raw, dict):
|
||||
return raw
|
||||
return {}
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# MC-Datenbank-Zugangsdaten (verschlüsselt)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
def set_group_db_creds(group_id: int, host: str, port: int, user: str, password: str, database: str):
|
||||
"""Verschlüsselt und speichert die MC-DB-Zugangsdaten einer Gruppe."""
|
||||
_creds_query(
|
||||
"INSERT INTO group_databases (group_id, enc_host, enc_port, enc_user, enc_password, enc_database) "
|
||||
"VALUES (%s,%s,%s,%s,%s,%s) "
|
||||
"ON DUPLICATE KEY UPDATE enc_host=VALUES(enc_host), enc_port=VALUES(enc_port), "
|
||||
"enc_user=VALUES(enc_user), enc_password=VALUES(enc_password), enc_database=VALUES(enc_database)",
|
||||
(group_id,
|
||||
encrypt_str(host),
|
||||
encrypt_str(str(port)),
|
||||
encrypt_str(user),
|
||||
encrypt_str(password),
|
||||
encrypt_str(database)),
|
||||
write=True
|
||||
)
|
||||
|
||||
|
||||
def get_group_db_creds(group_id: int) -> dict | None:
|
||||
"""Gibt die entschlüsselten MC-DB-Zugangsdaten zurück oder None."""
|
||||
row = _creds_query(
|
||||
"SELECT * FROM group_databases WHERE group_id=%s",
|
||||
(group_id,), fetchone=True
|
||||
)
|
||||
if not row:
|
||||
return None
|
||||
return {
|
||||
"host": decrypt_str(row["enc_host"]),
|
||||
"port": int(decrypt_str(row["enc_port"])),
|
||||
"user": decrypt_str(row["enc_user"]),
|
||||
"password": decrypt_str(row["enc_password"]),
|
||||
"database": decrypt_str(row["enc_database"]),
|
||||
}
|
||||
|
||||
|
||||
def delete_group_db_creds(group_id: int):
|
||||
_creds_query("DELETE FROM group_databases WHERE group_id=%s", (group_id,), write=True)
|
||||
|
||||
|
||||
def has_db_configured(group_id: int) -> bool:
|
||||
row = _creds_query(
|
||||
"SELECT id FROM group_databases WHERE group_id=%s",
|
||||
(group_id,), fetchone=True
|
||||
)
|
||||
return row is not None
|
||||
Reference in New Issue
Block a user