deploy
This commit is contained in:
206
docs/Umsetzungsanweisung/Old-Nexus/src/App/AuthService.php
Normal file
206
docs/Umsetzungsanweisung/Old-Nexus/src/App/AuthService.php
Normal file
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
final class AuthService
|
||||
{
|
||||
private const SESSION_TTL = 604800;
|
||||
|
||||
public function __construct(private App $app) {}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->app->config()->authEnabled;
|
||||
}
|
||||
|
||||
public function start(): void
|
||||
{
|
||||
$this->app->session()->start();
|
||||
}
|
||||
|
||||
public function user(): ?array
|
||||
{
|
||||
$this->start();
|
||||
$user = $_SESSION['auth_user'] ?? null;
|
||||
if (!is_array($user)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$expiresAt = (int) ($_SESSION['auth_expires_at'] ?? 0);
|
||||
if ($expiresAt > 0 && $expiresAt < time()) {
|
||||
$this->clearLocalSession();
|
||||
return null;
|
||||
}
|
||||
|
||||
$_SESSION['auth_expires_at'] = time() + self::SESSION_TTL;
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function isAuthenticated(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
}
|
||||
|
||||
public function login(string $returnTo = '/'): void
|
||||
{
|
||||
if (!$this->isEnabled()) {
|
||||
redirect($returnTo !== '' ? $returnTo : '/');
|
||||
}
|
||||
|
||||
$this->start();
|
||||
$_SESSION['oidc_return_to'] = $this->safeReturnTo($returnTo);
|
||||
redirect('/auth/login');
|
||||
}
|
||||
|
||||
public function callback(): void
|
||||
{
|
||||
$query = (string) ($_SERVER['QUERY_STRING'] ?? '');
|
||||
redirect('/auth/callback' . ($query !== '' ? '?' . $query : ''));
|
||||
}
|
||||
|
||||
public function logout(): void
|
||||
{
|
||||
redirect('/auth/logout');
|
||||
}
|
||||
|
||||
public function storeUser(array $claims, array $groups, string $idToken): void
|
||||
{
|
||||
$username = (string) ($claims['preferred_username'] ?? $claims['email'] ?? $claims['sub'] ?? '');
|
||||
$_SESSION['auth_user'] = [
|
||||
'sub' => (string) ($claims['sub'] ?? ''),
|
||||
'username' => $username,
|
||||
'email' => (string) ($claims['email'] ?? ''),
|
||||
'name' => (string) ($claims['name'] ?? $username),
|
||||
'groups' => $groups,
|
||||
'id_token' => $idToken,
|
||||
];
|
||||
$_SESSION['auth_id_token'] = $idToken;
|
||||
$_SESSION['auth_expires_at'] = time() + self::SESSION_TTL;
|
||||
|
||||
$this->rememberKeycloakUser($claims, $groups);
|
||||
}
|
||||
|
||||
public function canAccessModule(array $module): bool
|
||||
{
|
||||
$auth = is_array($module['auth'] ?? null) ? $module['auth'] : [];
|
||||
$required = (bool) ($auth['required'] ?? false);
|
||||
if (!$required || !$this->isEnabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$user = $this->user();
|
||||
if ($user === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$allowedUsers = $this->normalizeList($auth['users'] ?? []);
|
||||
$allowedGroups = $this->normalizeList($auth['groups'] ?? []);
|
||||
if ($allowedUsers === [] && $allowedGroups === []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$username = strtolower((string) ($user['username'] ?? ''));
|
||||
$email = strtolower((string) ($user['email'] ?? ''));
|
||||
$sub = strtolower((string) ($user['sub'] ?? ''));
|
||||
foreach ($allowedUsers as $allowedUser) {
|
||||
if ($allowedUser === $username || ($email !== '' && $allowedUser === $email) || ($sub !== '' && $allowedUser === $sub)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$userGroups = $this->normalizeList($user['groups'] ?? []);
|
||||
return array_intersect($allowedGroups, $userGroups) !== [];
|
||||
}
|
||||
|
||||
private function rememberKeycloakUser(array $claims, array $groups): void
|
||||
{
|
||||
$pdo = $this->app->basePdo();
|
||||
$sub = trim((string)($claims['sub'] ?? ''));
|
||||
if (!$pdo || $sub === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$groupsJson = json_encode(array_values(array_unique(array_map('strval', $groups))), JSON_UNESCAPED_UNICODE);
|
||||
if (!is_string($groupsJson)) {
|
||||
$groupsJson = '[]';
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO nexus_auth_users (sub, username, email, name, groups, last_login_at)
|
||||
VALUES (:sub, :username, :email, :name, :groups, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT(sub) DO UPDATE SET
|
||||
username = excluded.username,
|
||||
email = excluded.email,
|
||||
name = excluded.name,
|
||||
groups = excluded.groups,
|
||||
last_login_at = CURRENT_TIMESTAMP"
|
||||
);
|
||||
$stmt->execute([
|
||||
'sub' => $sub,
|
||||
'username' => (string)($claims['preferred_username'] ?? ''),
|
||||
'email' => (string)($claims['email'] ?? ''),
|
||||
'name' => (string)($claims['name'] ?? ''),
|
||||
'groups' => $groupsJson,
|
||||
]);
|
||||
}
|
||||
|
||||
public function requireModuleAccess(array $module): void
|
||||
{
|
||||
if ($this->canAccessModule($module)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->isAuthenticated()) {
|
||||
$this->login($this->currentPath());
|
||||
}
|
||||
|
||||
http_response_code(403);
|
||||
exit('Forbidden');
|
||||
}
|
||||
|
||||
public function filterModules(array $modules): array
|
||||
{
|
||||
return array_values(array_filter($modules, fn (array $module): bool => $this->canAccessModule($module)));
|
||||
}
|
||||
|
||||
private function normalizeList(mixed $values): array
|
||||
{
|
||||
if (is_string($values)) {
|
||||
$values = preg_split('/[,\\n]+/', $values) ?: [];
|
||||
}
|
||||
if (!is_array($values)) {
|
||||
$values = [$values];
|
||||
}
|
||||
|
||||
$normalized = [];
|
||||
foreach ($values as $value) {
|
||||
$item = strtolower(trim((string) $value));
|
||||
if ($item !== '') {
|
||||
$normalized[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($normalized));
|
||||
}
|
||||
|
||||
private function clearLocalSession(): void
|
||||
{
|
||||
unset($_SESSION['auth_user'], $_SESSION['auth_id_token'], $_SESSION['auth_expires_at']);
|
||||
}
|
||||
|
||||
private function currentPath(): string
|
||||
{
|
||||
$uri = (string) ($_SERVER['REQUEST_URI'] ?? '/');
|
||||
return $this->safeReturnTo($uri);
|
||||
}
|
||||
|
||||
private function safeReturnTo(string $returnTo): string
|
||||
{
|
||||
$returnTo = trim($returnTo);
|
||||
if ($returnTo === '' || !str_starts_with($returnTo, '/') || str_starts_with($returnTo, '//')) {
|
||||
return '/';
|
||||
}
|
||||
return $returnTo;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user