Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 0a155cd

Browse filesBrowse files
committed
Rebase symfony#26050 on current master
1 parent 50c22b3 commit 0a155cd
Copy full SHA for 0a155cd

File tree

5 files changed

+346
-11
lines changed
Filter options

5 files changed

+346
-11
lines changed

‎src/Symfony/Component/Security/Core/Authentication/AuthenticationProviderManager.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Core/Authentication/AuthenticationProviderManager.php
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
1818
use Symfony\Component\Security\Core\AuthenticationEvents;
1919
use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent;
20+
use Symfony\Component\Security\Core\Event\AuthenticationSensitiveEvent;
2021
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
2122
use Symfony\Component\Security\Core\Exception\AccountStatusException;
2223
use Symfony\Component\Security\Core\Exception\AuthenticationException;
@@ -66,6 +67,7 @@ public function authenticate(TokenInterface $token)
6667
{
6768
$lastException = null;
6869
$result = null;
70+
$providerClassName = null;
6971

7072
foreach ($this->providers as $provider) {
7173
if (!$provider instanceof AuthenticationProviderInterface) {
@@ -80,6 +82,7 @@ public function authenticate(TokenInterface $token)
8082
$result = $provider->authenticate($token);
8183

8284
if (null !== $result) {
85+
$providerClassName = get_class($provider);
8386
break;
8487
}
8588
} catch (AccountStatusException $e) {
@@ -92,6 +95,10 @@ public function authenticate(TokenInterface $token)
9295
}
9396

9497
if (null !== $result) {
98+
if (null !== $this->eventDispatcher) {
99+
$this->eventDispatcher->dispatch(new AuthenticationSensitiveEvent($token, $result, $providerClassName), AuthenticationEvents::AUTHENTICATION_SUCCESS_SENSITIVE);
100+
}
101+
95102
if (true === $this->eraseCredentials) {
96103
$result->eraseCredentials();
97104
}

‎src/Symfony/Component/Security/Core/AuthenticationEvents.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Core/AuthenticationEvents.php
+24-1Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,32 @@
1313

1414
final class AuthenticationEvents
1515
{
16+
/**
17+
* The AUTHENTICATION_SUCCESS_SENSITIVE event occurs after a user is
18+
* authenticated by one provider. It is dispatched immediately *prior* to
19+
* the companion AUTHENTICATION_SUCCESS event.
20+
*
21+
* This event *does* contain user credentials and other sensitive data. This
22+
* enables rehashing and other credentials-aware actions. Listeners and
23+
* subscribers of this event carry the added responsibility of passing
24+
* around sensitive data and usage should be limited to cases where this
25+
* extra information is explicitly utilized; otherwise, use the
26+
* AUTHENTICATION_SUCCESS event instead.
27+
*
28+
* @Event("Symfony\Component\Security\Core\Event\AuthenticationSensitiveEvent")
29+
*/
30+
const AUTHENTICATION_SUCCESS_SENSITIVE = 'security.authentication.success_sensitive';
31+
1632
/**
1733
* The AUTHENTICATION_SUCCESS event occurs after a user is authenticated
18-
* by one provider.
34+
* by one provider. It is dispatched immediately *after* the companion
35+
* AUTHENTICATION_SUCCESS_SENSITIVE event.
36+
*
37+
* This event does *not* contain user credentials and other sensitive data
38+
* by default. Listeners and subscribers of this event are shielded from
39+
* the added responsibility of passing around sensitive data and this event
40+
* should be used unless such extra information is required; use the
41+
* AUTHENTICATION_SUCCESS_SENSITIVE event instead if this is the case.
1942
*
2043
* @Event("Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent")
2144
*/
+97Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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\Core\Event;
13+
14+
use Symfony\Component\EventDispatcher\Event;
15+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
16+
17+
/**
18+
* This is an authentication event that includes sensitive data.
19+
*
20+
* @author Rob Frawley 2nd <rmf@src.run>
21+
*/
22+
class AuthenticationSensitiveEvent extends Event
23+
{
24+
private $preAuthenticationToken;
25+
private $authenticationToken;
26+
private $authenticationProviderClassName;
27+
28+
public function __construct(TokenInterface $preAuthenticationToken, TokenInterface $authenticationToken, ?string $authenticationProviderClassName = null)
29+
{
30+
$this->preAuthenticationToken = $preAuthenticationToken;
31+
$this->authenticationToken = $authenticationToken;
32+
$this->authenticationProviderClassName = $authenticationProviderClassName;
33+
}
34+
35+
public function getPreAuthenticationToken(): TokenInterface
36+
{
37+
return $this->preAuthenticationToken;
38+
}
39+
40+
public function getAuthenticationToken(): TokenInterface
41+
{
42+
return $this->authenticationToken;
43+
}
44+
45+
public function getAuthenticationProviderClassName(): ?string
46+
{
47+
return $this->authenticationProviderClassName;
48+
}
49+
50+
/**
51+
* Tries to extract the credentials password, first from the post-auth token and second from the pre-auth token.
52+
* It uses either a custom extraction closure (optionally passed as its first and only argument) or the default
53+
* extraction implementation. The default extractor fetches the token's credentials and directly returns it if
54+
* the value is a scalar or object that implements a "__toString()" method. If the credentials val is an array
55+
* the first "password", "api_key", "api-key", or "secret" index value (that exists and is non-false after being
56+
* cast to a sting using the prior described method) is returned. Lastly, if none of the previous conditions are
57+
* met, "null" is returned.
58+
*
59+
* @param \Closure|null $extractor An optional custom token credentials password extraction \Closure that is
60+
* provided an auth token (as an instance of TokenInterface) and an auth event
61+
* (as an instance of AuthenticationSensitiveEvent). This closure is called
62+
* first with the final-auth token and second with the pre-auth token, returning
63+
* early if a non-null/non-empty scalar/castable-object value is returned.
64+
*
65+
* @return null|string Either a credentials password/secret/auth_key is returned or null on extraction failure
66+
*/
67+
public function getAuthenticationTokenPassword(?\Closure $extractor = null): ?string
68+
{
69+
$extractor = $extractor ?? function (TokenInterface $token): ?string {
70+
return $this->tryCoercibleCredentialsPasswordToString($credentials = $token->getCredentials())
71+
?: $this->tryArrayFindCredentialsPasswordToString($credentials);
72+
};
73+
74+
return ($extractor($this->authenticationToken, $this) ?: null)
75+
?: ($extractor($this->preAuthenticationToken, $this) ?: null);
76+
}
77+
78+
private function tryCoercibleCredentialsPasswordToString($credentials): ?string
79+
{
80+
return is_scalar($credentials) || method_exists($credentials, '__toString')
81+
? $credentials
82+
: null;
83+
}
84+
85+
private function tryArrayFindCredentialsPasswordToString($credentials): ?string
86+
{
87+
if (is_array($credentials)) {
88+
foreach (array('password', 'api_key', 'api-key', 'secret') as $index) {
89+
if ($c = $this->tryCoercibleCredentialsPasswordToString($credentials[$index] ?? null)) {
90+
return $c;
91+
}
92+
}
93+
}
94+
95+
return null;
96+
}
97+
}

‎src/Symfony/Component/Security/Core/Tests/Authentication/AuthenticationProviderManagerTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Core/Tests/Authentication/AuthenticationProviderManagerTest.php
+43-10Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@
1212
namespace Symfony\Component\Security\Core\Tests\Authentication;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\EventDispatcher\EventDispatcher;
16+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
1517
use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
1618
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
1719
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
20+
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
1821
use Symfony\Component\Security\Core\AuthenticationEvents;
1922
use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent;
2023
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
24+
use Symfony\Component\Security\Core\Event\AuthenticationSensitiveEvent;
2125
use Symfony\Component\Security\Core\Exception\AccountStatusException;
2226
use Symfony\Component\Security\Core\Exception\AuthenticationException;
2327
use Symfony\Component\Security\Core\Exception\ProviderNotFoundException;
@@ -152,29 +156,58 @@ public function testAuthenticateDispatchesAuthenticationFailureEvent()
152156
}
153157
}
154158

155-
public function testAuthenticateDispatchesAuthenticationSuccessEvent()
159+
public function testAuthenticateDispatchesAuthenticationSuccessEvents()
156160
{
157-
$token = new UsernamePasswordToken('foo', 'bar', 'key');
161+
$finalToken = new UsernamePasswordToken('foo', 'bar', 'baz', array('role-01', 'role-02'));
162+
$priorToken = new UsernamePasswordToken('foo', 'bar', 'baz');
158163

159-
$provider = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface')->getMock();
160-
$provider->expects($this->once())->method('supports')->willReturn(true);
161-
$provider->expects($this->once())->method('authenticate')->willReturn($token);
164+
$provider = $this->getAuthenticationProvider(true, $finalToken);
165+
$providerCN = get_class($provider);
162166

163-
$dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
167+
$dispatcher = $this->getMockBuilder(EventDispatcherInterface::class)->getMock();
164168
$dispatcher
165-
->expects($this->once())
169+
->expects($this->exactly(2))
166170
->method('dispatch')
167-
->with($this->equalTo(new AuthenticationSuccessEvent($token)), AuthenticationEvents::AUTHENTICATION_SUCCESS);
171+
->withConsecutive([
172+
$this->equalTo(new AuthenticationSensitiveEvent($priorToken, $finalToken, $providerCN)), AuthenticationEvents::AUTHENTICATION_SUCCESS_SENSITIVE,
173+
], [
174+
$this->equalTo(new AuthenticationSuccessEvent($finalToken)), AuthenticationEvents::AUTHENTICATION_SUCCESS,
175+
]);
176+
177+
$manager = new AuthenticationProviderManager(array($provider));
178+
$manager->setEventDispatcher($dispatcher);
179+
180+
$this->assertSame($finalToken, $manager->authenticate($priorToken));
181+
}
182+
183+
public function testAuthenticateDispatchesAuthenticationSuccessEventsWithCredentialsAvailableAndRemovedForSuccessiveDispatches()
184+
{
185+
$finalToken = new UsernamePasswordToken('foo', 'bar', 'baz', array('role-01', 'role-02'));
186+
$priorToken = new UsernamePasswordToken('foo', 'bar', 'baz');
187+
188+
$provider = $this->getAuthenticationProvider(true, $finalToken);
189+
$providerCN = get_class($provider);
190+
191+
$dispatcher = new EventDispatcher();
192+
$dispatcher->addListener(AuthenticationEvents::AUTHENTICATION_SUCCESS_SENSITIVE, function (AuthenticationSensitiveEvent $event) use ($providerCN) {
193+
$this->assertSame($providerCN, $event->getAuthenticationProviderClassName());
194+
$this->assertSame('bar', $event->getAuthenticationTokenPassword());
195+
$this->assertEquals('bar', $event->getPreAuthenticationToken()->getCredentials());
196+
$this->assertEquals('bar', $event->getAuthenticationToken()->getCredentials());
197+
});
198+
$dispatcher->addListener(AuthenticationEvents::AUTHENTICATION_SUCCESS, function (AuthenticationSuccessEvent $event) {
199+
$this->assertEquals('', $event->getAuthenticationToken()->getCredentials());
200+
});
168201

169202
$manager = new AuthenticationProviderManager([$provider]);
170203
$manager->setEventDispatcher($dispatcher);
171204

172-
$this->assertSame($token, $manager->authenticate($token));
205+
$this->assertSame($finalToken, $manager->authenticate($priorToken));
173206
}
174207

175208
protected function getAuthenticationProvider($supports, $token = null, $exception = null)
176209
{
177-
$provider = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface')->getMock();
210+
$provider = $this->getMockBuilder(AuthenticationProviderInterface::class)->getMock();
178211
$provider->expects($this->once())
179212
->method('supports')
180213
->will($this->returnValue($supports))

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.