Skip to content

Commit

Permalink
Create a request extractor API to replace the static method for extra…
Browse files Browse the repository at this point in the history
…cting the refresh token from the request
  • Loading branch information
mbabker committed Jul 13, 2021
1 parent a30e372 commit 7d9ff61
Show file tree
Hide file tree
Showing 18 changed files with 460 additions and 29 deletions.
25 changes: 25 additions & 0 deletions DependencyInjection/Compiler/AddExtractorsToChainCompilerPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Gesdinet\JWTRefreshTokenBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class AddExtractorsToChainCompilerPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;

public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('gesdinet.jwtrefreshtoken.request.extractor.chain')) {
return;
}

$definition = $container->getDefinition('gesdinet.jwtrefreshtoken.request.extractor.chain');

foreach ($this->findAndSortTaggedServices('gesdinet_jwt_refresh_token.request_extractor', $container) as $extractorService) {
$definition->addMethodCall('addExtractor', [$extractorService]);
}
}
}
3 changes: 3 additions & 0 deletions DependencyInjection/GesdinetJWTRefreshTokenExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Gesdinet\JWTRefreshTokenBundle\DependencyInjection;

use Gesdinet\JWTRefreshTokenBundle\Request\Extractor\ExtractorInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Definition;
Expand Down Expand Up @@ -40,6 +41,8 @@ public function load(array $configs, ContainerBuilder $container)
$loader = new Loader\PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.php');

$container->registerForAutoconfiguration(ExtractorInterface::class)->addTag('gesdinet_jwt_refresh_token.request_extractor');

$container->setParameter('gesdinet_jwt_refresh_token.ttl', $config['ttl']);
$container->setParameter('gesdinet_jwt_refresh_token.ttl_update', $config['ttl_update']);
$container->setParameter('gesdinet_jwt_refresh_token.security.firewall', $config['firewall']);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal
];

$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);
->replaceArgument(2, new Reference($userProviderId))
->replaceArgument(3, new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)))
->replaceArgument(4, new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)))
->replaceArgument(5, $options);

return $authenticatorId;
}
Expand Down
25 changes: 21 additions & 4 deletions EventListener/AttachRefreshTokenOnSuccessListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Gesdinet\JWTRefreshTokenBundle\Generator\RefreshTokenGeneratorInterface;
use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenInterface;
use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenManagerInterface;
use Gesdinet\JWTRefreshTokenBundle\Request\Extractor\ExtractorInterface;
use Gesdinet\JWTRefreshTokenBundle\Request\RequestRefreshToken;
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;
use Symfony\Component\HttpFoundation\RequestStack;
Expand Down Expand Up @@ -51,6 +52,11 @@ class AttachRefreshTokenOnSuccessListener
*/
protected $refreshTokenGenerator;

/**
* @var ExtractorInterface|null
*/
protected $extractor;

/**
* AttachRefreshTokenOnSuccessListener constructor.
*
Expand All @@ -64,27 +70,38 @@ public function __construct(
RequestStack $requestStack,
$tokenParameterName,
$singleUse,
RefreshTokenGeneratorInterface $refreshTokenGenerator
RefreshTokenGeneratorInterface $refreshTokenGenerator,
?ExtractorInterface $extractor = null
) {
$this->refreshTokenManager = $refreshTokenManager;
$this->ttl = $ttl;
$this->requestStack = $requestStack;
$this->tokenParameterName = $tokenParameterName;
$this->singleUse = $singleUse;
$this->refreshTokenGenerator = $refreshTokenGenerator;
$this->extractor = $extractor;

if (null === $extractor) {
trigger_deprecation('gesdinet/jwt-refresh-token-bundle', '1.0', 'Not injecting a "%s" into "%s" is deprecated, it will be required in 2.0.', ExtractorInterface::class, self::class);
}
}

public function attachRefreshToken(AuthenticationSuccessEvent $event)
{
$data = $event->getData();
$user = $event->getUser();
$request = $this->requestStack->getCurrentRequest();

if (!$user instanceof UserInterface) {
return;
}

$refreshTokenString = RequestRefreshToken::getRefreshToken($request, $this->tokenParameterName);
$data = $event->getData();
$request = $this->requestStack->getCurrentRequest();

if (null !== $this->extractor) {
$refreshTokenString = $this->extractor->getRefreshToken($request, $this->tokenParameterName);
} else {
$refreshTokenString = RequestRefreshToken::getRefreshToken($request, $this->tokenParameterName);
}

if ($refreshTokenString && true === $this->singleUse) {
$refreshToken = $this->refreshTokenManager->get($refreshTokenString);
Expand Down
2 changes: 2 additions & 0 deletions GesdinetJWTRefreshTokenBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Gesdinet\JWTRefreshTokenBundle;

use Gesdinet\JWTRefreshTokenBundle\DependencyInjection\Compiler\AddExtractorsToChainCompilerPass;
use Gesdinet\JWTRefreshTokenBundle\DependencyInjection\Compiler\CustomUserProviderCompilerPass;
use Gesdinet\JWTRefreshTokenBundle\DependencyInjection\Compiler\ObjectManagerCompilerPass;
use Gesdinet\JWTRefreshTokenBundle\DependencyInjection\Compiler\UserCheckerCompilerPass;
Expand All @@ -17,6 +18,7 @@ public function build(ContainerBuilder $container)
{
parent::build($container);

$container->addCompilerPass(new AddExtractorsToChainCompilerPass());
$container->addCompilerPass(new CustomUserProviderCompilerPass(true));
$container->addCompilerPass(new ObjectManagerCompilerPass());
$container->addCompilerPass(new UserCheckerCompilerPass(true));
Expand Down
38 changes: 38 additions & 0 deletions Request/Extractor/ChainExtractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?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\Request\Extractor;

use Symfony\Component\HttpFoundation\Request;

final class ChainExtractor implements ExtractorInterface
{
/**
* @var ExtractorInterface[]
*/
private $extractors = [];

public function addExtractor(ExtractorInterface $extractor): void
{
$this->extractors[] = $extractor;
}

public function getRefreshToken(Request $request, string $parameter): ?string
{
foreach ($this->extractors as $extractor) {
if (null !== $token = $extractor->getRefreshToken($request, $parameter)) {
return $token;
}
}

return null;
}
}
19 changes: 19 additions & 0 deletions Request/Extractor/ExtractorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?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\Request\Extractor;

use Symfony\Component\HttpFoundation\Request;

interface ExtractorInterface
{
public function getRefreshToken(Request $request, string $parameter): ?string;
}
29 changes: 29 additions & 0 deletions Request/Extractor/RequestBodyExtractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?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\Request\Extractor;

use Symfony\Component\HttpFoundation\Request;

final class RequestBodyExtractor implements ExtractorInterface
{
public function getRefreshToken(Request $request, string $parameter): ?string
{
if (false === strpos($request->getContentType(), 'json')) {
return null;
}

$content = $request->getContent();
$params = !empty($content) ? json_decode($content, true) : [];

return isset($params[$parameter]) ? trim($params[$parameter]) : null;
}
}
22 changes: 22 additions & 0 deletions Request/Extractor/RequestParameterExtractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?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\Request\Extractor;

use Symfony\Component\HttpFoundation\Request;

final class RequestParameterExtractor implements ExtractorInterface
{
public function getRefreshToken(Request $request, string $parameter): ?string
{
return $request->get($parameter);
}
}
6 changes: 6 additions & 0 deletions Request/RequestRefreshToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@

namespace Gesdinet\JWTRefreshTokenBundle\Request;

use Gesdinet\JWTRefreshTokenBundle\Request\Extractor\ExtractorInterface;
use Symfony\Component\HttpFoundation\Request;

trigger_deprecation('gesdinet/jwt-refresh-token-bundle', '1.0', 'The "%s" class is deprecated and will be removed in 2.0, use a "%s" implementation instead.', RequestRefreshToken::class, ExtractorInterface::class);

/**
* @deprecated to be removed in 2.0, use a `Gesdinet\JWTRefreshTokenBundle\Request\Extractor\ExtractorInterface` implementation instead.
*/
class RequestRefreshToken
{
public static function getRefreshToken(Request $request, $tokenParameterName)
Expand Down
30 changes: 26 additions & 4 deletions Resources/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
use Gesdinet\JWTRefreshTokenBundle\Generator\RefreshTokenGenerator;
use Gesdinet\JWTRefreshTokenBundle\Generator\RefreshTokenGeneratorInterface;
use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenManagerInterface;
use Gesdinet\JWTRefreshTokenBundle\Request\Extractor\ChainExtractor;
use Gesdinet\JWTRefreshTokenBundle\Request\Extractor\ExtractorInterface;
use Gesdinet\JWTRefreshTokenBundle\Request\Extractor\RequestBodyExtractor;
use Gesdinet\JWTRefreshTokenBundle\Request\Extractor\RequestParameterExtractor;
use Gesdinet\JWTRefreshTokenBundle\Security\Http\Authentication\AuthenticationFailureHandler;
use Gesdinet\JWTRefreshTokenBundle\Security\Http\Authentication\AuthenticationSuccessHandler;
use Gesdinet\JWTRefreshTokenBundle\Security\Authenticator\RefreshTokenAuthenticator as LegacyRefreshTokenAuthenticator;
Expand All @@ -29,6 +33,7 @@
new Parameter('gesdinet_jwt_refresh_token.token_parameter_name'),
new Parameter('gesdinet_jwt_refresh_token.single_use'),
new Reference('gesdinet.jwtrefreshtoken.refresh_token_generator'),
new Reference('gesdinet.jwtrefreshtoken.request.extractor.chain'),
])
->tag('kernel.event_listener', [
'event' => 'lexik_jwt_authentication.on_authentication_success',
Expand All @@ -54,6 +59,22 @@

$services->alias(RefreshTokenManagerInterface::class, 'gesdinet.jwtrefreshtoken.refresh_token_manager');

$services->set('gesdinet.jwtrefreshtoken.request.extractor.chain')
->class(ChainExtractor::class)
->public();

$services->alias(ExtractorInterface::class, 'gesdinet.jwtrefreshtoken.request.extractor.chain');

$services->set('gesdinet.jwtrefreshtoken.request.extractor.request_body')
->class(RequestBodyExtractor::class)
->public()
->tag('gesdinet_jwt_refresh_token.request_extractor');

$services->set('gesdinet.jwtrefreshtoken.request.extractor.request_parameter')
->class(RequestParameterExtractor::class)
->public()
->tag('gesdinet_jwt_refresh_token.request_extractor');

$services->set('gesdinet.jwtrefreshtoken')
->class(RefreshToken::class)
->public()
Expand Down Expand Up @@ -101,10 +122,11 @@
->args([
new Reference('gesdinet.jwtrefreshtoken.refresh_token_manager'),
new Reference('event_dispatcher'),
// User provider parameter is added in the security factory, change to an abstract argument reference when Symfony 5.1 and newer are required
// Success handler parameter is added in the security factory, change to an abstract argument reference when Symfony 5.1 and newer are required
// Failure handler parameter is added in the security factory, change to an abstract argument reference when Symfony 5.1 and newer are required
// Options parameter is added in the security factory, change to an abstract argument reference when Symfony 5.1 and newer are required
null, // User provider parameter is replaced in the security factory, change to an abstract argument reference when Symfony 5.1 and newer are required
null, // Success handler parameter is replaced in the security factory, change to an abstract argument reference when Symfony 5.1 and newer are required
null, // Failure handler parameter is replaced in the security factory, change to an abstract argument reference when Symfony 5.1 and newer are required
null, // Options parameter is replaced in the security factory, change to an abstract argument reference when Symfony 5.1 and newer are required
new Reference('gesdinet.jwtrefreshtoken.request.extractor.chain'),
]);

$services->set(ClearInvalidRefreshTokensCommand::class)
Expand Down
21 changes: 19 additions & 2 deletions Security/Authenticator/RefreshTokenAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Gesdinet\JWTRefreshTokenBundle\Security\Authenticator;

use Gesdinet\JWTRefreshTokenBundle\Request\Extractor\ExtractorInterface;
use Gesdinet\JWTRefreshTokenBundle\Request\RequestRefreshToken;
use Gesdinet\JWTRefreshTokenBundle\Exception\UnknownRefreshTokenException;
use Gesdinet\JWTRefreshTokenBundle\Exception\UnknownUserFromRefreshTokenException;
Expand Down Expand Up @@ -44,26 +45,42 @@ class RefreshTokenAuthenticator extends AbstractGuardAuthenticator
*/
protected $tokenParameterName;

/**
* @var ExtractorInterface|null
*/
protected $extractor;

/**
* Constructor.
*
* @param string $tokenParameterName
*/
public function __construct(UserCheckerInterface $userChecker, $tokenParameterName)
public function __construct(UserCheckerInterface $userChecker, $tokenParameterName, ?ExtractorInterface $extractor = null)
{
$this->userChecker = $userChecker;
$this->tokenParameterName = $tokenParameterName;
$this->extractor = $extractor;
}

public function supports(Request $request)
{
if (null !== $this->extractor) {
return null !== $this->extractor->getRefreshToken($request, $this->tokenParameterName);
}

return null !== RequestRefreshToken::getRefreshToken($request, $this->tokenParameterName);
}

public function getCredentials(Request $request)
{
if (null !== $this->extractor) {
$token = $this->extractor->getRefreshToken($request, $this->tokenParameterName);
} else {
$token = RequestRefreshToken::getRefreshToken($request, $this->tokenParameterName);
}

return [
'token' => RequestRefreshToken::getRefreshToken($request, $this->tokenParameterName),
'token' => $token,
];
}

Expand Down
Loading

0 comments on commit 7d9ff61

Please sign in to comment.