BastionGuard/webui/includes/users.php
specialworld83 f0f913a209 Release 2.0
2026-07-15 10:52:22 +02:00

197 lines
9.9 KiB
PHP

<?php
require_once __DIR__ . '/functions.php';
function bg_auth_config_path() {
$primary = __DIR__ . '/webui_config.php';
if (is_file($primary)) return $primary;
// Compatibility with older installs where setup wrote config.php in the web root.
$legacy = dirname(__DIR__) . '/config.php';
if (is_file($legacy)) {
$head = @file_get_contents($legacy, false, null, 0, 4096);
if (is_string($head) && strpos($head, 'AUTH_DRIVER') !== false && strpos($head, 'BastionGuard configurations') === false) return $legacy;
}
return '';
}
function bg_config_exists() { return bg_auth_config_path() !== ''; }
function bg_load_config() { $p = bg_auth_config_path(); if ($p !== '') require_once $p; }
function bg_user_pdo() {
bg_load_config();
$driver = defined('AUTH_DRIVER') ? strtolower((string)AUTH_DRIVER) : 'mysql';
if ($driver === 'sqlite') {
$db = defined('SQLITE_DB') ? SQLITE_DB : dirname(__DIR__) . '/db/users.db';
if (!is_dir(dirname($db))) bg_safe_mkdir(dirname($db), 0750);
return new PDO('sqlite:' . $db, null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
}
if (in_array($driver, ['pgsql','postgres','postgresql'], true)) {
$dsn = 'pgsql:host=' . DB_HOST;
if (defined('DB_PORT') && DB_PORT !== '') $dsn .= ';port=' . DB_PORT;
$dsn .= ';dbname=' . DB_NAME;
$pdo = new PDO($dsn, DB_USER, DB_PASS, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
try { $pdo->exec("SET client_encoding TO 'UTF8'"); } catch (Throwable $e) { /* optional */ }
return $pdo;
}
$dsn = 'mysql:host=' . DB_HOST;
if (defined('DB_PORT') && DB_PORT !== '') $dsn .= ';port=' . DB_PORT;
$dsn .= ';dbname=' . DB_NAME . ';charset=utf8mb4';
return new PDO($dsn, DB_USER, DB_PASS, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
}
function bg_users_schema(PDO $pdo) {
$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
if ($driver === 'sqlite') {
$pdo->exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, email TEXT NOT NULL DEFAULT '', password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'admin', created_at TEXT DEFAULT CURRENT_TIMESTAMP)");
$cols = [];
foreach ($pdo->query('PRAGMA table_info(users)') as $row) $cols[$row['name']] = true;
if (empty($cols['email'])) $pdo->exec("ALTER TABLE users ADD COLUMN email TEXT NOT NULL DEFAULT ''");
if (empty($cols['role'])) $pdo->exec("ALTER TABLE users ADD COLUMN role TEXT NOT NULL DEFAULT 'admin'");
} elseif ($driver === 'pgsql') {
$pdo->exec("CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, username VARCHAR(100) UNIQUE NOT NULL, email VARCHAR(190) NOT NULL DEFAULT '', password_hash VARCHAR(255) NOT NULL, role VARCHAR(20) NOT NULL DEFAULT 'admin', created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP)");
try { $pdo->exec("ALTER TABLE users ADD COLUMN IF NOT EXISTS email VARCHAR(190) NOT NULL DEFAULT ''"); } catch (Throwable $e) { /* already present */ }
try { $pdo->exec("ALTER TABLE users ADD COLUMN IF NOT EXISTS role VARCHAR(20) NOT NULL DEFAULT 'admin'"); } catch (Throwable $e) { /* already present */ }
} else {
$pdo->exec("CREATE TABLE IF NOT EXISTS users (id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(100) UNIQUE NOT NULL, email VARCHAR(190) NOT NULL DEFAULT '', password_hash VARCHAR(255) NOT NULL, role VARCHAR(20) NOT NULL DEFAULT 'admin', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
try { $pdo->exec("ALTER TABLE users ADD COLUMN email VARCHAR(190) NOT NULL DEFAULT ''"); } catch (Throwable $e) { /* already present */ }
try { $pdo->exec("ALTER TABLE users ADD COLUMN role VARCHAR(20) NOT NULL DEFAULT 'admin'"); } catch (Throwable $e) { /* already present */ }
}
}
function bg_normalize_role($role) {
$role = strtolower(trim((string)$role));
return in_array($role, ['admin','user'], true) ? $role : 'user';
}
function bg_create_user($username, $password, $role = 'user', $email = '') {
$username = trim((string)$username);
if ($username === '') throw new InvalidArgumentException('Username is required.');
if (strlen((string)$password) < 8) throw new InvalidArgumentException('Password must be at least 8 characters.');
$role = bg_normalize_role($role);
$email = trim((string)$email);
if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) throw new InvalidArgumentException('Invalid email address.');
$pdo = bg_user_pdo();
bg_users_schema($pdo);
$stmt = $pdo->prepare('INSERT INTO users (username, email, password_hash, role) VALUES (?, ?, ?, ?)');
return $stmt->execute([$username, $email, password_hash($password, PASSWORD_DEFAULT), $role]);
}
function bg_verify_user($username, $password) {
$pdo = bg_user_pdo();
bg_users_schema($pdo);
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = ?');
$stmt->execute([trim((string)$username)]);
$user = $stmt->fetch();
if (!$user) return false;
$ok = false;
if (!empty($user['password_hash']) && password_verify($password, $user['password_hash'])) $ok = true;
if (!$ok && !empty($user['password_sha256']) && !empty($user['salt'])) {
$hash = hash('sha256', $user['salt'] . $password);
$ok = hash_equals($user['password_sha256'], $hash);
}
if (!$ok) return false;
if (empty($user['role'])) $user['role'] = 'admin'; // compatibility with pre-role databases
$user['role'] = bg_normalize_role($user['role']);
return $user;
}
function bg_current_user() {
return isset($_SESSION['user']) ? (string)$_SESSION['user'] : '';
}
function bg_current_user_role() {
$role = isset($_SESSION['role']) ? (string)$_SESSION['role'] : '';
return bg_normalize_role($role ?: 'user');
}
function bg_current_user_is_admin() {
return bg_current_user() !== '' && bg_current_user_role() === 'admin';
}
function bg_require_admin() {
if (bg_current_user_is_admin()) return;
if (!headers_sent()) http_response_code(403);
include __DIR__ . '/header.php';
include __DIR__ . '/sidebar.php';
echo '<div class="page-title"><div><h1>' . bg_e(bg_t('admin_required')) . '</h1><p>' . bg_e(bg_t('admin_required_desc')) . '</p></div></div>';
echo '<div class="alert alert-warning">' . bg_e(bg_t('admin_required_full')) . '</div>';
include __DIR__ . '/footer.php';
exit;
}
function bg_users_all() {
$pdo = bg_user_pdo();
bg_users_schema($pdo);
$stmt = $pdo->query('SELECT id, username, email, role, created_at FROM users ORDER BY username ASC');
$rows = $stmt ? $stmt->fetchAll() : [];
foreach ($rows as &$r) $r['role'] = bg_normalize_role($r['role'] ?? 'user');
return $rows;
}
function bg_user_by_id($id) {
$pdo = bg_user_pdo();
bg_users_schema($pdo);
$stmt = $pdo->prepare('SELECT id, username, email, role, created_at FROM users WHERE id = ?');
$stmt->execute([(int)$id]);
$u = $stmt->fetch();
if ($u) $u['role'] = bg_normalize_role($u['role'] ?? 'user');
return $u;
}
function bg_admin_count(PDO $pdo = null) {
$pdo = $pdo ?: bg_user_pdo();
bg_users_schema($pdo);
$stmt = $pdo->query("SELECT COUNT(*) AS c FROM users WHERE role = 'admin'");
$row = $stmt ? $stmt->fetch() : ['c'=>0];
return (int)($row['c'] ?? 0);
}
function bg_update_user($id, $username, $role, $password = '', $email = '') {
$id = (int)$id;
$username = trim((string)$username);
if ($id <= 0 || $username === '') throw new InvalidArgumentException('Invalid user.');
$role = bg_normalize_role($role);
$email = trim((string)$email);
if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) throw new InvalidArgumentException('Invalid email address.');
$pdo = bg_user_pdo();
bg_users_schema($pdo);
$old = bg_user_by_id($id);
if (!$old) throw new RuntimeException('User not found.');
if (($old['role'] ?? '') === 'admin' && $role !== 'admin' && bg_admin_count($pdo) <= 1) throw new RuntimeException('At least one administrator must remain.');
if ($password !== '') {
if (strlen((string)$password) < 8) throw new InvalidArgumentException('Password must be at least 8 characters.');
$stmt = $pdo->prepare('UPDATE users SET username = ?, email = ?, role = ?, password_hash = ? WHERE id = ?');
return $stmt->execute([$username, $email, $role, password_hash($password, PASSWORD_DEFAULT), $id]);
}
$stmt = $pdo->prepare('UPDATE users SET username = ?, email = ?, role = ? WHERE id = ?');
return $stmt->execute([$username, $email, $role, $id]);
}
function bg_delete_user($id) {
$id = (int)$id;
if ($id <= 0) throw new InvalidArgumentException('Invalid user.');
$pdo = bg_user_pdo();
bg_users_schema($pdo);
$old = bg_user_by_id($id);
if (!$old) throw new RuntimeException('User not found.');
if (($old['role'] ?? '') === 'admin' && bg_admin_count($pdo) <= 1) throw new RuntimeException('At least one administrator must remain.');
if (($old['username'] ?? '') === bg_current_user()) throw new RuntimeException('You cannot delete the account currently in use.');
$stmt = $pdo->prepare('DELETE FROM users WHERE id = ?');
return $stmt->execute([$id]);
}
function bg_admin_emails() {
try {
$pdo = bg_user_pdo();
bg_users_schema($pdo);
$stmt = $pdo->query("SELECT email FROM users WHERE role = 'admin' AND email <> '' ORDER BY username ASC");
$rows = $stmt ? $stmt->fetchAll() : [];
$out = [];
foreach ($rows as $r) {
$email = trim((string)($r['email'] ?? ''));
if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL) && !in_array($email, $out, true)) $out[] = $email;
}
return $out;
} catch (Throwable $e) {
return [];
}
}
?>