daUser->username(); $prefix = $ctx->daUser->prefix(); $showRemoteHosts = $ctx->settings->allowRemoteHosts(); $postgresPort = $ctx->pg->serverPort(); $passwordChangedFor = ''; $changedPassword = ''; $createdRoleName = ''; $createdRolePassword = ''; $createdRoleAssignedDbs = []; $showCreateUserForm = (strtolower(trim($ctx->queryString('add_user'))) === '1'); $deletePromptRoleRaw = trim($ctx->postString('delete_role')); if ($deletePromptRoleRaw === '') { $deletePromptRoleRaw = trim($ctx->queryString('delete_role')); } $deletePromptRole = ''; $deletePromptAssignedDbs = []; $createRoleSuffixInput = trim($ctx->postString('create_role_name')); if (strpos($createRoleSuffixInput, $prefix) === 0) { $createRoleSuffixInput = substr($createRoleSuffixInput, strlen($prefix)); } $createPasswordInput = $ctx->postString('create_password'); $createSelectedDbsRaw = $ctx->postArray('create_user_databases'); $createSelectedDbs = array_values(array_unique(array_filter($createSelectedDbsRaw, static fn(string $db): bool => $db !== ''))); $databases = $ctx->pg->listDatabasesForUser($daUser); $allDbs = array_map(static fn(array $db): string => $db['name'], $databases); $roles = $ctx->pg->listRolesForUser($daUser); $roleNames = array_map(static fn(array $r): string => $r['name'], $roles); $preferredRoleRaw = $ctx->postString('role_name'); if ($preferredRoleRaw === '') { $preferredRoleRaw = $ctx->queryString('role'); } if ($ctx->isPost()) { $postAction = $ctx->postString('form_action'); try { if ($postAction === 'create_user_inline') { $ctx->requireCsrf('create_user_inline_submit'); $showCreateUserForm = true; $newRole = NamePolicy::normalizeRoleName($ctx->postString('create_role_name'), $prefix); if ($ctx->pg->roleExists($newRole)) { throw new RuntimeException('Użytkownik PostgreSQL już istnieje: ' . $newRole); } $newPassword = $ctx->postString('create_password'); if (strlen($newPassword) < 12) { throw new RuntimeException('Hasło użytkownika musi mieć co najmniej 12 znaków.'); } $selectedDbs = []; foreach ($createSelectedDbs as $rawDbName) { $dbName = NamePolicy::normalizeDatabaseName($rawDbName, $prefix); if (!in_array($dbName, $allDbs, true)) { throw new RuntimeException('Baza nie należy do konta DA: ' . $dbName); } if (!in_array($dbName, $selectedDbs, true)) { $selectedDbs[] = $dbName; } } $ctx->pg->createRole($newRole, $newPassword); try { $ctx->pg->replaceRoleHostsWithNotes($daUser, $newRole, [['host' => 'localhost', 'note' => '']]); foreach ($selectedDbs as $dbName) { $ctx->pg->grantPrivileges($dbName, $newRole, NamePolicy::defaultPrivileges()); } $sync = $ctx->pg->syncHbaRules(); if (!$sync['ok']) { $errors[] = 'Użytkownik został utworzony, ale synchronizacja pg_hba nie powiodła się: ' . $sync['output']; } $createdRoleName = $newRole; $createdRolePassword = $newPassword; $createdRoleAssignedDbs = $selectedDbs; if (empty($selectedDbs)) { $ok[] = 'Utworzono użytkownika ' . $newRole . ' bez przypisanych baz danych.'; } else { $ok[] = 'Utworzono użytkownika ' . $newRole . ' i przypisano do ' . count($selectedDbs) . ' baz(y).'; } $preferredRoleRaw = $newRole; $showCreateUserForm = false; $createRoleSuffixInput = ''; $createPasswordInput = ''; $createSelectedDbs = []; } catch (Throwable $inner) { try { $ctx->pg->deleteRoleHosts($daUser, $newRole); } catch (Throwable $ignored) { } try { $ctx->pg->dropRole($newRole); } catch (Throwable $ignored) { } throw $inner; } } elseif ($postAction === 'delete_user_confirm') { $ctx->requireCsrf('delete_user_submit'); $roleToDelete = NamePolicy::normalizeRoleName($ctx->postString('delete_role'), $prefix); if (!in_array($roleToDelete, $roleNames, true)) { throw new RuntimeException('Użytkownik PostgreSQL nie należy do konta DA: ' . $roleToDelete); } $owned = array_values(array_unique($ctx->pg->roleOwnedDatabases($roleToDelete, $daUser))); if (!empty($owned)) { throw new RuntimeException( 'Nie można usunąć użytkownika, ponieważ jest właścicielem baz: ' . implode(', ', $owned) ); } $assigned = array_values(array_unique($ctx->pg->roleAssignedDatabases($roleToDelete, $daUser))); foreach ($assigned as $dbName) { $owner = $ctx->pg->getDatabaseOwner($dbName); if ($owner !== null && $owner !== $roleToDelete) { $ctx->pg->reassignOwnedObjects($dbName, $roleToDelete, $owner); } $ctx->pg->revokePrivileges($dbName, $roleToDelete); } $ctx->pg->deleteRoleHosts($daUser, $roleToDelete); $ctx->pg->dropRole($roleToDelete); $sync = $ctx->pg->syncHbaRules(); if (!$sync['ok']) { $errors[] = 'Usuwanie wykonane, ale synchronizacja pg_hba nie powiodła się: ' . $sync['output']; } $ok[] = 'Usunięto użytkownika ' . $roleToDelete . '.'; if ($preferredRoleRaw === $roleToDelete) { $preferredRoleRaw = ''; } $deletePromptRoleRaw = ''; } else { $ctx->requireCsrf('user_manage_submit'); if (empty($roleNames)) { throw new RuntimeException('Brak użytkowników PostgreSQL do zarządzania.'); } $selectedRole = NamePolicy::normalizeRoleName($ctx->postString('role_name', $preferredRoleRaw), $prefix); if (!in_array($selectedRole, $roleNames, true)) { throw new RuntimeException('Użytkownik PostgreSQL nie należy do konta DA: ' . $selectedRole); } $preferredRoleRaw = $selectedRole; if ($postAction === 'change_password') { $password = $ctx->postString('password'); if (strlen($password) < 12) { throw new RuntimeException('Nowe hasło musi mieć minimum 12 znaków.'); } $ctx->pg->changeRolePassword($selectedRole, $password); $passwordChangedFor = $selectedRole; $changedPassword = $password; } elseif ($postAction === 'grant_db') { $dbName = NamePolicy::normalizeDatabaseName($ctx->postString('db_name'), $prefix); if (!in_array($dbName, $allDbs, true)) { throw new RuntimeException('Baza nie należy do konta DA: ' . $dbName); } $privileges = NamePolicy::sanitizePrivileges($ctx->postArray('privileges')); if (empty($privileges)) { $privileges = NamePolicy::defaultPrivileges(); } $ctx->pg->grantPrivileges($dbName, $selectedRole, $privileges); $ok[] = 'Przyznano dostęp do bazy ' . $dbName . ' użytkownikowi ' . $selectedRole . '.'; } elseif ($postAction === 'revoke_db') { $dbName = NamePolicy::normalizeDatabaseName($ctx->postString('db_name'), $prefix); $ctx->pg->revokePrivileges($dbName, $selectedRole); $ctx->pg->deleteRoleHosts($daUser, $selectedRole, $dbName); $ok[] = 'Odebrano dostęp do bazy ' . $dbName . ' użytkownikowi ' . $selectedRole . '.'; } elseif ($postAction === 'add_host' && $showRemoteHosts) { $dbName = NamePolicy::normalizeDatabaseName($ctx->postString('db_name'), $prefix); if (!in_array($dbName, $allDbs, true)) { throw new RuntimeException('Baza nie należy do konta DA: ' . $dbName); } if (!in_array($selectedRole, $ctx->pg->listDatabaseUsers($daUser, $dbName), true)) { throw new RuntimeException('Użytkownik PostgreSQL nie ma dostępu do wybranej bazy: ' . $selectedRole); } $allowAllHosts = $ctx->postString('allow_all_hosts') === '1'; $host = $allowAllHosts ? NamePolicy::ALL_IPV4_HOST_PATTERN : NamePolicy::normalizeRemoteIpv4AccessPattern($ctx->postString('host')); $note = NamePolicy::normalizeHostComment($ctx->postString('host_note')); if ($allowAllHosts && $note === '') { $note = 'Dostęp z każdego adresu IPv4'; } $entries = $ctx->pg->getRoleHostEntries($daUser, $selectedRole, $dbName); $map = []; foreach ($entries as $entry) { $map[$entry['host']] = $entry['note']; } $map[$host] = $note; if (count($map) > $ctx->settings->maxHostsPerUser()) { throw new RuntimeException('Przekroczono limit hostów dla wybranej bazy i użytkownika PostgreSQL.'); } $save = []; foreach ($map as $h => $n) { $save[] = ['host' => $h, 'note' => $n]; } $ctx->pg->replaceRoleHostsWithNotes($daUser, $selectedRole, $save, $dbName); $restartPostgresql = $ctx->postString('restart_postgresql') === '1'; $sync = $ctx->pg->syncHbaRules($restartPostgresql); if (!$sync['ok']) { $errors[] = 'Host dodany, ale synchronizacja pg_hba nie powiodła się: ' . $sync['output']; } elseif ($restartPostgresql) { $ok[] = 'Synchronizacja konfiguracji PostgreSQL zakończona i usługa PostgreSQL została zrestartowana.'; } $ok[] = 'Dodano host ' . $host . ' dla użytkownika ' . $selectedRole . ' i bazy ' . $dbName . '.'; } elseif ($postAction === 'remove_host' && $showRemoteHosts) { $dbName = NamePolicy::normalizeDatabaseName($ctx->postString('db_name'), $prefix); if (!in_array($dbName, $allDbs, true)) { throw new RuntimeException('Baza nie należy do konta DA: ' . $dbName); } $host = trim($ctx->postString('host')); $entries = $ctx->pg->getRoleHostEntries($daUser, $selectedRole, $dbName); $save = []; foreach ($entries as $entry) { if ($entry['host'] === $host) { continue; } $save[] = $entry; } $ctx->pg->replaceRoleHostsWithNotes($daUser, $selectedRole, $save, $dbName); $sync = $ctx->pg->syncHbaRules(); if (!$sync['ok']) { $errors[] = 'Host usunięty, ale synchronizacja pg_hba nie powiodła się: ' . $sync['output']; } $ok[] = 'Usunięto host ' . $host . ' dla użytkownika ' . $selectedRole . ' i bazy ' . $dbName . '.'; } } } catch (Throwable $e) { $errors[] = $e->getMessage(); if ($postAction === 'create_user_inline') { $showCreateUserForm = true; } if ($postAction === 'delete_user_confirm') { $deletePromptRoleRaw = trim($ctx->postString('delete_role')); } } } $databases = $ctx->pg->listDatabasesForUser($daUser); $allDbs = array_map(static fn(array $db): string => $db['name'], $databases); $roles = $ctx->pg->listRolesForUser($daUser); $roleNames = array_map(static fn(array $r): string => $r['name'], $roles); $selectedRole = ''; if (!empty($roleNames)) { $candidateRaw = $preferredRoleRaw !== '' ? $preferredRoleRaw : $roleNames[0]; try { $candidate = NamePolicy::normalizeRoleName($candidateRaw, $prefix); } catch (Throwable $e) { $candidate = $roleNames[0]; } if (!in_array($candidate, $roleNames, true)) { $candidate = $roleNames[0]; } $selectedRole = $candidate; } if ($deletePromptRoleRaw !== '') { try { $deletePromptRole = NamePolicy::normalizeRoleName($deletePromptRoleRaw, $prefix); if (!in_array($deletePromptRole, $roleNames, true)) { throw new RuntimeException('Użytkownik PostgreSQL nie należy do konta DA: ' . $deletePromptRole); } $deletePromptAssignedDbs = array_values(array_unique($ctx->pg->roleAssignedDatabases($deletePromptRole, $daUser))); } catch (Throwable $e) { $errors[] = $e->getMessage(); $deletePromptRole = ''; $deletePromptAssignedDbs = []; } } $createSelectedDbSet = []; foreach ($createSelectedDbs as $rawDbName) { try { $dbName = NamePolicy::normalizeDatabaseName($rawDbName, $prefix); } catch (Throwable $e) { continue; } if (in_array($dbName, $allDbs, true)) { $createSelectedDbSet[$dbName] = true; } } $roleOptions = ''; foreach ($roleNames as $roleName) { $sel = ($roleName === $selectedRole) ? ' selected' : ''; $roleOptions .= ''; } $assignedDbs = []; $grantableDbs = $allDbs; if ($selectedRole !== '') { $assignedDbs = $ctx->pg->roleAssignedDatabases($selectedRole, $daUser); $grantableDbs = array_values(array_diff($allDbs, $assignedDbs)); } $dbOptions = ''; foreach ($grantableDbs as $dbName) { $dbOptions .= ''; } $assignedDbOptions = ''; foreach ($assignedDbs as $dbName) { $assignedDbOptions .= ''; } $dbRows = ''; foreach ($assignedDbs as $dbName) { $profile = $ctx->pg->getGrantProfile($dbName, $selectedRole); $label = (empty($profile) || in_array('ALL', $profile, true)) ? 'Pełny dostęp' : implode(', ', $profile); $dbRows .= ''; $dbRows .= '' . AppContext::e($dbName) . ''; $dbRows .= '' . AppContext::e($label) . ''; $dbRows .= ''; $dbRows .= 'Zarządzaj'; $dbRows .= 'Przywileje'; $dbRows .= '
'; $dbRows .= $ctx->csrfField('user_manage_submit'); $dbRows .= ''; $dbRows .= ''; $dbRows .= ''; $dbRows .= ''; $dbRows .= '
'; $dbRows .= ''; $dbRows .= ''; } if ($dbRows === '') { $dbRows = 'Brak przypisanych baz danych.'; } $hostsRows = ''; $hostsCount = 0; $hasAllHostsEntry = false; if ($showRemoteHosts && $selectedRole !== '') { foreach ($assignedDbs as $dbName) { $hostEntries = $ctx->pg->getRoleHostEntries($daUser, $selectedRole, $dbName); $hostsCount += count($hostEntries); foreach ($hostEntries as $entry) { $host = $entry['host']; $note = $entry['note']; if ($host === NamePolicy::ALL_IPV4_HOST_PATTERN) { $hasAllHostsEntry = true; } $hostsRows .= ''; $hostsRows .= '' . AppContext::e($dbName) . ''; $hostsRows .= '' . AppContext::e($host) . ''; $hostsRows .= '' . AppContext::e($note) . ''; $hostsRows .= ''; $hostsRows .= '
'; $hostsRows .= $ctx->csrfField('user_manage_submit'); $hostsRows .= ''; $hostsRows .= ''; $hostsRows .= ''; $hostsRows .= ''; $hostsRows .= ''; $hostsRows .= '
'; $hostsRows .= ''; $hostsRows .= ''; } } if ($hostsRows === '') { $hostsRows = 'Brak dodatkowych hostów dla przypisanych baz.'; } } $endpoint = $ctx->pg->connectionEndpoint(); $endpointHost = trim($endpoint['host']) !== '' ? $endpoint['host'] : 'localhost'; $endpointPort = $endpoint['port'] > 0 ? (string)$endpoint['port'] : '5432'; $createdUserCard = ''; if ($createdRoleName !== '' && $createdRolePassword !== '') { $assignedDbsHtml = ''; if (!empty($createdRoleAssignedDbs)) { $assignedItems = ''; foreach ($createdRoleAssignedDbs as $assignedDbName) { $assignedItems .= '
  • ' . AppContext::e($assignedDbName) . '
  • '; } $assignedDbsHtml .= '
    Przypisane bazy danych:
    '; $assignedDbsHtml .= '
    '; } $createdUserCard .= '
    '; $createdUserCard .= '
    '; $createdUserCard .= '
    '; $createdUserCard .= '

    Dane dostępowe użytkownika PostgreSQL

    '; $createdUserCard .= '
    '; $createdUserCard .= '
    Nazwa hosta:
    ' . AppContext::e($endpointHost) . ' (Port: ' . AppContext::e($endpointPort) . ')
    '; $createdUserCard .= '
    Nazwa użytkownika:
    ' . AppContext::e($createdRoleName) . '
    '; $createdUserCard .= '
    Hasło:
    ' . AppContext::e($createdRolePassword) . '
    '; $createdUserCard .= $assignedDbsHtml; $createdUserCard .= '
    '; } $passwordChangedCard = ''; if ($passwordChangedFor !== '' && $changedPassword !== '') { $passwordChangedCard .= '
    '; $passwordChangedCard .= '
    '; $passwordChangedCard .= '
    '; $passwordChangedCard .= '

    Hasło zostało zmienione

    '; $passwordChangedCard .= '
    '; $passwordChangedCard .= '
    Nazwa hosta:
    ' . AppContext::e($endpointHost) . ' (Port: ' . AppContext::e($endpointPort) . ')
    '; $passwordChangedCard .= '
    Nazwa użytkownika:
    ' . AppContext::e($passwordChangedFor) . '
    '; $passwordChangedCard .= '
    Hasło:
    ' . AppContext::e($changedPassword) . '
    '; $passwordChangedCard .= '
    '; } $body = ''; $body .= $createdUserCard; $body .= $passwordChangedCard; $body .= '
    '; $body .= '

    Zarządzaj użytkownikami

    '; $body .= '
    '; $body .= '
    '; $body .= 'Dodaj użytkownika'; if ($showCreateUserForm) { $body .= 'Anuluj'; } $body .= '
    '; if ($selectedRole !== '') { $body .= '
    '; $body .= '
    '; $body .= ''; $body .= ''; $body .= '
    '; $body .= 'Usuń użytkownika'; $body .= '
    '; } else { $body .= 'Brak użytkowników PostgreSQL. Dodaj pierwszego użytkownika.'; } $body .= '
    '; $body .= '

    Szczegółowe informacje o użytkowniku.

    '; if ($selectedRole !== '') { $body .= ''; $body .= ''; if ($showRemoteHosts) { $body .= ''; } else { $body .= ''; } $body .= '
    Nazwa użytkownika
    ' . AppContext::e($selectedRole) . '
    Bazy danych
    ' . AppContext::e((string)count($assignedDbs)) . '
    Dozwolone hosty
    ' . AppContext::e((string)$hostsCount) . '
    Dozwolone hosty
    localhost
    '; } else { $body .= '

    Aby zarządzać użytkownikami najpierw utwórz użytkownika PostgreSQL.

    '; } $body .= '
    '; if ($deletePromptRole !== '') { $cancelUrl = $ctx->url('change_password.html', $selectedRole !== '' ? ['role' => $selectedRole] : []); $body .= '
    '; $body .= '
    Czy na pewno chcesz usunąć użytkownika ' . AppContext::e($deletePromptRole) . '?
    '; if (count($deletePromptAssignedDbs) === 1) { $body .= '
    Uwaga użytkownik ' . AppContext::e($deletePromptRole) . ' jest przypisany do następującej bazy danych ' . AppContext::e($deletePromptAssignedDbs[0]) . '.
    '; } elseif (count($deletePromptAssignedDbs) > 1) { $body .= '
    Uwaga użytkownik ' . AppContext::e($deletePromptRole) . ' jest przypisany do następujących baz danych:
    '; $body .= ''; } $body .= '
    '; $body .= $ctx->csrfField('delete_user_submit'); $body .= ''; $body .= ''; $body .= '
    '; $body .= ''; $body .= 'Anuluj'; $body .= '
    '; $body .= '
    '; $body .= '
    '; } if ($showCreateUserForm) { $body .= '
    '; $body .= '

    Utwórz nowego użytkownika PostgreSQL

    '; $body .= '
    '; $body .= $ctx->csrfField('create_user_inline_submit'); $body .= ''; $body .= '
    '; $body .= '
    ' . AppContext::e($prefix) . '
    '; $body .= '
    '; $body .= '
    '; $body .= ''; $body .= ''; $body .= ''; $body .= '
    '; $body .= '
    '; $body .= '
    '; $body .= '
    '; $body .= 'Przypisz do baz danych (opcjonalnie)'; if (empty($allDbs)) { $body .= '

    Brak baz do przypisania.

    '; } else { $body .= '
    '; foreach ($allDbs as $dbName) { $checked = isset($createSelectedDbSet[$dbName]) ? ' checked' : ''; $body .= ''; } $body .= '
    '; } $body .= '

    Możesz pozostawić użytkownika bez przypisania do baz i nadać dostęp później.

    '; $body .= '
    '; $body .= '
    '; $body .= '
    '; $body .= '
    '; } if ($selectedRole !== '') { $body .= '
    '; $body .= '

    Zarządzanie hasłami

    '; $body .= '
    '; $body .= $ctx->csrfField('user_manage_submit'); $body .= ''; $body .= ''; $body .= ''; $body .= '
    '; $body .= ''; $body .= ''; $body .= ''; $body .= '
    '; $body .= '
    '; $body .= '
    '; $body .= '
    '; $body .= '
    '; $body .= '

    Dostęp do baz danych

    '; $body .= '' . $dbRows . '
    Nazwa bazy danychPrzywilejeAkcje
    '; $body .= '
    '; $body .= $ctx->csrfField('user_manage_submit'); $body .= ''; $body .= ''; $body .= ''; $body .= ''; $body .= ''; $body .= '
    '; $body .= '
    '; $body .= '
    '; if ($showRemoteHosts) { $body .= '
    '; $body .= '

    Dozwolone hosty

    '; if ($hasAllHostsEntry) { $body .= '
    Dla co najmniej jednej bazy włączono dostęp z każdego adresu IPv4. Plugin nie otwiera portu PostgreSQL w CSF dla tego trybu. Port PostgreSQL do otwarcia w CSF: ' . AppContext::e((string)$postgresPort) . '.
    '; } $body .= '

    Lista źródłowych adresów IP/CIDR, które mogą łączyć się z konkretną bazą przy użyciu poświadczeń użytkownika.

    '; $body .= '' . $hostsRows . '
    BazaHost/CIDRKomentarzAkcje
    '; $body .= '
    '; $body .= $ctx->csrfField('user_manage_submit'); $body .= ''; $body .= ''; $body .= '
    '; $body .= '
    '; $body .= '
    '; $body .= '
    '; $body .= '
    '; $body .= '
    '; $body .= ''; $body .= ''; $body .= '
    '; $body .= '

    Ten checkbox ignoruje pole IPv4/CIDR i wymaga ręcznego otwarcia portu PostgreSQL w CSF. Port PostgreSQL do otwarcia w CSF: ' . AppContext::e((string)$postgresPort) . '.

    '; $body .= '
    '; $body .= '
    '; $body .= '
    '; } } $ctx->renderPage('Zarządzaj użytkownikami', $body, $ok, $errors); };