197 lines
9.9 KiB
PHP
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 [];
|
|
}
|
|
}
|
|
|
|
?>
|