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

Skip to content

Commit 8708a6c

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

File tree

4 files changed

+349
-1
lines changed

4 files changed

+349
-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 testCreateWithEntryPoint()
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: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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(sprintf('The return value of "%1$s::getCredentials()" must not be null. Return false from "%1$s::supports()" instead.', get_debug_type($this->guard)));
62+
}
63+
64+
// get the user from the GuardAuthenticator
65+
$user = $this->guard->getUser($credentials, $this->userProvider);
66+
67+
if (null === $user) {
68+
throw new UsernameNotFoundException(sprintf('Null returned from "%s::getUser()".', get_debug_type($this->guard)));
69+
}
70+
71+
if (!$user instanceof UserInterface) {
72+
throw new \UnexpectedValueException(sprintf('The "%s::getUser()" method must return a UserInterface. You returned "%s".', get_debug_type($this->guard), get_debug_type($user)));
73+
}
74+
75+
$passport = new Passport($user, new CustomCredentials([$this->guard, 'checkCredentials'], $credentials));
76+
if ($this->userProvider instanceof PasswordUpgraderInterface && $this->guard instanceof PasswordAuthenticatedInterface && (null !== $password = $this->guard->getPassword($credentials))) {
77+
$passport->addBadge(new PasswordUpgradeBadge($password, $this->userProvider));
78+
}
79+
80+
if ($this->guard->supportsRememberMe()) {
81+
$passport->addBadge(new RememberMeBadge());
82+
}
83+
84+
return $passport;
85+
}
86+
87+
public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface
88+
{
89+
if (!$passport instanceof UserPassportInterface) {
90+
throw new \LogicException(sprintf('"%s" does not support non-user passports.', __CLASS__));
91+
}
92+
93+
return $this->guard->createAuthenticatedToken($passport->getUser(), $firewallName);
94+
}
95+
96+
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
97+
{
98+
return $this->guard->onAuthenticationSuccess($request, $token, $firewallName);
99+
}
100+
101+
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
102+
{
103+
return $this->guard->onAuthenticationFailure($request, $exception);
104+
}
105+
106+
public function isInteractive(): bool
107+
{
108+
// the GuardAuthenticationHandler always dispatches the InteractiveLoginEvent
109+
return true;
110+
}
111+
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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+
if (!interface_exists(\Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface::class)) {
37+
$this->markTestSkipped('Authenticator system not installed.');
38+
}
39+
40+
$this->guardAuthenticator = $this->createMock(AuthenticatorInterface::class);
41+
$this->userProvider = $this->createMock(UserProviderInterface::class);
42+
$this->authenticator = new GuardBridgeAuthenticator($this->guardAuthenticator, $this->userProvider);
43+
}
44+
45+
public function testSupports()
46+
{
47+
$request = new Request();
48+
49+
$this->guardAuthenticator->expects($this->once())
50+
->method('supports')
51+
->with($request)
52+
->willReturn(true);
53+
54+
$this->assertTrue($this->authenticator->supports($request));
55+
}
56+
57+
public function testNoSupport()
58+
{
59+
$request = new Request();
60+
61+
$this->guardAuthenticator->expects($this->once())
62+
->method('supports')
63+
->with($request)
64+
->willReturn(false);
65+
66+
$this->assertFalse($this->authenticator->supports($request));
67+
}
68+
69+
public function testAuthenticate()
70+
{
71+
$request = new Request();
72+
73+
$credentials = ['password' => 's3cr3t'];
74+
$this->guardAuthenticator->expects($this->once())
75+
->method('getCredentials')
76+
->with($request)
77+
->willReturn($cr 10000 edentials);
78+
79+
$user = new User('test', null, ['ROLE_USER']);
80+
$this->guardAuthenticator->expects($this->once())
81+
->method('getUser')
82+
->with($credentials, $this->userProvider)
83+
->willReturn($user);
84+
85+
$passport = $this->authenticator->authenticate($request);
86+
$this->assertTrue($passport->hasBadge(CustomCredentials::class));
87+
88+
$this->guardAuthenticator->expects($this->once())
89+
->method('checkCredentials')
90+
->with($credentials, $user)
91+
->willReturn(true);
92+
93+
$passport->getBadge(CustomCredentials::class)->executeCustomChecker($user);
94+
}
95+
96+
public function testAuthenticateNoUser()
97+
{
98+
$this->expectException(UsernameNotFoundException::class);
99+
100+
$request = new Request();
101+
102+
$credentials = ['password' => 's3cr3t'];
103+
$this->guardAuthenticator->expects($this->once())
104+
->method('getCredentials')
105+
->with($request)
106+
->willReturn($credentials);
107+
108+
$this->guardAuthenticator->expects($this->once())
109+
->method('getUser')
110+
->with($credentials, $this->userProvider)
111+
->willReturn(null);
112+
113+
$this->authenticator->authenticate($request);
114+
}
115+
116+
/**
117+
* @dataProvider provideRememberMeData
118+
*/
119+
public function testAuthenticateRememberMe(bool $rememberMeSupported)
120+
{
121+
$request = new Request();
122+
123+
$credentials = ['password' => 's3cr3t'];
124+
$this->guardAuthenticator->expects($this->once())
125+
->method('getCredentials')
< B41A div aria-hidden="true" class="position-absolute top-0 d-flex user-select-none DiffLineTableCellParts-module__comment-indicator--eI0hb">
126+
->with($request)
127+
->willReturn($credentials);
128+
129+
$user = new User('test', null, ['ROLE_USER']);
130+
$this->guardAuthenticator->expects($this->once())
131+
->method('getUser')
132+
->with($credentials, $this->userProvider)
133+
->willReturn($user);
134+
135+
$this->guardAuthenticator->expects($this->once())
136+
->method('supportsRememberMe')
137+
->willReturn($rememberMeSupported);
138+
139+
$passport = $this->authenticator->authenticate($request);
140+
$this->assertEquals($rememberMeSupported, $passport->hasBadge(RememberMeBadge::class));
141+
}
142+
143+
public function provideRememberMeData()
144+
{
145+
yield [true];
146+
yield [false];
147+
}
148+
149+
public function testCreateAuthenticatedToken()
150+
{
151+
$user = new User('test', null, ['ROLE_USER']);
152+
153+
$token = new PostAuthenticationGuardToken($user, 'main', ['ROLE_USER']);
154+
$this->guardAuthenticator->expects($this->once())
155+
->method('createAuthenticatedToken')
156+
->with($user, 'main')
157+
->willReturn($token);
158+
159+
$this->assertSame($token, $this->authenticator->createAuthenticatedToken(new SelfValidatingPassport($user), 'main'));
160+
}
161+
162+
public function testHandleSuccess()
163+
{
164+
$request = new Request();
165+
$token = new PostAuthenticationGuardToken(new User('test', null, ['ROLE_USER']), 'main', ['ROLE_USER']);
166+
167+
$response = new Response();
168+
$this->guardAuthenticator->expects($this->once())
169+
->method('onAuthenticationSuccess')
170+
->with($request, $token)
171+
->willReturn($response);
172+
173+
$this->assertSame($response, $this->authenticator->onAuthenticationSuccess($request, $token, 'main'));
174+
}
175+
176+
public function testOnFailure()
177+
{
178+
$request = new Request();
179+
$exception = new AuthenticationException();
180+
181+
$response = new Response();
182+
$this->guardAuthenticator->expects($this->once())
183+
->method('onAuthenticationFailure')
184+
->with($request, $exception 57DA )
185+
->willReturn($response);
186+
187+
$this->assertSame($response, $this->authenticator->onAuthenticationFailure($request, $exception));
188+
}
189+
}

0 commit comments

Comments
 (0)
0