Skip to content

Commit

Permalink
Use Symfony's security system
Browse files Browse the repository at this point in the history
Upon arrival, ArrivialAuthenticator creates a new user in the
identity context and authenticates them in the session.

The current implementation covers what AssignUserIdOnKernelRequest
did before. It adds some complexity, but opens the way for #130
to secure the /metrics endpoint with basic auth, and #6 to eventually
authenticate the user in any way.
  • Loading branch information
marein committed Oct 16, 2022
1 parent f615c1a commit cb0a9db
Show file tree
Hide file tree
Showing 15 changed files with 201 additions and 86 deletions.
20 changes: 14 additions & 6 deletions config/web-interface/config.yml
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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'
4 changes: 4 additions & 0 deletions config/web-interface/services/controller.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,30 @@ services:
arguments:
- '@twig'
- '@web-interface.connect-four-service'
- '@web-interface.security'
tags:
- 'controller.service_arguments'

web-interface.chat-controller:
class: Gaming\WebInterface\Presentation\Http\ChatController
arguments:
- '@web-interface.chat-service'
- '@web-interface.security'
tags:
- 'controller.service_arguments'

web-interface.connect-four-controller:
class: Gaming\WebInterface\Presentation\Http\ConnectFourController
arguments:
- '@web-interface.connect-four-service'
- '@web-interface.security'
tags:
- 'controller.service_arguments'

web-interface.identity-controller:
class: Gaming\WebInterface\Presentation\Http\IdentityController
arguments:
- '@web-interface.identity-service'
- '@web-interface.security'
tags:
- 'controller.service_arguments'
9 changes: 0 additions & 9 deletions config/web-interface/services/event_listener.yml

This file was deleted.

17 changes: 17 additions & 0 deletions config/web-interface/services/security.yml
Original file line number Diff line number Diff line change
@@ -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'
4 changes: 3 additions & 1 deletion src/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,7 +32,8 @@ public function registerBundles(): array
new MareinLockDoctrineMigrationsBundle(),
new MareinStandardHeadersCsrfBundle(),
new TwigBundle(),
new MonologBundle()
new MonologBundle(),
new SecurityBundle()
];

if ($this->getEnvironment() === 'dev') {
Expand Down

This file was deleted.

53 changes: 53 additions & 0 deletions src/WebInterface/Infrastructure/Security/ArrivalAuthenticator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace Gaming\WebInterface\Infrastructure\Security;

use Gaming\WebInterface\Application\IdentityService;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;

final class ArrivalAuthenticator extends AbstractAuthenticator
{
public function __construct(
private readonly IdentityService $identityService,
private readonly TokenStorageInterface $tokenStorage
) {
}

public function supports(Request $request): ?bool
{
return $this->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;
}
}
24 changes: 24 additions & 0 deletions src/WebInterface/Infrastructure/Security/Security.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Gaming\WebInterface\Infrastructure\Security;

use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Uid\NilUuid;

final class Security
{
public function __construct(
private readonly TokenStorageInterface $tokenStorage
) {
}

public function getUser(): UserInterface
{
return $this->tokenStorage->getToken()?->getUser() ?? new User(
(new NilUuid())->toRfc4122()
);
}
}
29 changes: 29 additions & 0 deletions src/WebInterface/Infrastructure/Security/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Gaming\WebInterface\Infrastructure\Security;

use Symfony\Component\Security\Core\User\UserInterface;

final class User implements UserInterface
{
public function __construct(
private readonly string $userIdentifier
) {
}

public function getRoles(): array
{
return [];
}

public function eraseCredentials(): void
{
}

public function getUserIdentifier(): string
{
return $this->userIdentifier;
}
}
26 changes: 26 additions & 0 deletions src/WebInterface/Infrastructure/Security/UserProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Gaming\WebInterface\Infrastructure\Security;

use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;

final class UserProvider implements UserProviderInterface
{
public function refreshUser(UserInterface $user): UserInterface
{
return $this->loadUserByIdentifier($user->getUserIdentifier());
}

public function supportsClass(string $class)
{
return $class == User::class;
}

public function loadUserByIdentifier(string $identifier): UserInterface
{
return new User($identifier);
}
}
14 changes: 7 additions & 7 deletions src/WebInterface/Presentation/Http/ChatController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@
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
{
return new JsonResponse(
$this->chatService->writeMessage(
$chatId,
(string)$request->getSession()->get('user'),
$this->security->getUser()->getUserIdentifier(),
(string)$request->request->get('message')
)
);
Expand All @@ -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
)
Expand Down
20 changes: 10 additions & 10 deletions src/WebInterface/Presentation/Http/ConnectFourController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
)
);
}
Expand All @@ -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()
)
);
}
Expand All @@ -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()
)
);
}
Expand All @@ -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()
)
);
}
Expand All @@ -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)
)
);
Expand Down
Loading

0 comments on commit cb0a9db

Please sign in to comment.