Skip to content

Commit

Permalink
New authenticator for the newer Symfony authenticator API
Browse files Browse the repository at this point in the history
  • Loading branch information
mbabker committed Jun 29, 2021
1 parent aafe5fb commit 1010b69
Show file tree
Hide file tree
Showing 28 changed files with 1,095 additions and 92 deletions.
19 changes: 19 additions & 0 deletions DependencyInjection/Compiler/CustomUserProviderCompilerPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,33 @@

/**
* CustomUserProviderCompilerPass.
*
* @deprecated no replacement
*/
final class CustomUserProviderCompilerPass implements CompilerPassInterface
{
/**
* @var bool
*/
private $internalUse;

/**
* @param bool $internalUse Flag indicating the pass was created by an internal bundle call (used to suppress runtime deprecations)
*/
public function __construct(bool $internalUse = false)
{
$this->internalUse = $internalUse;
}

/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (false === $this->internalUse) {
trigger_deprecation('gesdinet/jwt-refresh-token-bundle', '0.13', 'The "%s" class is deprecated.', self::class);
}

$customUserProvider = $container->getParameter('gesdinet_jwt_refresh_token.user_provider');
if (!$customUserProvider) {
return;
Expand Down
19 changes: 19 additions & 0 deletions DependencyInjection/Compiler/UserCheckerCompilerPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,33 @@

/**
* UserCheckerCompilerPass.
*
* @deprecated no replacement
*/
final class UserCheckerCompilerPass implements CompilerPassInterface
{
/**
* @var bool
*/
private $internalUse;

/**
* @param bool $internalUse Flag indicating the pass was created by an internal bundle call (used to suppress runtime deprecations)
*/
public function __construct(bool $internalUse = false)
{
$this->internalUse = $internalUse;
}

/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (false === $this->internalUse) {
trigger_deprecation('gesdinet/jwt-refresh-token-bundle', '0.13', 'The "%s" class is deprecated.', self::class);
}

$userCheckerId = $container->getParameter('gesdinet.jwtrefreshtoken.user_checker.id');
if (!$userCheckerId) {
return;
Expand Down
56 changes: 39 additions & 17 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Gesdinet\JWTRefreshTokenBundle\DependencyInjection;

use Gesdinet\JWTRefreshTokenBundle\Entity\RefreshToken;
use Symfony\Component\Config\Definition\BaseNode;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
Expand All @@ -32,39 +33,60 @@ public function getConfigTreeBuilder()

$rootNode
->children()
->integerNode('ttl')->defaultValue(2592000)->end()
->booleanNode('ttl_update')->defaultFalse()->end()
->scalarNode('firewall')->defaultValue('api')->end()
->scalarNode('user_provider')->defaultNull()->end()
->scalarNode('user_identity_field')->defaultValue('username')->end()
->integerNode('ttl')
->defaultValue(2592000)
->info('The default TTL for all authenticators.')
->end()
->booleanNode('ttl_update')
->defaultFalse()
->info('The default update TTL flag for all authenticators.')
->end()
->scalarNode('firewall')
->setDeprecated(...$this->getDeprecationParameters('The "%node%" node is deprecated without replacement.', '0.13'))
->defaultValue('api')
->end()
->scalarNode('user_provider')
->setDeprecated(...$this->getDeprecationParameters('The "%node%" node is deprecated without replacement.', '0.13'))
->defaultNull()
->end()
->scalarNode('user_identity_field')
->setDeprecated(...$this->getDeprecationParameters('The "%node%" node is deprecated without replacement.', '0.13'))
->defaultValue('username')
->end()
->scalarNode('manager_type')
->defaultValue('orm')
->info('Set manager mode instead of default one (orm)')
->end()
->info('Set the type of object manager to use (default: orm)')
->end()
->scalarNode('refresh_token_class')
->defaultNull()
->info('Set another refresh token class to use instead of default one (Gesdinet\JWTRefreshTokenBundle\Entity\RefreshToken)')
->end()
->info(sprintf('Set the refresh token class to use (default: %s)', RefreshToken::class))
->end()
->scalarNode('object_manager')
->defaultNull()
->info('Set object manager to use (default: doctrine.orm.entity_manager)')
->end()
->scalarNode('user_checker')->defaultValue('security.user_checker')->end()
->info('Set the object manager to use (default: doctrine.orm.entity_manager)')
->end()
->scalarNode('user_checker')
->setDeprecated(...$this->getDeprecationParameters('The "%node%" node is deprecated without replacement.', '0.13'))
->defaultValue('security.user_checker')
->end()
->scalarNode('refresh_token_entity')
->setDeprecated(...$this->getDeprecationParameters('The "%node%" node is deprecated, use the "refresh_token_class" node instead.', '0.5'))
->defaultNull()
->info('Set another refresh token class to use instead of default one (Gesdinet\JWTRefreshTokenBundle\Entity\RefreshToken)')
->end()
->info(sprintf('Set the refresh token class to use (default: %s)', RefreshToken::class))
->end()
->scalarNode('entity_manager')
->setDeprecated(...$this->getDeprecationParameters('The "%node%" node is deprecated, use the "object_manager" node instead.', '0.5'))
->defaultNull()
->info('Set entity manager to use')
->end()
->info('Set the entity manager to use')
->end()
->scalarNode('single_use')
->defaultFalse()
->info('When true, generate a new refresh token on consumption (deleting the old one)')
->end()
->scalarNode('token_parameter_name')->defaultValue('refresh_token')->end()
->scalarNode('token_parameter_name')
->defaultValue('refresh_token')
->info('The default request parameter name containing the refresh token for all authenticators.')
->end()
->booleanNode('doctrine_mappings')
->setDeprecated(...$this->getDeprecationParameters('The "%node%" node is deprecated without replacement.', '0.13'))
->info('When true, resolving of Doctrine mapping is done automatically to use either ORM or ODM object manager')
Expand Down
38 changes: 36 additions & 2 deletions DependencyInjection/GesdinetJWTRefreshTokenExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;

Expand All @@ -23,13 +24,18 @@
*/
class GesdinetJWTRefreshTokenExtension extends Extension
{
private const DEPRECATED_SERVICES = [
'gesdinet.jwtrefreshtoken' => '0.13',
'gesdinet.jwtrefreshtoken.authenticator' => '0.13',
'gesdinet.jwtrefreshtoken.user_provider' => '0.13',
];

/**
* {@inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$config = $this->processConfiguration($this->getConfiguration($configs, $container), $configs);

$loader = new Loader\PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.php');
Expand Down Expand Up @@ -64,6 +70,34 @@ public function load(array $configs, ContainerBuilder $container)
$container->setParameter('gesdinet.jwtrefreshtoken.refresh_token.class', $refreshTokenClass);
$container->setParameter('gesdinet.jwtrefreshtoken.object_manager.id', $objectManager);
$container->setParameter('gesdinet.jwtrefreshtoken.user_checker.id', $config['user_checker']);

$this->deprecateServices($container);
}

private function deprecateServices(ContainerBuilder $container): void
{
$usesSymfony51Api = method_exists(Definition::class, 'getDeprecation');

foreach (self::DEPRECATED_SERVICES as $serviceId => $deprecatedSince) {
if (!$container->hasDefinition($serviceId)) {
continue;
}

$service = $container->getDefinition($serviceId);

if ($usesSymfony51Api) {
$service->setDeprecated(
'gesdinet/jwt-refresh-token-bundle',
$deprecatedSince,
'The "%service_id%" service is deprecated.'
);
} else {
$service->setDeprecated(
true,
'The "%service_id%" service is deprecated.'
);
}
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

/*
* This file is part of the GesdinetJWTRefreshTokenBundle package.
*
* (c) Gesdinet <http://www.gesdinet.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Gesdinet\JWTRefreshTokenBundle\DependencyInjection\Security\Factory;

use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference;

final class RefreshTokenAuthenticatorFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface
{
public function create(ContainerBuilder $container, string $id, array $config, string $userProviderId, ?string $defaultEntryPointId): array
{
// Does not support the legacy authentication system
return [];
}

public function getPosition(): string
{
return 'http';
}

public function getKey(): string
{
return 'refresh-jwt';
}

public function addConfiguration(NodeDefinition $node): void
{
// no-op TTL and param configuration until bundle is further updated to support per-authenticator configuration
$node
->children()
->scalarNode('provider')->end()
->scalarNode('success_handler')->end()
->scalarNode('failure_handler')->end()
/*
->integerNode('ttl')
->defaultNull()
->info('Sets a TTL specific to this authenticator, if not set then the "ttl" bundle config is used.')
->end()
->booleanNode('ttl_update')
->defaultNull()
->info('Sets whether the TTL for refresh tokens should be refreshed for this authenticator, if not set then the "ttl_update" bundle config is used.')
->end()
->scalarNode('token_parameter_name')
->defaultNull()
->info('Sets the parameter name for the refresh token for this authenticator, if not set then the "token_parameter_name" bundle config is used.')
->end()
*/
->end()
;
}

public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId)
{
$authenticatorId = 'security.authenticator.refresh_jwt.'.$firewallName;

// When per-authenticator configuration is supported, this array should be updated to check the $config values before falling back to the bundle parameters
$options = [
'ttl' => new Parameter('gesdinet_jwt_refresh_token.ttl'),
'ttl_update' => new Parameter('gesdinet_jwt_refresh_token.ttl_update'),
'token_parameter_name' => new Parameter('gesdinet_jwt_refresh_token.token_parameter_name'),
];

$container->setDefinition($authenticatorId, new ChildDefinition('gesdinet.jwtrefreshtoken.security.refresh_token_authenticator'))
->addArgument(new Reference($userProviderId))
->addArgument(new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)))
->addArgument(new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)))
->addArgument($options);

return $authenticatorId;
}

private function createAuthenticationSuccessHandler(ContainerBuilder $container, string $id, array $config)
{
$successHandlerId = $this->getSuccessHandlerId($id);

if (isset($config['success_handler'])) {
$container->setDefinition($successHandlerId, new ChildDefinition('security.authentication.custom_success_handler'))
->replaceArgument(0, new Reference($config['success_handler']))
->replaceArgument(1, [])
->replaceArgument(2, $id);
} else {
$container->setDefinition($successHandlerId, new ChildDefinition('gesdinet.jwtrefreshtoken.security.authentication.success_handler'))
->addMethodCall('setFirewallName', [$id]);
}

return $successHandlerId;
}

private function createAuthenticationFailureHandler(ContainerBuilder $container, string $id, array $config)
{
$id = $this->getFailureHandlerId($id);

if (isset($config['failure_handler'])) {
$container->setDefinition($id, new ChildDefinition('security.authentication.custom_failure_handler'))
->replaceArgument(0, new Reference($config['failure_handler']))
->replaceArgument(1, []);
} else {
$container->setDefinition($id, new ChildDefinition('gesdinet.jwtrefreshtoken.security.authentication.failure_handler'));
}

return $id;
}

private function getSuccessHandlerId(string $id)
{
return 'security.authentication.success_handler.'.$id.'.'.str_replace('-', '_', $this->getKey());
}

private function getFailureHandlerId(string $id)
{
return 'security.authentication.failure_handler.'.$id.'.'.str_replace('-', '_', $this->getKey());
}
}
49 changes: 49 additions & 0 deletions Event/RefreshAuthenticationFailureEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of the GesdinetJWTRefreshTokenBundle package.
*
* (c) Gesdinet <http://www.gesdinet.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Gesdinet\JWTRefreshTokenBundle\Event;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AuthenticationException;

class RefreshAuthenticationFailureEvent extends Event
{
/**
* @var AuthenticationException
*/
private $exception;

/**
* @var Response|null
*/
private $response;

public function __construct(AuthenticationException $exception, ?Response $response = null)
{
$this->exception = $exception;
$this->response = $response;
}

public function getException(): AuthenticationException
{
return $this->exception;
}

public function getResponse(): ?Response
{
return $this->response;
}

public function setResponse(?Response $response = null): void
{
$this->response = $response;
}
}
Loading

0 comments on commit 1010b69

Please sign in to comment.