Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New authenticator for the newer Symfony authenticator API #246

Merged
merged 1 commit into from
Jul 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
58 changes: 40 additions & 18 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()
->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