Import postgresql plugin

This commit is contained in:
Marek Miklewicz
2026-07-04 16:00:04 +02:00
commit ddc971567f
74 changed files with 18735 additions and 0 deletions
+216
View File
@@ -0,0 +1,216 @@
<?php
declare(strict_types=1);
return static function (AppContext $ctx): void {
$ok = [];
$errors = [];
$daUser = $ctx->daUser->username();
$prefix = $ctx->daUser->prefix();
$dbNames = array_map(static fn(array $db): string => $db['name'], $ctx->pg->listDatabasesForUser($daUser));
$roleNames = array_map(static fn(array $r): string => $r['name'], $ctx->pg->listRolesForUser($daUser));
if (empty($dbNames)) {
$ctx->renderPage('Zarządzaj bazą danych', '<div class="panel"><p class="muted">Brak baz danych do zarządzania.</p></div>', $ok, $errors);
return;
}
$selectedDb = $ctx->postString('db_name');
if ($selectedDb === '') {
$selectedDb = $ctx->queryString('db');
}
if ($selectedDb === '') {
$selectedDb = $dbNames[0];
}
try {
$selectedDb = NamePolicy::normalizeDatabaseName($selectedDb, $prefix);
} catch (Throwable $e) {
$selectedDb = $dbNames[0];
}
if (!in_array($selectedDb, $dbNames, true)) {
$selectedDb = $dbNames[0];
}
$selectedRole = $ctx->postString('role_name');
if ($selectedRole === '') {
$selectedRole = $ctx->queryString('role');
}
if ($ctx->isPost()) {
try {
$ctx->requireCsrf('modify_privileges_submit');
$syncHbaAfterChange = false;
$selectedDb = NamePolicy::normalizeDatabaseName($ctx->postString('db_name', $selectedDb), $prefix);
if (!in_array($selectedDb, $dbNames, true)) {
throw new RuntimeException('Baza nie należy do konta DA: ' . $selectedDb);
}
$action = $ctx->postString('form_action', 'save_privileges');
if ($action === 'revoke_user') {
$role = NamePolicy::normalizeRoleName($ctx->postString('role_name'), $prefix);
if (!in_array($role, $roleNames, true)) {
throw new RuntimeException('Nieprawidłowy użytkownik PostgreSQL: ' . $role);
}
$ctx->pg->revokePrivileges($selectedDb, $role);
$ctx->pg->deleteRoleHosts($daUser, $role, $selectedDb);
$syncHbaAfterChange = true;
$selectedRole = $role;
$ok[] = 'Odebrano dostęp użytkownikowi ' . $role . ' do bazy ' . $selectedDb . '.';
} elseif ($action === 'grant_user') {
$role = NamePolicy::normalizeRoleName($ctx->postString('role_name'), $prefix);
if (!in_array($role, $roleNames, true)) {
throw new RuntimeException('Nieprawidłowy użytkownik PostgreSQL: ' . $role);
}
$privileges = NamePolicy::sanitizePrivileges($ctx->postArray('grant_privileges'));
if (empty($privileges)) {
$privileges = NamePolicy::defaultPrivileges();
}
$ctx->pg->grantPrivileges($selectedDb, $role, $privileges);
$syncHbaAfterChange = true;
$selectedRole = $role;
$ok[] = 'Przyznano dostęp użytkownikowi ' . $role . ' do bazy ' . $selectedDb . '.';
} else {
$role = NamePolicy::normalizeRoleName($ctx->postString('role_name'), $prefix);
if (!in_array($role, $roleNames, true)) {
throw new RuntimeException('Nieprawidłowy użytkownik PostgreSQL: ' . $role);
}
$privileges = NamePolicy::sanitizePrivileges($ctx->postArray('privileges'));
if (empty($privileges)) {
throw new RuntimeException('Wybierz przynajmniej jedno uprawnienie.');
}
$ctx->pg->grantPrivileges($selectedDb, $role, $privileges);
$syncHbaAfterChange = true;
$selectedRole = $role;
$ok[] = 'Zaktualizowano uprawnienia użytkownika ' . $role . '.';
}
if ($syncHbaAfterChange) {
$sync = $ctx->pg->syncHbaRules();
if (!$sync['ok']) {
$errors[] = 'Uprawnienia zapisane, ale synchronizacja pg_hba nie powiodła się: ' . $sync['output'];
} else {
$ok[] = 'Synchronizacja pg_hba zakończona.';
}
}
} catch (Throwable $e) {
$errors[] = $e->getMessage();
}
}
$dbInfo = $ctx->pg->getDatabaseInfo($selectedDb);
$dbStats = $ctx->pg->getDatabaseObjectStats($selectedDb);
$dbUsers = $ctx->pg->listDatabaseUsers($daUser, $selectedDb);
if ($selectedRole !== '') {
try {
$selectedRole = NamePolicy::normalizeRoleName($selectedRole, $prefix);
} catch (Throwable $e) {
$selectedRole = '';
}
}
if ($selectedRole === '' && !empty($dbUsers)) {
$selectedRole = $dbUsers[0];
}
$currentProfile = [];
if ($selectedRole !== '') {
$currentProfile = $ctx->pg->getGrantProfile($selectedDb, $selectedRole);
if (empty($currentProfile) && in_array($selectedRole, $dbUsers, true)) {
$currentProfile = ['ALL'];
}
}
$dbOptions = '';
foreach ($dbNames as $dbName) {
$sel = ($dbName === $selectedDb) ? ' selected' : '';
$dbOptions .= '<option value="' . AppContext::e($dbName) . '"' . $sel . '>' . AppContext::e($dbName) . '</option>';
}
$assignableRoles = array_values(array_diff($roleNames, $dbUsers));
$assignOptions = '<option value="">-- wybierz nowego użytkownika --</option>';
foreach ($assignableRoles as $role) {
$assignOptions .= '<option value="' . AppContext::e($role) . '">' . AppContext::e($role) . '</option>';
}
$usersRows = '';
foreach ($dbUsers as $role) {
$profile = $ctx->pg->getGrantProfile($selectedDb, $role);
$label = (empty($profile) || in_array('ALL', $profile, true))
? 'Pełny dostęp'
: implode(', ', $profile);
$usersRows .= '<tr>';
$usersRows .= '<td>' . AppContext::e($role) . '</td>';
$usersRows .= '<td><span class="badge">' . AppContext::e($label) . '</span></td>';
$usersRows .= '<td class="actions">';
$usersRows .= '<a class="btn" href="' . AppContext::e($ctx->url('change_password.html', ['role' => $role])) . '">Zarządzaj</a>';
$usersRows .= '<a class="btn" href="' . AppContext::e($ctx->url('modify_privileges.html', ['db' => $selectedDb, 'role' => $role])) . '#przywileje">Przywileje</a>';
$usersRows .= '<form method="post" style="display:inline-flex;gap:6px;margin:0">';
$usersRows .= $ctx->csrfField('modify_privileges_submit');
$usersRows .= '<input type="hidden" name="db_name" value="' . AppContext::e($selectedDb) . '">';
$usersRows .= '<input type="hidden" name="role_name" value="' . AppContext::e($role) . '">';
$usersRows .= '<input type="hidden" name="form_action" value="revoke_user">';
$usersRows .= '<button class="btn danger" type="submit">Odbierz dostęp</button>';
$usersRows .= '</form>';
$usersRows .= '</td>';
$usersRows .= '</tr>';
}
if ($usersRows === '') {
$usersRows = '<tr><td colspan="3" class="muted">Brak użytkowników z dostępem do bazy.</td></tr>';
}
$privCheckboxes = '';
foreach (NamePolicy::PRIVILEGE_LABELS as $code => $label) {
$checked = in_array($code, $currentProfile, true) ? ' checked' : '';
$privCheckboxes .= '<label class="inline"><input type="checkbox" name="privileges[]" value="' . AppContext::e($code) . '"' . $checked . '> ' . AppContext::e($label) . '</label>';
}
$body = '';
$body .= '<section class="panel">';
$body .= '<h3>Zarządzaj bazą danych</h3>';
$body .= '<form method="get" action="' . AppContext::e($ctx->url('modify_privileges.html')) . '" class="actions" style="justify-content:flex-end">';
$body .= '<label style="margin:0">Baza: <select name="db" style="width:auto;display:inline-block;margin-left:8px">' . $dbOptions . '</select></label>';
$body .= '<button class="btn" type="submit">Przełącz</button>';
$body .= '</form>';
$body .= '<p class="section-text">Szczegółowe informacje o bazie danych.</p>';
$body .= '<table><tbody>';
$body .= '<tr><td><strong>Nazwa bazy danych</strong><br>' . AppContext::e($dbInfo['name']) . '</td><td><strong>Domyślne kodowanie znaków</strong><br>' . AppContext::e($dbInfo['encoding']) . '</td><td><strong>Domyślne porównywanie znaków</strong><br>' . AppContext::e($dbInfo['collation']) . '</td></tr>';
$body .= '<tr><td><strong>Rozmiar</strong><br>' . AppContext::e($dbInfo['size']) . '</td><td><strong>Użytkownicy</strong><br>' . AppContext::e((string)count($dbUsers)) . '</td><td><strong>Liczba Tabel</strong><br>' . AppContext::e((string)$dbStats['tables']) . '</td></tr>';
$body .= '<tr><td><strong>Widoki</strong><br>' . AppContext::e((string)$dbStats['views']) . '</td><td><strong>Wyzwalacze</strong><br>' . AppContext::e((string)$dbStats['triggers']) . '</td><td><strong>Rutyny i procedury</strong><br>' . AppContext::e((string)$dbStats['routines']) . '</td></tr>';
$body .= '</tbody></table>';
$body .= '</section>';
$body .= '<section class="panel" style="margin-top:14px">';
$body .= '<h3>Dostęp użytkownika</h3>';
$body .= '<p class="section-text">Lista użytkowników, którzy mają dostęp do tej bazy danych wraz z podsumowaniem uprawnień.</p>';
$body .= '<table><thead><tr><th>Nazwa użytkownika</th><th>Przywileje</th><th>Akcje</th></tr></thead><tbody>' . $usersRows . '</tbody></table>';
$body .= '<form method="post" style="margin-top:10px">';
$body .= $ctx->csrfField('modify_privileges_submit');
$body .= '<input type="hidden" name="db_name" value="' . AppContext::e($selectedDb) . '">';
$body .= '<input type="hidden" name="form_action" value="grant_user">';
$body .= '<label>Przyznaj dostęp kolejnemu użytkownikowi</label>';
$body .= '<select name="role_name">' . $assignOptions . '</select>';
$body .= '<input type="hidden" name="grant_privileges[]" value="ALL">';
$body .= '<div class="actions"><button class="btn" type="submit">+ Przyznaj pełny dostęp</button></div>';
$body .= '</form>';
$body .= '</section>';
if ($selectedRole !== '') {
$body .= '<section class="panel" id="przywileje" style="margin-top:14px">';
$body .= '<h3>Przywileje użytkownika: ' . AppContext::e($selectedRole) . '</h3>';
$body .= '<form method="post">';
$body .= $ctx->csrfField('modify_privileges_submit');
$body .= '<input type="hidden" name="db_name" value="' . AppContext::e($selectedDb) . '">';
$body .= '<input type="hidden" name="role_name" value="' . AppContext::e($selectedRole) . '">';
$body .= '<input type="hidden" name="form_action" value="save_privileges">';
$body .= '<div class="privileges-group" data-privileges-group>' . $privCheckboxes . '</div>';
$body .= '<div class="actions"><button class="primary" type="submit">Zapisz zmiany</button></div>';
$body .= '</form>';
$body .= '</section>';
}
$ctx->renderPage('Zarządzaj bazą danych', $body, $ok, $errors);
};