diff --git a/config/web-interface/config.yml b/config/web-interface/config.yml index 2d5cefa0..21913c7e 100644 --- a/config/web-interface/config.yml +++ b/config/web-interface/config.yml @@ -1,10 +1,5 @@ imports: - - { resource: services/console.yml } - - { resource: services/consumer.yml } - - { resource: services/controller.yml } - - { resource: services/event_listener.yml } - - { resource: services/integration.yml } - - { resource: services/persistence.yml } + - { resource: services/ } # The framework configuration belongs here, because when we transition to the microservice approach, # the other contexts don't have the concept of a session. @@ -16,3 +11,16 @@ framework: twig: paths: "%kernel.project_dir%/src/WebInterface/Presentation/Http/View": web-interface + +security: + providers: + user_provider: + id: 'web-interface.security.user_provider' + firewalls: + dev: + pattern: ^/(_(profiler|wdt)|css|images|js)/ + security: false + main: + pattern: ^/ + custom_authenticators: + - 'web-interface.security.arrival_authenticator' diff --git a/config/web-interface/services/controller.yml b/config/web-interface/services/controller.yml index ffe28019..ae76ca2c 100644 --- a/config/web-interface/services/controller.yml +++ b/config/web-interface/services/controller.yml @@ -5,6 +5,7 @@ services: arguments: - '@twig' - '@web-interface.connect-four-service' + - '@web-interface.security' tags: - 'controller.service_arguments' @@ -12,6 +13,7 @@ services: class: Gaming\WebInterface\Presentation\Http\ChatController arguments: - '@web-interface.chat-service' + - '@web-interface.security' tags: - 'controller.service_arguments' @@ -19,6 +21,7 @@ services: class: Gaming\WebInterface\Presentation\Http\ConnectFourController arguments: - '@web-interface.connect-four-service' + - '@web-interface.security' tags: - 'controller.service_arguments' @@ -26,5 +29,6 @@ services: class: Gaming\WebInterface\Presentation\Http\IdentityController arguments: - '@web-interface.identity-service' + - '@web-interface.security' tags: - 'controller.service_arguments' diff --git a/config/web-interface/services/event_listener.yml b/config/web-interface/services/event_listener.yml deleted file mode 100644 index db013c37..00000000 --- a/config/web-interface/services/event_listener.yml +++ /dev/null @@ -1,9 +0,0 @@ -services: - - web-interface.assign-user-id-on-kernel-request: - class: Gaming\WebInterface\Infrastructure\EventListener\AssignUserIdOnKernelRequest - public: false - arguments: - - '@web-interface.identity-service' - tags: - - { name: kernel.event_listener, event: kernel.request } diff --git a/config/web-interface/services/security.yml b/config/web-interface/services/security.yml new file mode 100644 index 00000000..0e712a5b --- /dev/null +++ b/config/web-interface/services/security.yml @@ -0,0 +1,17 @@ +services: + + web-interface.security: + class: Gaming\WebInterface\Infrastructure\Security\Security + arguments: + - '@security.token_storage' + + web-interface.security.user_provider: + class: Gaming\WebInterface\Infrastructure\Security\UserProvider + public: false + + web-interface.security.arrival_authenticator: + class: Gaming\WebInterface\Infrastructure\Security\ArrivalAuthenticator + public: false + arguments: + - '@web-interface.identity-service' + - '@security.token_storage' diff --git a/src/Kernel.php b/src/Kernel.php index 44046779..10cc9421 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -11,6 +11,7 @@ use Symfony\Bundle\DebugBundle\DebugBundle; use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\MonologBundle\MonologBundle; +use Symfony\Bundle\SecurityBundle\SecurityBundle; use Symfony\Bundle\TwigBundle\TwigBundle; use Symfony\Bundle\WebProfilerBundle\WebProfilerBundle; use Symfony\Component\Config\Loader\LoaderInterface; @@ -31,7 +32,8 @@ public function registerBundles(): array new MareinLockDoctrineMigrationsBundle(), new MareinStandardHeadersCsrfBundle(), new TwigBundle(), - new MonologBundle() + new MonologBundle(), + new SecurityBundle() ]; if ($this->getEnvironment() === 'dev') { diff --git a/src/WebInterface/Infrastructure/EventListener/AssignUserIdOnKernelRequest.php b/src/WebInterface/Infrastructure/EventListener/AssignUserIdOnKernelRequest.php deleted file mode 100644 index 9d32e9ed..00000000 --- a/src/WebInterface/Infrastructure/EventListener/AssignUserIdOnKernelRequest.php +++ /dev/null @@ -1,36 +0,0 @@ -identityService = $identityService; - } - - public function onKernelRequest(RequestEvent $event): void - { - if (!$event->isMainRequest()) { - return; - } - - $request = $event->getRequest(); - $session = $request->getSession(); - - if (!$session->has('user')) { - // todo Create user only when it's really needed. - // Currently, a new user is created in the identity context for each visitor of this website. - // In the future, a new user must only be created when it performs an action where an identity is necessary. - // For example: open a game, sign up etc. - $session->set('user', $this->identityService->arrive()['userId']); - } - } -} diff --git a/src/WebInterface/Infrastructure/Security/ArrivalAuthenticator.php b/src/WebInterface/Infrastructure/Security/ArrivalAuthenticator.php new file mode 100644 index 00000000..ad986a0d --- /dev/null +++ b/src/WebInterface/Infrastructure/Security/ArrivalAuthenticator.php @@ -0,0 +1,53 @@ +tokenStorage->getToken() === null; + } + + public function authenticate(Request $request): Passport + { + $currentUser = $this->tokenStorage->getToken()?->getUser() ?? new User( + $this->identityService->arrive()['userId'] + ); + + return new SelfValidatingPassport( + new UserBadge( + $currentUser->getUserIdentifier() + ) + ); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return null; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + throw $exception; + } +} diff --git a/src/WebInterface/Infrastructure/Security/Security.php b/src/WebInterface/Infrastructure/Security/Security.php new file mode 100644 index 00000000..1f4cc94d --- /dev/null +++ b/src/WebInterface/Infrastructure/Security/Security.php @@ -0,0 +1,24 @@ +tokenStorage->getToken()?->getUser() ?? new User( + (new NilUuid())->toRfc4122() + ); + } +} diff --git a/src/WebInterface/Infrastructure/Security/User.php b/src/WebInterface/Infrastructure/Security/User.php new file mode 100644 index 00000000..3dc3fdd6 --- /dev/null +++ b/src/WebInterface/Infrastructure/Security/User.php @@ -0,0 +1,29 @@ +userIdentifier; + } +} diff --git a/src/WebInterface/Infrastructure/Security/UserProvider.php b/src/WebInterface/Infrastructure/Security/UserProvider.php new file mode 100644 index 00000000..9d755faa --- /dev/null +++ b/src/WebInterface/Infrastructure/Security/UserProvider.php @@ -0,0 +1,26 @@ +loadUserByIdentifier($user->getUserIdentifier()); + } + + public function supportsClass(string $class) + { + return $class == User::class; + } + + public function loadUserByIdentifier(string $identifier): UserInterface + { + return new User($identifier); + } +} diff --git a/src/WebInterface/Presentation/Http/ChatController.php b/src/WebInterface/Presentation/Http/ChatController.php index 4cac8925..b90c785e 100644 --- a/src/WebInterface/Presentation/Http/ChatController.php +++ b/src/WebInterface/Presentation/Http/ChatController.php @@ -5,16 +5,16 @@ namespace Gaming\WebInterface\Presentation\Http; use Gaming\WebInterface\Application\ChatService; +use Gaming\WebInterface\Infrastructure\Security\Security; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; final class ChatController { - private ChatService $chatService; - - public function __construct(ChatService $chatService) - { - $this->chatService = $chatService; + public function __construct( + private readonly ChatService $chatService, + private readonly Security $security + ) { } public function writeMessageAction(Request $request, string $chatId): JsonResponse @@ -22,7 +22,7 @@ public function writeMessageAction(Request $request, string $chatId): JsonRespon return new JsonResponse( $this->chatService->writeMessage( $chatId, - (string)$request->getSession()->get('user'), + $this->security->getUser()->getUserIdentifier(), (string)$request->request->get('message') ) ); @@ -34,7 +34,7 @@ public function messagesAction(Request $request, string $chatId): JsonResponse [ 'messages' => $this->chatService->messages( $chatId, - (string)$request->getSession()->get('user'), + $this->security->getUser()->getUserIdentifier(), 0, 10000 ) diff --git a/src/WebInterface/Presentation/Http/ConnectFourController.php b/src/WebInterface/Presentation/Http/ConnectFourController.php index 3f1484b6..8aed8247 100644 --- a/src/WebInterface/Presentation/Http/ConnectFourController.php +++ b/src/WebInterface/Presentation/Http/ConnectFourController.php @@ -5,16 +5,16 @@ namespace Gaming\WebInterface\Presentation\Http; use Gaming\WebInterface\Application\ConnectFourService; +use Gaming\WebInterface\Infrastructure\Security\Security; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; final class ConnectFourController { - private ConnectFourService $connectFourService; - - public function __construct(ConnectFourService $connectFourService) - { - $this->connectFourService = $connectFourService; + public function __construct( + private readonly ConnectFourService $connectFourService, + private readonly Security $security + ) { } public function showAction(string $gameId): JsonResponse @@ -28,7 +28,7 @@ public function openAction(Request $request): JsonResponse { return new JsonResponse( $this->connectFourService->open( - (string)$request->getSession()->get('user') + $this->security->getUser()->getUserIdentifier() ) ); } @@ -38,7 +38,7 @@ public function joinAction(Request $request, string $gameId): JsonResponse return new JsonResponse( $this->connectFourService->join( $gameId, - (string)$request->getSession()->get('user') + $this->security->getUser()->getUserIdentifier() ) ); } @@ -48,7 +48,7 @@ public function abortAction(Request $request, string $gameId): JsonResponse return new JsonResponse( $this->connectFourService->abort( $gameId, - (string)$request->getSession()->get('user') + $this->security->getUser()->getUserIdentifier() ) ); } @@ -58,7 +58,7 @@ public function resignAction(Request $request, string $gameId): JsonResponse return new JsonResponse( $this->connectFourService->resign( $gameId, - (string)$request->getSession()->get('user') + $this->security->getUser()->getUserIdentifier() ) ); } @@ -68,7 +68,7 @@ public function moveAction(Request $request, string $gameId): JsonResponse return new JsonResponse( $this->connectFourService->move( $gameId, - (string)$request->getSession()->get('user'), + $this->security->getUser()->getUserIdentifier(), (int)$request->request->get('column', -1) ) ); diff --git a/src/WebInterface/Presentation/Http/IdentityController.php b/src/WebInterface/Presentation/Http/IdentityController.php index cad4b9c7..becaacbb 100644 --- a/src/WebInterface/Presentation/Http/IdentityController.php +++ b/src/WebInterface/Presentation/Http/IdentityController.php @@ -5,23 +5,23 @@ namespace Gaming\WebInterface\Presentation\Http; use Gaming\WebInterface\Application\IdentityService; +use Gaming\WebInterface\Infrastructure\Security\Security; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; final class IdentityController { - private IdentityService $identityService; - - public function __construct(IdentityService $identityService) - { - $this->identityService = $identityService; + public function __construct( + private readonly IdentityService $identityService, + private readonly Security $security + ) { } public function signUpAction(Request $request): JsonResponse { return new JsonResponse( $this->identityService->signUp( - (string)$request->getSession()->get('user'), + $this->security->getUser()->getUserIdentifier(), (string)$request->request->get('username', uniqid()), (string)$request->request->get('password', 'password') ) diff --git a/src/WebInterface/Presentation/Http/PageController.php b/src/WebInterface/Presentation/Http/PageController.php index 61b5e116..5ea42148 100644 --- a/src/WebInterface/Presentation/Http/PageController.php +++ b/src/WebInterface/Presentation/Http/PageController.php @@ -5,22 +5,18 @@ namespace Gaming\WebInterface\Presentation\Http; use Gaming\WebInterface\Application\ConnectFourService; +use Gaming\WebInterface\Infrastructure\Security\Security; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Twig\Environment; final class PageController { - private Environment $twig; - - private ConnectFourService $connectFourService; - public function __construct( - Environment $twig, - ConnectFourService $connectFourService + private readonly Environment $twig, + private readonly ConnectFourService $connectFourService, + private readonly Security $security ) { - $this->twig = $twig; - $this->connectFourService = $connectFourService; } public function lobbyAction(): Response @@ -29,7 +25,8 @@ public function lobbyAction(): Response $this->twig->render('@web-interface/lobby.html.twig', [ 'maximumNumberOfGamesInList' => 10, 'openGames' => $this->connectFourService->openGames()['games'], - 'runningGames' => $this->connectFourService->runningGames() + 'runningGames' => $this->connectFourService->runningGames(), + 'user' => $this->security->getUser() ]) ); } @@ -48,7 +45,7 @@ public function profileAction(Request $request): Response return new Response( $this->twig->render('@web-interface/profile.html.twig', [ 'games' => $this->connectFourService->gamesByPlayer( - (string)$request->getSession()->get('user') + $this->security->getUser()->getUserIdentifier() )['games'] ]) ); diff --git a/src/WebInterface/Presentation/Http/View/lobby.html.twig b/src/WebInterface/Presentation/Http/View/lobby.html.twig index b2577b9e..b59d33fb 100644 --- a/src/WebInterface/Presentation/Http/View/lobby.html.twig +++ b/src/WebInterface/Presentation/Http/View/lobby.html.twig @@ -13,7 +13,7 @@