add user management
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Admin;
|
||||
|
||||
use App\Security\AccessDeniedPageRenderer;
|
||||
use App\Security\ApplicationRoles;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
@@ -11,13 +15,25 @@ use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
final class SecurityController extends AbstractController
|
||||
{
|
||||
#[Route('/admin/login', name: 'admin_login')]
|
||||
public function login(AuthenticationUtils $authUtils): Response
|
||||
{
|
||||
// Wenn bereits eingeloggt → direkt ins Dashboard
|
||||
if ($this->getUser() !== null && $this->isGranted('ROLE_ADMIN_AREA')) {
|
||||
public function login(
|
||||
Request $request,
|
||||
AuthenticationUtils $authUtils,
|
||||
AccessDeniedPageRenderer $accessDeniedPageRenderer,
|
||||
): Response {
|
||||
$user = $this->getUser();
|
||||
|
||||
if ($user !== null && $this->isGranted(ApplicationRoles::ROLE_ADMIN_AREA)) {
|
||||
return $this->redirectToRoute('admin_dashboard');
|
||||
}
|
||||
|
||||
if ($user !== null) {
|
||||
return $accessDeniedPageRenderer->renderForbidden(
|
||||
$request,
|
||||
'admin',
|
||||
ApplicationRoles::ROLE_ADMIN_AREA,
|
||||
);
|
||||
}
|
||||
|
||||
return $this->render('admin/security/login.html.twig', [
|
||||
'last_username' => $authUtils->getLastUsername(),
|
||||
'error' => $authUtils->getLastAuthenticationError(),
|
||||
@@ -27,7 +43,7 @@ final class SecurityController extends AbstractController
|
||||
#[Route('/admin/logout', name: 'admin_logout')]
|
||||
public function logout(): void
|
||||
{
|
||||
// Symfony interceptet diese Route, daher bleibt sie leer.
|
||||
// Symfony intercepts this route via the firewall logout configuration.
|
||||
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Chat;
|
||||
|
||||
use App\Security\AccessDeniedPageRenderer;
|
||||
use App\Security\ApplicationRoles;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
@@ -19,12 +22,25 @@ use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
final class SecurityController extends AbstractController
|
||||
{
|
||||
#[Route('/chat/login', name: 'chat_login', methods: ['GET', 'POST'])]
|
||||
public function login(AuthenticationUtils $authUtils): Response
|
||||
{
|
||||
if ($this->getUser() !== null && $this->isGranted('ROLE_CHAT_USER')) {
|
||||
public function login(
|
||||
Request $request,
|
||||
AuthenticationUtils $authUtils,
|
||||
AccessDeniedPageRenderer $accessDeniedPageRenderer,
|
||||
): Response {
|
||||
$user = $this->getUser();
|
||||
|
||||
if ($user !== null && $this->isGranted(ApplicationRoles::ROLE_CHAT_USER)) {
|
||||
return $this->redirectToRoute('chat_index');
|
||||
}
|
||||
|
||||
if ($user !== null) {
|
||||
return $accessDeniedPageRenderer->renderForbidden(
|
||||
$request,
|
||||
'chat',
|
||||
ApplicationRoles::ROLE_CHAT_USER,
|
||||
);
|
||||
}
|
||||
|
||||
return $this->render('chat/security/login.html.twig', [
|
||||
'last_username' => $authUtils->getLastUsername(),
|
||||
'error' => $authUtils->getLastAuthenticationError(),
|
||||
@@ -34,6 +50,7 @@ final class SecurityController extends AbstractController
|
||||
#[Route('/chat/logout', name: 'chat_logout', methods: ['GET'])]
|
||||
public function logout(): void
|
||||
{
|
||||
// Symfony intercepts this route via the firewall logout configuration.
|
||||
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
|
||||
}
|
||||
}
|
||||
|
||||
25
src/Security/AccessDeniedHandler.php
Normal file
25
src/Security/AccessDeniedHandler.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Security;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
|
||||
|
||||
/**
|
||||
* Shows a user-friendly 403 page instead of the framework default access denied response.
|
||||
*/
|
||||
final readonly class AccessDeniedHandler implements AccessDeniedHandlerInterface
|
||||
{
|
||||
public function __construct(private AccessDeniedPageRenderer $renderer)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(Request $request, AccessDeniedException $accessDeniedException): ?Response
|
||||
{
|
||||
return $this->renderer->renderForbidden($request);
|
||||
}
|
||||
}
|
||||
153
src/Security/AccessDeniedPageRenderer.php
Normal file
153
src/Security/AccessDeniedPageRenderer.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Security;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
/**
|
||||
* Renders a consistent forbidden page for firewall access denials and login area mismatches.
|
||||
*/
|
||||
final readonly class AccessDeniedPageRenderer
|
||||
{
|
||||
public function __construct(
|
||||
private Environment $twig,
|
||||
private Security $security,
|
||||
) {
|
||||
}
|
||||
|
||||
public function renderForbidden(
|
||||
Request $request,
|
||||
?string $area = null,
|
||||
?string $requiredRole = null,
|
||||
?string $message = null,
|
||||
): Response {
|
||||
$context = $this->buildContext($request, $area, $requiredRole, $message);
|
||||
|
||||
return new Response(
|
||||
$this->twig->render('error/access_denied.html.twig', $context),
|
||||
Response::HTTP_FORBIDDEN,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function buildContext(
|
||||
Request $request,
|
||||
?string $area,
|
||||
?string $requiredRole,
|
||||
?string $message,
|
||||
): array {
|
||||
$areaKey = $area ?? $this->detectArea($request);
|
||||
$target = $this->targetContext($areaKey);
|
||||
$currentUser = $this->security->getUser();
|
||||
$fallbackLink = $this->fallbackLink($target['home_route']);
|
||||
|
||||
return [
|
||||
'status_code' => Response::HTTP_FORBIDDEN,
|
||||
'status_text' => 'Zugriff verweigert',
|
||||
'area_label' => $target['label'],
|
||||
'required_role' => $requiredRole ?? $target['required_role'],
|
||||
'login_route' => $target['login_route'],
|
||||
'logout_route' => $target['logout_route'],
|
||||
'home_route' => $fallbackLink['route'],
|
||||
'home_label' => $fallbackLink['label'],
|
||||
'current_user_identifier' => $currentUser instanceof UserInterface ? $currentUser->getUserIdentifier() : null,
|
||||
'message' => $message ?? $this->defaultMessage($currentUser instanceof UserInterface, $target['label']),
|
||||
];
|
||||
}
|
||||
|
||||
private function detectArea(Request $request): string
|
||||
{
|
||||
$path = $request->getPathInfo();
|
||||
|
||||
if (str_starts_with($path, '/admin')) {
|
||||
return 'admin';
|
||||
}
|
||||
|
||||
if (
|
||||
$path === '/'
|
||||
|| str_starts_with($path, '/chat')
|
||||
|| str_starts_with($path, '/ask-jobs')
|
||||
|| str_starts_with($path, '/ask-sse')
|
||||
|| str_starts_with($path, '/history')
|
||||
|| str_starts_with($path, '/chat-messages/frontend')
|
||||
) {
|
||||
return 'chat';
|
||||
}
|
||||
|
||||
return 'application';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{label: string, required_role: string, login_route: string|null, logout_route: string|null, home_route: string|null}
|
||||
*/
|
||||
private function targetContext(string $area): array
|
||||
{
|
||||
return match ($area) {
|
||||
'admin' => [
|
||||
'label' => 'Adminbereich',
|
||||
'required_role' => ApplicationRoles::ROLE_ADMIN_AREA,
|
||||
'login_route' => 'admin_login',
|
||||
'logout_route' => 'admin_logout',
|
||||
'home_route' => 'admin_dashboard',
|
||||
],
|
||||
'chat' => [
|
||||
'label' => 'Chatbereich',
|
||||
'required_role' => ApplicationRoles::ROLE_CHAT_USER,
|
||||
'login_route' => 'chat_login',
|
||||
'logout_route' => 'chat_logout',
|
||||
'home_route' => 'chat_index',
|
||||
],
|
||||
default => [
|
||||
'label' => 'Anwendung',
|
||||
'required_role' => ApplicationRoles::ROLE_USER,
|
||||
'login_route' => null,
|
||||
'logout_route' => null,
|
||||
'home_route' => null,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{route: string|null, label: string|null}
|
||||
*/
|
||||
private function fallbackLink(?string $targetHomeRoute): array
|
||||
{
|
||||
if ($targetHomeRoute === 'admin_dashboard' && $this->security->isGranted(ApplicationRoles::ROLE_ADMIN_AREA)) {
|
||||
return ['route' => 'admin_dashboard', 'label' => 'Zum Admin-Dashboard'];
|
||||
}
|
||||
|
||||
if ($targetHomeRoute === 'chat_index' && $this->security->isGranted(ApplicationRoles::ROLE_CHAT_USER)) {
|
||||
return ['route' => 'chat_index', 'label' => 'Zum Chat'];
|
||||
}
|
||||
|
||||
if ($this->security->isGranted(ApplicationRoles::ROLE_CHAT_USER)) {
|
||||
return ['route' => 'chat_index', 'label' => 'Zum Chat'];
|
||||
}
|
||||
|
||||
if ($this->security->isGranted(ApplicationRoles::ROLE_ADMIN_AREA)) {
|
||||
return ['route' => 'admin_dashboard', 'label' => 'Zum Admin-Dashboard'];
|
||||
}
|
||||
|
||||
return ['route' => null, 'label' => null];
|
||||
}
|
||||
|
||||
private function defaultMessage(bool $authenticated, string $areaLabel): string
|
||||
{
|
||||
if ($authenticated) {
|
||||
return sprintf(
|
||||
'Du bist angemeldet, aber dein Benutzerkonto hat nicht die notwendige Berechtigung für den %s.',
|
||||
$areaLabel,
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf('Für den Zugriff auf den %s ist eine Anmeldung mit passender Rolle erforderlich.', $areaLabel);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user