8000 Integrated Guards with the Authenticator system · symfony/symfony@b0b5136 · GitHub
[go: up one dir, main page]

Skip to content

Commit b0b5136

Browse files
committed
Integrated Guards with the Authenticator system
1 parent c6cf433 commit b0b5136

File tree

4 files changed

+358
-1
lines changed

4 files changed

+358
-1
lines changed

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1616
use Symfony\Component\DependencyInjection\ChildDefinition;
1717
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\DependencyInjection\Definition;
1819
use Symfony\Component\DependencyInjection\Reference;
20+
use Symfony\Component\Security\Guard\Authenticator\GuardBridgeAuthenticator;
1921

2022
/**
2123
* Configures the "guard" authentication provider key under a firewall.
2224
*
2325
* @author Ryan Weaver <ryan@knpuniversity.com>
2426
*/
25-
class GuardAuthenticationFactory implements SecurityFactoryInterface
27+
class GuardAuthenticationFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface, EntryPointFactoryInterface
2628
{
2729
public function getPosition()
2830
{
@@ -92,6 +94,28 @@ public function create(ContainerBuilder $container, string $id, array $config, s
9294
return [$providerId, $listenerId, $entryPointId];
9395
}
9496

97+
public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId)
98+
{
99+
$userProvider = new Reference($userProviderId);
100+
$authenticatorIds = [];
101+
102+
$guardAuthenticatorIds = $config['authenticators'];
103+
foreach ($guardAuthenticatorIds as $i => $guardAuthenticatorId) {
104+
$container->setDefinition($authenticatorIds[] = 'security.authenticator.guard.'.$firewallName.'.'.$i, new Definition(GuardBridgeAuthenticator::class))
105+
->setArguments([
106+
new Reference($guardAuthenticatorId),
107+
$userProvider,
108+
]);
109+
}
110+
111+
return $authenticatorIds;
112+
}
113+
114+
public function createEntryPoint(ContainerBuilder $container, string $id, array $config, ?string $defaultEntryPointId): string
115+
{
116+
return $this->determineEntryPoint($defaultEntryPointId, $config);
117+
}
118+
95119
private function determineEntryPoint(?string $defaultEntryPointId, array $config): string
96120
{
97121
if ($defaultEntryPointId) {

src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1818
use Symfony\Component\DependencyInjection\ContainerBuilder;
1919
use Symfony\Component\DependencyInjection\Reference;
20+
use Symfony\Component\Security\Guard\Authenticator\GuardBridgeAuthenticator;
2021

2122
class GuardAuthenticationFactoryTest extends TestCase
2223
{
@@ -163,6 +164,29 @@ public function testCreate F438 WithEntryPoint()
163164
$this->assertEquals('authenticatorABC', $entryPointId);
164165
}
165166

167+
public function testAuthenticatorSystemCreate()
168+
{
169+
$container = new ContainerBuilder();
170+
$firewallName = 'my_firewall';
171+
$userProviderId = 'my_user_provider';
172+
$config = [
173+
'authenticators' => ['authenticator123'],
174+
'entry_point' => null,
175+
];
176+
$factory = new GuardAuthenticationFactory();
177+
178+
$authenticators = $factory->createAuthenticator($container, $firewallName, $config, $userProviderId);
179+
$this->assertEquals('security.authenticator.guard.my_firewall.0', $authenticators[0]);
180+
181+
$entryPointId = $factory->createEntryPoint($container, $firewallName, $config, null);
182+
$this->assertEquals('authenticator123', $entryPointId);
183+
184+
$authenticatorDefinition = $container->getDefinition('security.authenticator.guard.my_firewall.0');
185+
$this->assertEquals(GuardBridgeAuthenticator::class, $authenticatorDefinition->getClass());
186+
$this->assertEquals('authenticator123', (string) $authenticatorDefinition->getArgument(0));
187+
$this->assertEquals($userProviderId, (string) $authenticatorDefinition->getArgument(1));
188+
}
189+
166190
private function executeCreate(array $config, $defaultEntryPointId)
167191
{
168192
$container = new ContainerBuilder();
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Guard\Authenticator;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpFoundation\Response;
16+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
17+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
18+
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
19+
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
20+
use Symfony\Component\Security\Core\User\UserInterface;
21+
use Symfony\Component\Security\Core\User\UserProviderInterface;
22+
use Symfony\Component\Security\Guard\AuthenticatorInterface as GuardAuthenticatorInterface;
23+
use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
24+
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
25+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
26+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
27+
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials;
28+
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
29+
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
30+
use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface;
31+
32+
/**
33+
* This authenticator is used to bridge Guard authenticators with
34+
* the Symfony Authenticator system.
35+
*
36+
* @author Wouter de Jong <wouter@wouterj.nl>
37+
*
38+
* @internal
39+
*/
40+
class GuardBridgeAuthenticator implements InteractiveAuthenticatorInterface
41+
{
42+
private $guard;
43+
private $userProvider;
44+
45+
public function __construct(GuardAuthenticatorInterface $guard, UserProviderInterface $userProvider)
46+
{
47+
$this->guard = $guard;
48+
$this->userProvider = $userProvider;
49+
}
50+
51+
public function supports(Request $request): ?bool
52+
{
53+
return $this->guard->supports($request);
54+
}
55+
56+
public function authenticate(Request $request): PassportInterface
57+
{
58+
$credentials = $this->guard->getCredentials($request);
59+
60+
if (null === $credentials) {
61+
throw new \UnexpectedValueException(
62+
sprintf(
63+
'The return value of "%1$s::getCredentials()" must not be null. Return false from "%1$s::supports()" instead.',
64+
get_debug_type($this->guard)
65+
)
66+
);
67+
}
68+
69+
// get the user from the GuardAuthenticator
70+
$user = $this->guard->getUser($credentials, $this->userProvider);
71+
72+
if (null === $user) {
73+
throw new UsernameNotFoundException(
74+
sprintf('Null returned from "%s::getUser()".', get_debug_type($this->guard))
75+
);
76+
}
77+
78+
if (!$user instanceof UserInterface) {
79+
throw new \UnexpectedValueException(
80+
sprintf(
81+
'The "%s::getUser()" method must return a UserInterface. You returned "%s".',
82+
get_debug_type($this->guard),
83+
get_debug_type($user)
84+
)
85+
);
86+
}
87+
88+
$passport = new Passport($user, new CustomCredentials([$this->guard, 'checkCredentials'], $credentials));
89+
if ($this->userProvider instanceof PasswordUpgraderInterface && $this->guard instanceof PasswordAuthenticatedInterface && (null !== $password = $this->guard->getPassword($credentials))) {
90+
$passport->addBadge(new PasswordUpgradeBadge($password, $this->userProvider));
91+
}
92+
93+
if ($this->guard->supportsRememberMe()) {
94+
$passport->addBadge(new RememberMeBadge());
95+
}
96+
97+
return $passport;
98+
}
99+
100+
public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface
101+
{
102+
if (!$passport instanceof UserPassportInterface) {
103+
throw new \LogicException(sprintf('"%s" does not support non-user passports.', __CLASS__));
104+
}
105+
106+
return $this->guard->createAuthenticatedToken($passport->getUser(), $firewallName);
107+
}
108+
109+
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
110+
{
111+
return $this->guard->onAuthenticationSuccess($request, $token, $firewallName);
112+
}
113+
114+
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
115+
{
116+
return $this->guard->onAuthenticationFailure($request, $exception);
117+
}
118+
119+
public function isInteractive(): bool
120+
{
121+
// the GuardAuthenticationHandler always dispatches the InteractiveLoginEvent
122+
return true;
123+
}
124+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Guard\Tests\Authenticator;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpFoundation\Response;
17+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
18+
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
19+
use Symfony\Component\Security\Core\User\User;
20+
use Symfony\Component\Security\Core\User\UserProviderInterface;
21+
use Symfony\Component\Security\Guard\Authenticator\GuardBridgeAuthenticator;
22+
use Symfony\Component\Security\Guard\AuthenticatorInterface;
23+
use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;
24+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
25+
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials;
26+
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
27+
28+
class GuardBridgeAuthenticatorTest extends TestCase
29+
{
30+
private $guardAuthenticator;
31+
private $userProvider;
32+
private $authenticator;
33+
34+
protected function setUp(): void
35+
{
36+
$this->guardAuthenticator = $this->createMock(AuthenticatorInterface::class);
37+
$this->userProvider = $this->createMock(UserProviderInterface::class);
38+
$this->authenticator = new GuardBridgeAuthenticator($this->guardAuthenticator, $this->userProvider);
39+
}
40+
41+
public function testSupports()
42+
{
43+
$request = new Request();
44+
45+
$this->guardAuthenticator->expects($this->once())
46+
->method('supports')
47+
->with($request)
48+
->willReturn(true);
49+
50+
$this->assertTrue($this->authenticator->supports($request));
51+
}
52+
53+
public function testNoSupport()
54+
{
55+
$request = new Request();
56+
57+
$this->guardAuthenticator->expects($this->once())
58+
->method('supports')
59+
->with($request)
60+
->willReturn(false);
61+
62+
$this->assertFalse($this->authenticator->supports($request));
63+
}
64+
65+
public function testAuthenticate()
66+
{
67+
$request = new Request();
68+
69+
$credentials = ['password' => 's3cr3t'];
70+
$this->guardAuthenticator->expects($this->once())
71+
->method('getCredentials')
72+
->with($request)
73+
->willReturn($credentials);
74+
75+
$user = new User('test', null, ['ROLE_USER']);
76+
$this->guardAuthenticator->expects($this->once())
77+
->method('getUser')
78+
->with($credentials, $this->userProvider)
79+
->willReturn($user);
80+
81+
$passport = $this->authenticator->authenticate($request);
82+
$this->assertTrue($passport->hasBadge(CustomCredentials::class));
83+
84+
$this->guardAuthenticator->expects($this->once())
85+
->method('checkCredentials')
86+
->with($credentials, $user)
87+
->willReturn(true);
88+
89+
$passport->getBadge(CustomCredentials::class)->executeCustomChecker($user);
90+
}
91+
92+
public function testAuthenticateNoUser()
93+
{
94+
$this->expectException(UsernameNotFoundException::class);
95+
96+
$request = new Request();
97+
98+
$credentials = ['password' => 's3cr3t'];
99+
$this->guardAuthenticator->expects($this->once())
100+
->method('getCredentials')
101+
->with($request)
102+
->willReturn($credentials);
103+
104+
$this->guardAuthenticator->expects($this->once())
105+
->method('getUser')
106+
->with($credentials, $this->userProvider)
107+
->willReturn(null);
108+
109+
$this->authenticator->authenticate($request);
110+
}
111+
112+
/**
113+
* @dataProvider provideRememberMeData
114+
*/
115+
public function testAuthenticateRememberMe(bool $rememberMeSupported)
116+
{
117+
$request = new Request();
118+
119+
$credentials = ['password' => 's3cr3t'];
120+
$this->guardAuthenticator->expects($this->once())
121+
->method('getCredentials')
122+
->with($request)
123+
->willReturn($credentials);
124+
125+
$user = new User('test', null, ['ROLE_USER']);
126+
$this->guardAuthenticator->expects($this->once())
127+
->method('getUser')
128+
->with($credentials, $this->userProvider)
129+
->willReturn($user);
130+
131+
$this->guardAuthenticator->expects($this->once())
132+
->method('supportsRememberMe')
133+
->willReturn($rememberMeSupported);
134+
135+
$passport = $this->authenticator->authenticate($request);
136+
$this->assertEquals($rememberMeSupported, $passport->hasBadge(RememberMeBadge::class));
137+
}
138+
139+
public function provideRememberMeData()
140+
{
141+
yield [true];
142+
yield [false];
143+
}
144+
145+
public function testCreateAuthenticatedToken()
146+
{
147+
$user = new User('test', null, ['ROLE_USER']);
148+
149+
$token = new PostAuthenticationGuardToken($user, 'main', ['ROLE_USER']);
150+
$this->guardAuthenticator->expects($this->once())
151+
->method('createAuthenticatedToken')
152+
->with($user, 'main')
153+
->willReturn($token);
154+
155+
$this->assertSame($token, $this->authenticator->createAuthenticatedToken(new SelfValidatingPassport($user), 'main'));
156+
}
157+
158+
public function testHandleSuccess()
159+
{
160+
$request = new Request();
161+
$token = new PostAuthenticationGuardToken(new User('test', null, ['ROLE_USER']), 'main', ['ROLE_USER']);
162+
163+
$response = new Response();
164+
$this->guardAuthenticator->expects($this->once())
165+
->method('onAuthenticationSuccess')
166+
->with($request, $token)
167+
->willReturn($response);
168+
169+
$this->assertSame($response, $this->authenticator->onAuthenticationSuccess($request, $token, 'main'));
170+
}
171+
172+
public function testOnFailure()
173+
{
174+
$request = new Request();
175+
$exception = new AuthenticationException();
176+
177+
$response = new Response();
178+
$this->guardAuthenticator->expects($this->once())
179+
->method('onAuthenticationFailure')
180+
->with($request, $exception)
181+
->willReturn($response);
182+
183+
$this->assertSame($response, $this->authenticator->onAuthenticationFailure($request, $exception));
184+
}
185+
}

0 commit comments

Comments
 (0)
0