diff --git a/public/admin/users/index.php b/public/admin/users/index.php index 03ccc974..b5f9a79e 100644 --- a/public/admin/users/index.php +++ b/public/admin/users/index.php @@ -5,12 +5,26 @@ declare(strict_types=1); require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; use App\ConfigLoader; +use App\AccountGate; use App\RegistrationService; session_start(); $projectRoot = dirname(__DIR__, 3); +$accountGate = new AccountGate(ConfigLoader::load($projectRoot, 'registration')); $registration = new RegistrationService($projectRoot, ConfigLoader::load($projectRoot, 'registration')); + +if (isset($_SESSION['desktop_auth']) && is_array($_SESSION['desktop_auth'])) { + $currentAuthUser = is_array($_SESSION['desktop_auth']['user'] ?? null) ? $_SESSION['desktop_auth']['user'] : []; + $accountCheck = $accountGate->checkUsername((string) ($currentAuthUser['username'] ?? '')); + + if (!($accountCheck['allowed'] ?? false)) { + unset($_SESSION['desktop_auth']); + header('Location: /auth/inactive/?message=' . urlencode((string) ($accountCheck['message'] ?? 'Dieses Konto ist noch nicht freigeschaltet.')), true, 302); + exit; + } +} + $currentUser = is_array($_SESSION['desktop_auth']['user'] ?? null) ? $_SESSION['desktop_auth']['user'] : []; $currentGroups = array_map(static fn (string $group): string => strtolower(trim($group, '/')), array_values(array_map('strval', (array) ($currentUser['groups'] ?? [])))); diff --git a/public/api/mining-checker/index.php b/public/api/mining-checker/index.php index 1dcdea32..9b7f49c2 100644 --- a/public/api/mining-checker/index.php +++ b/public/api/mining-checker/index.php @@ -5,6 +5,7 @@ declare(strict_types=1); require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; use App\ConfigLoader; +use App\AccountGate; use App\KeycloakAuth; use MiningChecker\LegacyModuleStore; use MiningChecker\MiningCheckerUserScope; @@ -13,9 +14,20 @@ session_start(); $projectRoot = dirname(__DIR__, 3); $auth = new KeycloakAuth(ConfigLoader::load($projectRoot, 'keycloak')); +$accountGate = new AccountGate(ConfigLoader::load($projectRoot, 'registration')); header('Content-Type: application/json; charset=utf-8'); +if ($auth->isAuthenticated()) { + $currentUser = is_array($_SESSION['desktop_auth']['user'] ?? null) ? $_SESSION['desktop_auth']['user'] : []; + $accountCheck = $accountGate->checkUsername((string) ($currentUser['username'] ?? '')); + + if (!($accountCheck['allowed'] ?? false)) { + $auth->logout(); + respond(['error' => (string) ($accountCheck['message'] ?? 'Dieses Konto ist noch nicht freigeschaltet.')], 403); + } +} + if (!$auth->shouldShowDesktop()) { respond(['error' => 'Nicht autorisiert.'], 401); } diff --git a/public/api/module-auth/mining-checker/index.php b/public/api/module-auth/mining-checker/index.php index e2a7a168..4c935d5d 100644 --- a/public/api/module-auth/mining-checker/index.php +++ b/public/api/module-auth/mining-checker/index.php @@ -5,6 +5,7 @@ declare(strict_types=1); require_once dirname(__DIR__, 5) . '/src/App/bootstrap.php'; use App\ConfigLoader; +use App\AccountGate; use App\KeycloakAuth; use MiningChecker\LegacyModuleStore; use MiningChecker\MiningCheckerUserScope; @@ -13,9 +14,22 @@ session_start(); $projectRoot = dirname(__DIR__, 5); $auth = new KeycloakAuth(ConfigLoader::load($projectRoot, 'keycloak')); +$accountGate = new AccountGate(ConfigLoader::load($projectRoot, 'registration')); header('Content-Type: application/json; charset=utf-8'); +if ($auth->isAuthenticated()) { + $currentUser = is_array($_SESSION['desktop_auth']['user'] ?? null) ? $_SESSION['desktop_auth']['user'] : []; + $accountCheck = $accountGate->checkUsername((string) ($currentUser['username'] ?? '')); + + if (!($accountCheck['allowed'] ?? false)) { + $auth->logout(); + http_response_code(403); + echo json_encode(['error' => (string) ($accountCheck['message'] ?? 'Dieses Konto ist noch nicht freigeschaltet.')], JSON_UNESCAPED_UNICODE); + exit; + } +} + if (!$auth->shouldShowDesktop()) { http_response_code(401); echo json_encode(['error' => 'Nicht autorisiert.'], JSON_UNESCAPED_UNICODE); diff --git a/public/apps/mining-checker/index.php b/public/apps/mining-checker/index.php index 84675046..f31bfea5 100644 --- a/public/apps/mining-checker/index.php +++ b/public/apps/mining-checker/index.php @@ -5,12 +5,25 @@ declare(strict_types=1); require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; use App\ConfigLoader; +use App\AccountGate; use App\KeycloakAuth; session_start(); $projectRoot = dirname(__DIR__, 3); $auth = new KeycloakAuth(ConfigLoader::load($projectRoot, 'keycloak')); +$accountGate = new AccountGate(ConfigLoader::load($projectRoot, 'registration')); + +if ($auth->isAuthenticated()) { + $currentUser = is_array($_SESSION['desktop_auth']['user'] ?? null) ? $_SESSION['desktop_auth']['user'] : []; + $accountCheck = $accountGate->checkUsername((string) ($currentUser['username'] ?? '')); + + if (!($accountCheck['allowed'] ?? false)) { + $auth->logout(); + header('Location: /auth/inactive/?message=' . urlencode((string) ($accountCheck['message'] ?? 'Dieses Konto ist noch nicht freigeschaltet.')), true, 302); + exit; + } +} if (!$auth->shouldShowDesktop()) { header('Location: /auth/keycloak', true, 302); diff --git a/public/auth/callback/index.php b/public/auth/callback/index.php index dd64ffb4..07043ca4 100644 --- a/public/auth/callback/index.php +++ b/public/auth/callback/index.php @@ -5,12 +5,14 @@ declare(strict_types=1); require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; use App\ConfigLoader; +use App\AccountGate; use App\KeycloakAuth; session_start(); $projectRoot = dirname(__DIR__, 3); $auth = new KeycloakAuth(ConfigLoader::load($projectRoot, 'keycloak')); +$accountGate = new AccountGate(ConfigLoader::load($projectRoot, 'registration')); $state = (string) ($_GET['state'] ?? ''); $expectedState = (string) ($_SESSION['keycloak_oauth_state'] ?? ''); $code = (string) ($_GET['code'] ?? ''); @@ -50,9 +52,19 @@ if ($state === '' || $expectedState === '' || !hash_equals($expectedState, $stat } else { /** @var array $userPayload */ $userPayload = $userInfo['data']; - $auth->establishSession($tokenPayload, $userPayload); - header('Location: ' . $redirectTarget, true, 302); - exit; + $username = (string) ($userPayload['preferred_username'] ?? ''); + $accountCheck = $accountGate->checkUsername($username); + + if (!($accountCheck['allowed'] ?? false)) { + $auth->logout(); + http_response_code(403); + $title = 'Zugriff gesperrt'; + $message = (string) ($accountCheck['message'] ?? 'Dieses Konto ist noch nicht freigeschaltet.'); + } else { + $auth->establishSession($tokenPayload, $userPayload); + header('Location: ' . $redirectTarget, true, 302); + exit; + } } } } diff --git a/public/auth/inactive/index.php b/public/auth/inactive/index.php new file mode 100644 index 00000000..54d52c4b --- /dev/null +++ b/public/auth/inactive/index.php @@ -0,0 +1,35 @@ + + + + + + Konto nicht freigeschaltet + + + +
+ +
+ + diff --git a/public/auth/register/confirmation/index.php b/public/auth/register/confirmation/index.php index 09c3583c..3e82a2ee 100644 --- a/public/auth/register/confirmation/index.php +++ b/public/auth/register/confirmation/index.php @@ -39,8 +39,7 @@ unset($_SESSION['registration_confirmation']);
- - +
diff --git a/public/index.php b/public/index.php index 1f0153a2..6361c62f 100644 --- a/public/index.php +++ b/public/index.php @@ -5,6 +5,7 @@ declare(strict_types=1); require_once dirname(__DIR__) . '/src/App/bootstrap.php'; use App\App; +use App\AccountGate; use App\ConfigLoader; use App\KeycloakAuth; @@ -12,7 +13,20 @@ session_start(); $projectRoot = dirname(__DIR__); $keycloakConfig = ConfigLoader::load($projectRoot, 'keycloak'); +$registrationConfig = ConfigLoader::load($projectRoot, 'registration'); $auth = new KeycloakAuth($keycloakConfig); +$accountGate = new AccountGate($registrationConfig); + +if ($auth->isAuthenticated()) { + $currentUser = is_array($_SESSION['desktop_auth']['user'] ?? null) ? $_SESSION['desktop_auth']['user'] : []; + $accountCheck = $accountGate->checkUsername((string) ($currentUser['username'] ?? '')); + + if (!($accountCheck['allowed'] ?? false)) { + $auth->logout(); + header('Location: /auth/inactive/?message=' . urlencode((string) ($accountCheck['message'] ?? 'Dieses Konto ist noch nicht freigeschaltet.')), true, 302); + exit; + } +} if (!$auth->shouldShowDesktop()) { if ($auth->isConfigured()) { diff --git a/src/App/AccountGate.php b/src/App/AccountGate.php new file mode 100644 index 00000000..1733fc13 --- /dev/null +++ b/src/App/AccountGate.php @@ -0,0 +1,65 @@ + $config + */ + public function __construct( + private readonly array $config, + ) { + $this->ldap = new LdapProvisioner($config); + } + + /** + * @return array{allowed: bool, message: string} + */ + public function checkUsername(string $username): array + { + $username = strtolower(trim($username)); + + if ($username === '') { + return [ + 'allowed' => false, + 'message' => 'Für dieses Konto konnte kein Benutzername ermittelt werden.', + ]; + } + + if (!$this->ldap->isEnabled() || !$this->ldap->canUseLdap()) { + return [ + 'allowed' => false, + 'message' => 'Der Kontostatus konnte nicht sicher geprüft werden.', + ]; + } + + $user = $this->ldap->getUserByUsername($username, ['uid', 'shadowExpire']); + + if (!is_array($user)) { + return [ + 'allowed' => false, + 'message' => 'Dieses Konto ist nicht freigeschaltet.', + ]; + } + + $shadowExpire = (string) ($user['shadowexpire'] ?? $user['shadowExpire'] ?? ''); + $activeShadowExpire = (string) ($this->config['ldap']['active_shadow_expire'] ?? -1); + + if ($shadowExpire !== $activeShadowExpire) { + return [ + 'allowed' => false, + 'message' => 'Dieses Konto ist noch nicht freigeschaltet.', + ]; + } + + return [ + 'allowed' => true, + 'message' => 'Konto ist aktiv.', + ]; + } +} diff --git a/src/App/RegistrationService.php b/src/App/RegistrationService.php index 0b8e732f..87485f7d 100644 --- a/src/App/RegistrationService.php +++ b/src/App/RegistrationService.php @@ -64,7 +64,8 @@ final class RegistrationService $note = trim((string) ($input['note'] ?? '')); $password = (string) ($input['password'] ?? ''); $passwordConfirm = (string) ($input['password_confirm'] ?? ''); - $displayName = trim($givenName . ' ' . $familyName); + $displayName = $givenName; + $fullName = trim($givenName . ' ' . $familyName); $errors = []; if (!preg_match('/^[a-z0-9._-]{3,32}$/', $username)) { @@ -128,7 +129,7 @@ final class RegistrationService 'apple_generateduid' => '', 'display_name' => $displayName, 'cn' => $displayName !== '' ? $displayName : $username, - 'gecos' => $displayName, + 'gecos' => $fullName !== '' ? $fullName : $displayName, 'admin_note' => '', 'ldap_created_at' => gmdate('c'), ];