From 1f6f6f3ffff5cc876e491bbeab6daafd5062527d Mon Sep 17 00:00:00 2001
From: Marvin Buchmann <marvin.buchmann@typo3.com>
Date: Tue, 27 Aug 2024 10:56:04 +0200
Subject: [PATCH] Provide configuration options for routes

---
 README.md                                     | 11 ++++++-
 src/DependencyInjection/Configuration.php     | 10 +++++++
 .../T3GKeycloakExtension.php                  |  4 ++-
 src/Resources/config/services.yaml            |  3 ++
 src/Security/KeyCloakAuthenticator.php        | 30 +++++++++----------
 5 files changed, 41 insertions(+), 17 deletions(-)

diff --git a/README.md b/README.md
index d2ae26f..7bd6c6b 100644
--- a/README.md
+++ b/README.md
@@ -78,6 +78,10 @@ class HomeController extends AbstractController
     #[Route(path: '/login', name: 'login', methods: ['GET'])]
     public function login(ClientRegistry $clientRegistry): RedirectResponse
     {
+        if (null !== $this->getUser()) {
+            return $this->redirectToRoute('dashboard');
+        }
+
         return $clientRegistry
             ->getClient('keycloak')
             ->redirect([
@@ -86,7 +90,7 @@ class HomeController extends AbstractController
     }
 
     /**
-     * A callback route is required to authenticate the user. 
+     * This route must match the authentication route in your bundle configuration.
      */
     #[IsGranted('ROLE_USER')]
     #[Route(path: '/oauth/callback', name: 'oauth_callback', methods: ['GET'])]
@@ -120,6 +124,11 @@ t3g_keycloak:
             # Defaults:
             - ROLE_USER
             - ROLE_OAUTH_USER
+    routes:
+        # redirect_route passed to keycloak
+        authentication: oauth_callback
+        # route to redirect to after successful authentication
+        success: dashboard
 ```
 
 ### Role Mapping
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
index ca8870f..96c30c6 100644
--- a/src/DependencyInjection/Configuration.php
+++ b/src/DependencyInjection/Configuration.php
@@ -38,6 +38,16 @@ public function getConfigTreeBuilder(): TreeBuilder
                         ->end()
                     ->end()
                 ->end()
+                ->arrayNode('routes')->addDefaultsIfNotSet()
+                    ->children()
+                        ->scalarNode('authentication')
+                            ->defaultValue(null)
+                        ->end()
+                        ->scalarNode('success')
+                            ->defaultValue(null)
+                            ->end()
+                        ->end()
+                ->end()
             ->end()
         ;
 
diff --git a/src/DependencyInjection/T3GKeycloakExtension.php b/src/DependencyInjection/T3GKeycloakExtension.php
index 838b0d1..b18229b 100644
--- a/src/DependencyInjection/T3GKeycloakExtension.php
+++ b/src/DependencyInjection/T3GKeycloakExtension.php
@@ -37,9 +37,11 @@ public function prepend(ContainerBuilder $container): void
         $container->setParameter('t3g_keycloak.keycloak.user_provider_class', $config['keycloak']['user_provider_class']);
         $container->setParameter('t3g_keycloak.keycloak.default_roles', $config['keycloak']['default_roles']);
         $container->setParameter('t3g_keycloak.keycloak.role_mapping', $config['keycloak']['role_mapping']);
+        $container->setParameter('t3g_keycloak.routes.authentication', $config['routes']['authentication']);
+        $container->setParameter('t3g_keycloak.routes.success', $config['routes']['success']);
 
         if ($container->hasExtension($this->getAlias())) {
-            $container->prependExtensionConfig($this->getAlias(), ['keycloak' => []]);
+            $container->prependExtensionConfig($this->getAlias(), ['keycloak' => [], 'routes' => []]);
         }
 
         if ($container->hasExtension('httplug')) {
diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml
index ab2e3c0..aa433c6 100644
--- a/src/Resources/config/services.yaml
+++ b/src/Resources/config/services.yaml
@@ -15,6 +15,9 @@ services:
   T3G\Bundle\Keycloak\Security\KeyCloakAuthenticator:
     class: T3G\Bundle\Keycloak\Security\KeyCloakAuthenticator
     public: true
+    arguments:
+      $routeAuthentication: '%t3g_keycloak.routes.authentication%'
+      $routeSuccess: '%t3g_keycloak.routes.success%'
 
   T3G\Bundle\Keycloak\EventSubscriber\RequestSubscriber:
     tags: [ kernel.event_subscriber ]
diff --git a/src/Security/KeyCloakAuthenticator.php b/src/Security/KeyCloakAuthenticator.php
index 5911b21..74c48df 100644
--- a/src/Security/KeyCloakAuthenticator.php
+++ b/src/Security/KeyCloakAuthenticator.php
@@ -35,22 +35,25 @@ class KeyCloakAuthenticator extends OAuth2Authenticator implements Authenticatio
     private SessionInterface $session;
     private RouterInterface $router;
     private UserProviderInterface $userProvider;
+    private ?string $routeAuthentication;
+    private ?string $routeSuccess;
 
     /**
      * @param KeyCloakUserProvider $userProvider
      */
-    public function __construct(ClientRegistry $clientRegistry, RequestStack $requestStack, RouterInterface $router, UserProviderInterface $userProvider)
+    public function __construct(ClientRegistry $clientRegistry, RequestStack $requestStack, RouterInterface $router, UserProviderInterface $userProvider, ?string $routeAuthentication = null, ?string $routeSuccess = null)
     {
         $this->client = $clientRegistry->getClient('keycloak');
         $this->session = $requestStack->getSession();
         $this->router = $router;
         $this->userProvider = $userProvider;
+        $this->routeAuthentication = $routeAuthentication;
+        $this->routeSuccess = $routeSuccess;
     }
 
     public function supports(Request $request): ?bool
     {
-        // @TODO: make configurable
-        return 'oauth_callback' === $request->attributes->get('_route');
+        return $this->routeAuthentication === $request->attributes->get('_route');
     }
 
     public function authenticate(Request $request): Passport
@@ -76,20 +79,21 @@ public function authenticate(Request $request): Passport
 
     public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
     {
-        // @TODO: make configurable
+        if (null === $this->routeSuccess) {
+            return null;
+        }
+
         return new RedirectResponse(
-            $this->router->generate('dashboard'),
+            $this->router->generate($this->routeSuccess),
             Response::HTTP_TEMPORARY_REDIRECT
         );
     }
 
     public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
     {
-        // @TODO: make configurable
-        return new RedirectResponse(
-            $this->router->generate('login'),
-            Response::HTTP_TEMPORARY_REDIRECT
-        );
+        $message = strtr($exception->getMessageKey(), $exception->getMessageData());
+
+        return new Response($message, Response::HTTP_FORBIDDEN);
     }
 
     /**
@@ -98,11 +102,7 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio
      */
     public function start(Request $request, AuthenticationException $authException = null): Response
     {
-        // @TODO: make configurable
-        return new RedirectResponse(
-            $this->router->generate('login'),
-            Response::HTTP_TEMPORARY_REDIRECT
-        );
+        return new RedirectResponse('/', Response::HTTP_TEMPORARY_REDIRECT);
     }
 
     private function getScopesFromToken(AccessToken $token): array