From c000f97cb2134991bb8aea7f2b4615a76ba724f0 Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Sat, 11 Mar 2023 12:46:35 +0100 Subject: [PATCH] Split User Repository Registration mode (#385) Split User Repository Registration mode --- phpstan.neon | 1 + .../Controller/AssertionControllerFactory.php | 4 ++-- .../ProfileBasedRequestOptionsBuilder.php | 4 ++-- .../Factory/Security/WebauthnFactory.php | 4 ++-- .../DependencyInjection/WebauthnExtension.php | 4 +++- .../src/Repository/CanRegisterUserEntity.php | 14 ++++++++++++ ...ublicKeyCredentialUserEntityRepository.php | 22 +------------------ ...ublicKeyCredentialUserEntityRepository.php | 14 ++++-------- ...redentialUserEntityRepositoryInterface.php | 14 ++++++++++++ src/symfony/src/Resources/config/security.php | 8 +++---- src/symfony/src/Resources/config/services.php | 4 ++-- .../Guesser/CurrentUserEntityGuesser.php | 4 ++-- .../Guesser/RequestBodyUserEntityGuesser.php | 14 +++++++++--- .../Authenticator/WebauthnAuthenticator.php | 9 ++++++-- ...ublicKeyCredentialUserEntityRepository.php | 5 +++-- 15 files changed, 72 insertions(+), 53 deletions(-) create mode 100644 src/symfony/src/Repository/CanRegisterUserEntity.php create mode 100644 src/symfony/src/Repository/PublicKeyCredentialUserEntityRepositoryInterface.php diff --git a/phpstan.neon b/phpstan.neon index fbd06480..ff17920e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -65,6 +65,7 @@ parameters: message: '#Strict comparison using === between mixed and null will always evaluate to false\.#' path: src/metadata-service/src/Statement/StatusReport.php count: 1 + - '#Fetching class constant class of deprecated class Webauthn\\Bundle\\Repository\\PublicKeyCredentialUserEntityRepository.*#' - '#.*Binding.*#' - '#Parameter \#\d+ \$.* of .* expects .*, .* given\.#' - '#Property .* does not accept .*\|false\.#' diff --git a/src/symfony/src/Controller/AssertionControllerFactory.php b/src/symfony/src/Controller/AssertionControllerFactory.php index b5b30386..18e3c2c6 100644 --- a/src/symfony/src/Controller/AssertionControllerFactory.php +++ b/src/symfony/src/Controller/AssertionControllerFactory.php @@ -12,7 +12,7 @@ use Webauthn\AuthenticatorAssertionResponseValidator; use Webauthn\Bundle\CredentialOptionsBuilder\ProfileBasedRequestOptionsBuilder; use Webauthn\Bundle\CredentialOptionsBuilder\PublicKeyCredentialRequestOptionsBuilder; -use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepository; +use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepositoryInterface; use Webauthn\Bundle\Security\Handler\FailureHandler; use Webauthn\Bundle\Security\Handler\RequestOptionsHandler; use Webauthn\Bundle\Security\Handler\SuccessHandler; @@ -32,7 +32,7 @@ public function __construct( private readonly PublicKeyCredentialRequestOptionsFactory $publicKeyCredentialRequestOptionsFactory, private readonly PublicKeyCredentialLoader $publicKeyCredentialLoader, private readonly AuthenticatorAssertionResponseValidator $attestationResponseValidator, - private readonly PublicKeyCredentialUserEntityRepository $publicKeyCredentialUserEntityRepository, + private readonly PublicKeyCredentialUserEntityRepositoryInterface $publicKeyCredentialUserEntityRepository, private readonly PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository ) { $this->logger = new NullLogger(); diff --git a/src/symfony/src/CredentialOptionsBuilder/ProfileBasedRequestOptionsBuilder.php b/src/symfony/src/CredentialOptionsBuilder/ProfileBasedRequestOptionsBuilder.php index 9a16629c..1fe2f9ef 100644 --- a/src/symfony/src/CredentialOptionsBuilder/ProfileBasedRequestOptionsBuilder.php +++ b/src/symfony/src/CredentialOptionsBuilder/ProfileBasedRequestOptionsBuilder.php @@ -12,7 +12,7 @@ use Symfony\Component\Validator\Validator\ValidatorInterface; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; use Webauthn\Bundle\Dto\ServerPublicKeyCredentialRequestOptionsRequest; -use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepository; +use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepositoryInterface; use Webauthn\Bundle\Service\PublicKeyCredentialRequestOptionsFactory; use Webauthn\PublicKeyCredentialDescriptor; use Webauthn\PublicKeyCredentialRequestOptions; @@ -25,7 +25,7 @@ final class ProfileBasedRequestOptionsBuilder implements PublicKeyCredentialRequ public function __construct( private readonly SerializerInterface $serializer, private readonly ValidatorInterface $validator, - private readonly PublicKeyCredentialUserEntityRepository $userEntityRepository, + private readonly PublicKeyCredentialUserEntityRepositoryInterface $userEntityRepository, private readonly PublicKeyCredentialSourceRepository $credentialSourceRepository, private readonly PublicKeyCredentialRequestOptionsFactory $publicKeyCredentialRequestOptionsFactory, private readonly string $profile, diff --git a/src/symfony/src/DependencyInjection/Factory/Security/WebauthnFactory.php b/src/symfony/src/DependencyInjection/Factory/Security/WebauthnFactory.php index c0b682fa..22e81ad0 100644 --- a/src/symfony/src/DependencyInjection/Factory/Security/WebauthnFactory.php +++ b/src/symfony/src/DependencyInjection/Factory/Security/WebauthnFactory.php @@ -26,7 +26,7 @@ use Webauthn\Bundle\CredentialOptionsBuilder\ProfileBasedCreationOptionsBuilder; use Webauthn\Bundle\CredentialOptionsBuilder\ProfileBasedRequestOptionsBuilder; use Webauthn\Bundle\DependencyInjection\Compiler\DynamicRouteCompilerPass; -use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepository; +use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepositoryInterface; use Webauthn\Bundle\Security\Guesser\RequestBodyUserEntityGuesser; use Webauthn\Bundle\Security\Handler\DefaultCreationOptionsHandler; use Webauthn\Bundle\Security\Handler\DefaultFailureHandler; @@ -465,7 +465,7 @@ private function getAssertionOptionsBuilderId( ->setArguments([ new Reference(SerializerInterface::class), new Reference(ValidatorInterface::class), - new Reference(PublicKeyCredentialUserEntityRepository::class), + new Reference(PublicKeyCredentialUserEntityRepositoryInterface::class), new Reference(PublicKeyCredentialSourceRepository::class), new Reference(PublicKeyCredentialRequestOptionsFactory::class), $config['profile'], diff --git a/src/symfony/src/DependencyInjection/WebauthnExtension.php b/src/symfony/src/DependencyInjection/WebauthnExtension.php index c92348b0..d7b73299 100644 --- a/src/symfony/src/DependencyInjection/WebauthnExtension.php +++ b/src/symfony/src/DependencyInjection/WebauthnExtension.php @@ -38,6 +38,7 @@ use Webauthn\Bundle\DependencyInjection\Compiler\LoggerSetterCompilerPass; use Webauthn\Bundle\Doctrine\Type as DbalType; use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepository; +use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepositoryInterface; use Webauthn\Bundle\Service\PublicKeyCredentialCreationOptionsFactory; use Webauthn\Bundle\Service\PublicKeyCredentialRequestOptionsFactory; use Webauthn\Counter\CounterChecker; @@ -92,6 +93,7 @@ public function load(array $configs, ContainerBuilder $container): void $container->setAlias(PublicKeyCredentialSourceRepository::class, $config['credential_repository']); $container->setAlias(PublicKeyCredentialUserEntityRepository::class, $config['user_repository']); + $container->setAlias(PublicKeyCredentialUserEntityRepositoryInterface::class, $config['user_repository']); if ($config['token_binding_support_handler'] !== null) { $container->setAlias(TokenBindingHandler::class, $config['token_binding_support_handler']); @@ -242,7 +244,7 @@ private function loadRequestControllersSupport(ContainerBuilder $container, arra ->setArguments([ new Reference(SerializerInterface::class), new Reference(ValidatorInterface::class), - new Reference(PublicKeyCredentialUserEntityRepository::class), + new Reference(PublicKeyCredentialUserEntityRepositoryInterface::class), new Reference(PublicKeyCredentialSourceRepository::class), new Reference(PublicKeyCredentialRequestOptionsFactory::class), $requestConfig['profile'], diff --git a/src/symfony/src/Repository/CanRegisterUserEntity.php b/src/symfony/src/Repository/CanRegisterUserEntity.php new file mode 100644 index 00000000..4d2cfbf8 --- /dev/null +++ b/src/symfony/src/Repository/CanRegisterUserEntity.php @@ -0,0 +1,14 @@ +logger->critical( - 'Please change the Public Key Credential User Entity Repository in the bundle configuration. See https://webauthn-doc.spomky-labs.com/the-webauthn-server/the-symfony-way#repositories-1' - ); - throw new RuntimeException( - 'You are using the DummyPublicKeyCredentialUserEntityRepository service. Please create your own repository' - ); - } - - public function saveUserEntity(PublicKeyCredentialUserEntity $userEntity): void - { - $this->logger->critical( - 'Please change the Public Key Credential User Entity Repository in the bundle configuration. See https://webauthn-doc.spomky-labs.com/the-webauthn-server/the-symfony-way#repositories-1' - ); - throw new RuntimeException( - 'You are using the DummyPublicKeyCredentialUserEntityRepository service. Please create your own repository' - ); - } } diff --git a/src/symfony/src/Repository/PublicKeyCredentialUserEntityRepository.php b/src/symfony/src/Repository/PublicKeyCredentialUserEntityRepository.php index 73e01088..a01c8ec5 100644 --- a/src/symfony/src/Repository/PublicKeyCredentialUserEntityRepository.php +++ b/src/symfony/src/Repository/PublicKeyCredentialUserEntityRepository.php @@ -4,15 +4,9 @@ namespace Webauthn\Bundle\Repository; -use Webauthn\PublicKeyCredentialUserEntity; - -interface PublicKeyCredentialUserEntityRepository +/** + * @deprecated since 4.6.0, to be removed in 5.0.0. Use {@link PublicKeyCredentialUserEntityRepositoryInterface} and {@link CanRegisterUserEntity} instead. + */ +interface PublicKeyCredentialUserEntityRepository extends PublicKeyCredentialUserEntityRepositoryInterface, CanRegisterUserEntity { - public function findOneByUsername(string $username): ?PublicKeyCredentialUserEntity; - - public function findOneByUserHandle(string $userHandle): ?PublicKeyCredentialUserEntity; - - public function generateNextUserEntityId(): string; - - public function saveUserEntity(PublicKeyCredentialUserEntity $userEntity): void; } diff --git a/src/symfony/src/Repository/PublicKeyCredentialUserEntityRepositoryInterface.php b/src/symfony/src/Repository/PublicKeyCredentialUserEntityRepositoryInterface.php new file mode 100644 index 00000000..f46a05e3 --- /dev/null +++ b/src/symfony/src/Repository/PublicKeyCredentialUserEntityRepositoryInterface.php @@ -0,0 +1,14 @@ +args([[], // Firewall settings abstract_arg('Firewall name'), service('security.http_utils'), ]); $container->set(CurrentUserEntityGuesser::class)->args( - [service(TokenStorageInterface::class), service(PublicKeyCredentialUserEntityRepository::class)] + [service(TokenStorageInterface::class), service(PublicKeyCredentialUserEntityRepositoryInterface::class)] ); $container->set(RequestBodyUserEntityGuesser::class)->args( [service(SerializerInterface::class), service(ValidatorInterface::class), service( - PublicKeyCredentialUserEntityRepository::class + PublicKeyCredentialUserEntityRepositoryInterface::class ), ] ); }; diff --git a/src/symfony/src/Resources/config/services.php b/src/symfony/src/Resources/config/services.php index 6b8853e0..8d76f775 100644 --- a/src/symfony/src/Resources/config/services.php +++ b/src/symfony/src/Resources/config/services.php @@ -21,7 +21,7 @@ use Webauthn\Bundle\Controller\DummyControllerFactory; use Webauthn\Bundle\Repository\DummyPublicKeyCredentialSourceRepository; use Webauthn\Bundle\Repository\DummyPublicKeyCredentialUserEntityRepository; -use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepository; +use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepositoryInterface; use Webauthn\Bundle\Routing\Loader; use Webauthn\Bundle\Service\PublicKeyCredentialCreationOptionsFactory; use Webauthn\Bundle\Service\PublicKeyCredentialRequestOptionsFactory; @@ -135,7 +135,7 @@ service(PublicKeyCredentialRequestOptionsFactory::class), service(PublicKeyCredentialLoader::class), service(AuthenticatorAssertionResponseValidator::class), - service(PublicKeyCredentialUserEntityRepository::class), + service(PublicKeyCredentialUserEntityRepositoryInterface::class), service(PublicKeyCredentialSourceRepository::class), ]); diff --git a/src/symfony/src/Security/Guesser/CurrentUserEntityGuesser.php b/src/symfony/src/Security/Guesser/CurrentUserEntityGuesser.php index adc6d30b..31ab6506 100644 --- a/src/symfony/src/Security/Guesser/CurrentUserEntityGuesser.php +++ b/src/symfony/src/Security/Guesser/CurrentUserEntityGuesser.php @@ -7,14 +7,14 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Webauthn\Bundle\Exception\MissingUserEntityException; -use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepository; +use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepositoryInterface; use Webauthn\PublicKeyCredentialUserEntity; final class CurrentUserEntityGuesser implements UserEntityGuesser { public function __construct( private readonly TokenStorageInterface $tokenStorage, - private readonly PublicKeyCredentialUserEntityRepository $userEntityRepository + private readonly PublicKeyCredentialUserEntityRepositoryInterface $userEntityRepository ) { } diff --git a/src/symfony/src/Security/Guesser/RequestBodyUserEntityGuesser.php b/src/symfony/src/Security/Guesser/RequestBodyUserEntityGuesser.php index c250e011..ff343251 100644 --- a/src/symfony/src/Security/Guesser/RequestBodyUserEntityGuesser.php +++ b/src/symfony/src/Security/Guesser/RequestBodyUserEntityGuesser.php @@ -9,7 +9,9 @@ use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; use Webauthn\Bundle\Dto\ServerPublicKeyCredentialCreationOptionsRequest; -use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepository; +use Webauthn\Bundle\Exception\MissingUserEntityException; +use Webauthn\Bundle\Repository\CanRegisterUserEntity; +use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepositoryInterface; use Webauthn\Exception\InvalidDataException; use Webauthn\PublicKeyCredentialUserEntity; @@ -18,7 +20,7 @@ final class RequestBodyUserEntityGuesser implements UserEntityGuesser public function __construct( private readonly SerializerInterface $serializer, private readonly ValidatorInterface $validator, - private readonly PublicKeyCredentialUserEntityRepository $userEntityRepository + private readonly PublicKeyCredentialUserEntityRepositoryInterface $userEntityRepository ) { } @@ -48,8 +50,14 @@ public function findUserEntity(Request $request): PublicKeyCredentialUserEntity } $existingUserEntity = $this->userEntityRepository->findOneByUsername($dto->username); + if ($existingUserEntity !== null) { + return $existingUserEntity; + } - return $existingUserEntity ?? PublicKeyCredentialUserEntity::create( + if (! $this->userEntityRepository instanceof CanRegisterUserEntity) { + throw MissingUserEntityException::create('Unable to find the user entity'); + } + return PublicKeyCredentialUserEntity::create( $dto->username, $this->userEntityRepository->generateNextUserEntityId(), $dto->displayName diff --git a/src/symfony/src/Security/Http/Authenticator/WebauthnAuthenticator.php b/src/symfony/src/Security/Http/Authenticator/WebauthnAuthenticator.php index 3edf7610..da2b6243 100644 --- a/src/symfony/src/Security/Http/Authenticator/WebauthnAuthenticator.php +++ b/src/symfony/src/Security/Http/Authenticator/WebauthnAuthenticator.php @@ -4,6 +4,7 @@ namespace Webauthn\Bundle\Security\Http\Authenticator; +use LogicException; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Symfony\Component\HttpFoundation\Request; @@ -23,7 +24,8 @@ use Webauthn\AuthenticatorAttestationResponse; use Webauthn\AuthenticatorAttestationResponseValidator; use Webauthn\Bundle\Exception\MissingUserEntityException; -use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepository; +use Webauthn\Bundle\Repository\CanRegisterUserEntity; +use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepositoryInterface; use Webauthn\Bundle\Security\Authentication\Token\WebauthnToken; use Webauthn\Bundle\Security\Http\Authenticator\Passport\Credentials\WebauthnCredentials; use Webauthn\Bundle\Security\Storage\OptionsStorage; @@ -51,7 +53,7 @@ public function __construct( private readonly OptionsStorage $optionsStorage, private readonly array $securedRelyingPartyIds, private readonly PublicKeyCredentialSourceRepository $credentialSourceRepository, - private readonly PublicKeyCredentialUserEntityRepository $credentialUserEntityRepository, + private readonly PublicKeyCredentialUserEntityRepositoryInterface $credentialUserEntityRepository, private readonly PublicKeyCredentialLoader $publicKeyCredentialLoader, private readonly AuthenticatorAssertionResponseValidator $assertionResponseValidator, private readonly AuthenticatorAttestationResponseValidator $attestationResponseValidator @@ -203,6 +205,9 @@ private function processWithAssertion(Request $request): Passport private function processWithAttestation(Request $request): Passport { + if (! $this->credentialUserEntityRepository instanceof CanRegisterUserEntity) { + throw new LogicException('The credential source repository must implement CanRegisterUserEntity'); + } try { $format = method_exists( $request, diff --git a/tests/symfony/functional/PublicKeyCredentialUserEntityRepository.php b/tests/symfony/functional/PublicKeyCredentialUserEntityRepository.php index 6cc67b42..0ec78e03 100644 --- a/tests/symfony/functional/PublicKeyCredentialUserEntityRepository.php +++ b/tests/symfony/functional/PublicKeyCredentialUserEntityRepository.php @@ -7,10 +7,11 @@ use ParagonIE\ConstantTime\Base64UrlSafe; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Uid\Uuid; -use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepository as PublicKeyCredentialUserEntityRepositoryInterface; +use Webauthn\Bundle\Repository\CanRegisterUserEntity; +use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepositoryInterface; use Webauthn\PublicKeyCredentialUserEntity; -final class PublicKeyCredentialUserEntityRepository implements PublicKeyCredentialUserEntityRepositoryInterface +final class PublicKeyCredentialUserEntityRepository implements PublicKeyCredentialUserEntityRepositoryInterface, CanRegisterUserEntity { public function __construct( private readonly CacheItemPoolInterface $cacheItemPool