Skip to content

Commit d88c30d

Browse files
committed
Merge branch '6.0' into 6.1
* 6.0: [Mailer] Fix string-cast of exceptions thrown by authenticator in EsmtpTransport [Cache] fix error handling [Security] Fix wrong authenticator class in debug logs 43460: add missing validator component turkish translations Fix generic type for FormErrorIterator
2 parents 1211039 + a53af95 commit d88c30d

File tree

10 files changed

+103
-18
lines changed

10 files changed

+103
-18
lines changed

src/Symfony/Component/Cache/Adapter/AbstractAdapter.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,12 @@ public function commit(): bool
151151
$retry = $this->deferred = [];
152152

153153
if ($expiredIds) {
154-
$this->doDelete($expiredIds);
154+
try {
155+
$this->doDelete($expiredIds);
156+
} catch (\Exception $e) {
157+
$ok = false;
158+
CacheItem::log($this->logger, 'Failed to delete expired items: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]);
159+
}
155160
}
156161
foreach ($byLifetime as $lifetime => $values) {
157162
try {

src/Symfony/Component/Cache/Adapter/AbstractTagAwareAdapter.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,12 @@ public function commit(): bool
175175

176176
if ($expiredIds) {
177177
// Tags are not cleaned up in this case, however that is done on invalidateTags().
178-
$this->doDelete($expiredIds);
178+
try {
179+
$this->doDelete($expiredIds);
180+
} catch (\Exception $e) {
181+
$ok = false;
182+
CacheItem::log($this->logger, 'Failed to delete expired items: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]);
183+
}
179184
}
180185
foreach ($byLifetime as $lifetime => $values) {
181186
try {
@@ -289,8 +294,12 @@ public function invalidateTags(array $tags): bool
289294
$tagIds[] = $this->getId(self::TAGS_PREFIX.$tag);
290295
}
291296

292-
if ($this->doInvalidate($tagIds)) {
293-
return true;
297+
try {
298+
if ($this->doInvalidate($tagIds)) {
299+
return true;
300+
}
301+
} catch (\Exception $e) {
302+
CacheItem::log($this->logger, 'Failed to invalidate tags: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]);
294303
}
295304

296305
return false;

src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Predis\Connection\Aggregate\ClusterInterface;
1515
use Predis\Connection\Aggregate\PredisCluster;
1616
use Predis\Connection\Aggregate\ReplicationInterface;
17+
use Predis\Response\ErrorInterface;
1718
use Predis\Response\Status;
1819
use Symfony\Component\Cache\CacheItem;
1920
use Symfony\Component\Cache\Exception\InvalidArgumentException;
@@ -58,6 +59,7 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
5859
* detected eviction policy used on Redis server.
5960
*/
6061
private string $redisEvictionPolicy;
62+
private string $namespace;
6163

6264
public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
6365
{
@@ -76,6 +78,7 @@ public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInter
7678
}
7779

7880
$this->init($redis, $namespace, $defaultLifetime, new TagAwareMarshaller($marshaller));
81+
$this->namespace = $namespace;
7982
}
8083

8184
/**
@@ -159,7 +162,7 @@ protected function doDeleteYieldTags(array $ids): iterable
159162
});
160163

161164
foreach ($results as $id => $result) {
162-
if ($result instanceof \RedisException) {
165+
if ($result instanceof \RedisException || $result instanceof ErrorInterface) {
163166
CacheItem::log($this->logger, 'Failed to delete key "{key}": '.$result->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $result]);
164167

165168
continue;
@@ -253,7 +256,7 @@ protected function doInvalidate(array $tagIds): bool
253256

254257
$success = true;
255258
foreach ($results as $id => $values) {
256-
if ($values instanceof \RedisException) {
259+
if ($values instanceof \RedisException || $values instanceof ErrorInterface) {
257260
CacheItem::log($this->logger, 'Failed to invalidate key "{key}": '.$values->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $values]);
258261
$success = false;
259262

@@ -302,6 +305,11 @@ private function getRedisEvictionPolicy(): string
302305

303306
foreach ($hosts as $host) {
304307
$info = $host->info('Memory');
308+
309+
if ($info instanceof ErrorInterface) {
310+
continue;
311+
}
312+
305313
$info = $info['Memory'] ?? $info;
306314

307315
return $this->redisEvictionPolicy = $info['maxmemory_policy'];

src/Symfony/Component/Cache/Traits/RedisTrait.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Predis\Connection\Aggregate\ClusterInterface;
1616
use Predis\Connection\Aggregate\RedisCluster;
1717
use Predis\Connection\Aggregate\ReplicationInterface;
18+
use Predis\Response\ErrorInterface;
1819
use Predis\Response\Status;
1920
use Symfony\Component\Cache\Exception\CacheException;
2021
use Symfony\Component\Cache\Exception\InvalidArgumentException;
@@ -401,7 +402,7 @@ protected function doClear(string $namespace): bool
401402
}
402403

403404
$info = $host->info('Server');
404-
$info = $info['Server'] ?? $info;
405+
$info = !$info instanceof ErrorInterface ? $info['Server'] ?? $info : ['redis_version' => '2.0'];
405406

406407
if (!$host instanceof \Predis\ClientInterface) {
407408
$prefix = \defined('Redis::SCAN_PREFIX') && (\Redis::SCAN_PREFIX & $host->getOption(\Redis::OPT_SCAN)) ? '' : $host->getOption(\Redis::OPT_PREFIX);

src/Symfony/Component/Form/FormErrorIterator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
* @author Bernhard Schussek <bschussek@gmail.com>
3131
*
3232
* @implements \ArrayAccess<int, FormError|FormErrorIterator>
33-
* @implements \RecursiveIterator<int, FormError>
34-
* @implements \SeekableIterator<int, FormError>
33+
* @implements \RecursiveIterator<int, FormError|FormErrorIterator>
34+
* @implements \SeekableIterator<int, FormError|FormErrorIterator>
3535
*/
3636
class FormErrorIterator implements \RecursiveIterator, \SeekableIterator, \ArrayAccess, \Countable
3737
{

src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ private function handleAuth(array $modes): void
179179
}
180180

181181
// keep the error message, but tries the other authenticators
182-
$errors[$authenticator->getAuthKeyword()] = $e;
182+
$errors[$authenticator->getAuthKeyword()] = $e->getMessage();
183183
}
184184
}
185185

src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
2626
use Symfony\Component\Security\Core\User\UserInterface;
2727
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
28+
use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticator;
2829
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
2930
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface;
3031
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
@@ -145,14 +146,14 @@ private function executeAuthenticators(array $authenticators, Request $request):
145146
// eagerly (before token storage is initialized), whereas authenticate() is called
146147
// lazily (after initialization).
147148
if (false === $authenticator->supports($request)) {
148-
$this->logger?->debug('Skipping the "{authenticator}" authenticator as it did not support the request.', ['authenticator' => \get_class($authenticator)]);
149+
$this->logger?->debug('Skipping the "{authenticator}" authenticator as it did not support the request.', ['authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]);
149150

150151
continue;
151152
}
152153

153154
$response = $this->executeAuthenticator($authenticator, $request);
154155
if (null !== $response) {
155-
$this->logger?->debug('The "{authenticator}" authenticator set the response. Any later authenticator will not be called', ['authenticator' => \get_class($authenticator)]);
156+
$this->logger?->debug('The "{authenticator}" authenticator set the response. Any later authenticator will not be called', ['authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]);
156157

157158
return $response;
158159
}
@@ -200,7 +201,7 @@ private function executeAuthenticator(AuthenticatorInterface $authenticator, Req
200201

201202
$this->eventDispatcher->dispatch(new AuthenticationSuccessEvent($authenticatedToken), AuthenticationEvents::AUTHENTICATION_SUCCESS);
202203

203-
$this->logger?->info('Authenticator successful!', ['token' => $authenticatedToken, 'authenticator' => \get_class($authenticator)]);
204+
$this->logger?->info('Authenticator successful!', ['token' => $authenticatedToken, 'authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]);
204205
} catch (AuthenticationException $e) {
205206
// oh no! Authentication failed!
206207
$response = $this->handleAuthenticationFailure($e, $request, $authenticator, $passport);
@@ -217,7 +218,7 @@ private function executeAuthenticator(AuthenticatorInterface $authenticator, Req
217218
return $response;
218219
}
219220

220-
$this->logger?->debug('Authenticator set no success response: request continues.', ['authenticator' => \get_class($authenticator)]);
221+
$this->logger?->debug('Authenticator set no success response: request continues.', ['authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]);
221222

222223
return null;
223224
}
@@ -242,7 +243,7 @@ private function handleAuthenticationSuccess(TokenInterface $authenticatedToken,
242243
*/
243244
private function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, AuthenticatorInterface $authenticator, ?Passport $passport): ?Response
244245
{
245-
$this->logger?->info('Authenticator failed.', ['exception' => $authenticationException, 'authenticator' => \get_class($authenticator)]);
246+
$this->logger?->info('Authenticator failed.', ['exception' => $authenticationException, 'authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]);
246247

247248
// Avoid leaking error details in case of invalid user (e.g. user not found or invalid account status)
248249
// to prevent user enumeration via response content comparison
@@ -252,7 +253,7 @@ private function handleAuthenticationFailure(AuthenticationException $authentica
252253

253254
$response = $authenticator->onAuthenticationFailure($request, $authenticationException);
254255
if (null !== $response && null !== $this->logger) {
255-
$this->logger->debug('The "{authenticator}" authenticator set the failure response.', ['authenticator' => \get_class($authenticator)]);
256+
$this->logger->debug('The "{authenticator}" authenticator set the failure response.', ['authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]);
256257
}
257258

258259
$this->eventDispatcher->dispatch($loginFailureEvent = new LoginFailureEvent($authenticationException, $authenticator, $request, $response, $this->firewallName, $passport));

src/Symfony/Component/Security/Http/Authenticator/Debug/TraceableAuthenticator.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ public function isInteractive(): bool
9292
return $this->authenticator instanceof InteractiveAuthenticatorInterface && $this->authenticator->isInteractive();
9393
}
9494

95+
/**
96+
* @internal
97+
*/
98+
public function getAuthenticator(): AuthenticatorInterface
99+
{
100+
return $this->authenticator;
101+
}
102+
95103
public function __call($method, $args)
96104
{
97105
return $this->authenticator->{$method}(...$args);

src/Symfony/Component/Security/Http/Tests/Authentication/AuthenticatorManagerTest.php

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
namespace Symfony\Component\Security\Http\Tests\Authentication;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Psr\Log\AbstractLogger;
16+
use Psr\Log\LoggerInterface;
1517
use Symfony\Component\EventDispatcher\EventDispatcher;
1618
use Symfony\Component\HttpFoundation\Request;
1719
use Symfony\Component\HttpFoundation\Response;
@@ -22,6 +24,7 @@
2224
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
2325
use Symfony\Component\Security\Core\User\InMemoryUser;
2426
use Symfony\Component\Security\Http\Authentication\AuthenticatorManager;
27+
use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticator;
2528
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
2629
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
2730
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
@@ -310,6 +313,44 @@ public function testAuthenticateRequestHidesInvalidUserExceptions()
310313
$this->assertSame($this->response, $response);
311314
}
312315

316+
public function testLogsUseTheDecoratedAuthenticatorWhenItIsTraceable()
317+
{
318+
$authenticator = $this->createMock(TestInteractiveAuthenticator::class);
319+
$authenticator->expects($this->any())->method('isInteractive')->willReturn(true);
320+
$this->request->attributes->set('_security_authenticators', [new TraceableAuthenticator($authenticator)]);
321+
322+
$authenticator->expects($this->any())->method('authenticate')->willReturn(new SelfValidatingPassport(new UserBadge('wouter', function () { return $this->user; })));
323+
$authenticator->expects($this->any())->method('createToken')->willReturn($this->token);
324+
325+
$this->tokenStorage->expects($this->once())->method('setToken')->with($this->token);
326+
327+
$authenticator->expects($this->any())
328+
->method('onAuthenticationSuccess')
329+
->with($this->anything(), $this->token, 'main')
330+
->willReturn($this->response);
331+
332+
$authenticator->expects($this->any())
333+
->method('onAuthenticationSuccess')
334+
->with($this->anything(), $this->token, 'main')
335+
->willReturn($this->response);
336+
337+
$logger = new class() extends AbstractLogger {
338+
public $logContexts = [];
339+
340+
public function log($level, $message, array $context = []): void
341+
{
342+
if ($context['authenticator'] ?? false) {
343+
$this->logContexts[] = $context;
344+
}
345+
}
346+
};
347+
348+
$manager = $this->createManager([$authenticator], 'main', true, [], $logger);
349+
$response = $manager->authenticateRequest($this->request);
350+
$this->assertSame($this->response, $response);
351+
$this->assertStringContainsString('Mock_TestInteractiveAuthenticator', $logger->logContexts[0]['authenticator']);
352+
}
353+
313354
private function createAuthenticator($supports = true)
314355
{
315356
$authenticator = $this->createMock(TestInteractiveAuthenticator::class);
@@ -318,9 +359,9 @@ private function createAuthenticator($supports = true)
318359
return $authenticator;
319360
}
320361

321-
private function createManager($authenticators, $firewallName = 'main', $eraseCredentials = true, array $requiredBadges = [])
362+
private function createManager($authenticators, $firewallName = 'main', $eraseCredentials = true, array $requiredBadges = [], LoggerInterface $logger = null)
322363
{
323-
return new AuthenticatorManager($authenticators, $this->tokenStorage, $this->eventDispatcher, $firewallName, null, $eraseCredentials, true, $requiredBadges);
364+
return new AuthenticatorManager($authenticators, $this->tokenStorage, $this->eventDispatcher, $firewallName, $logger, $eraseCredentials, true, $requiredBadges);
324365
}
325366
}
326367

src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,18 @@
390390
<source>This value should be a valid expression.</source>
391391
<target>Bu değer geçerli bir ifade olmalıdır.</target>
392392
</trans-unit>
393+
<trans-unit id="101">
394+
<source>This value is not a valid CSS color.</source>
395+
<target>Bu değer geçerli bir CSS rengi değil.</target>
396+
</trans-unit>
397+
<trans-unit id="102">
398+
<source>This value is not a valid CIDR notation.</source>
399+
<target>Bu değer geçerli bir CIDR yazımı değil.</target>
400+
</trans-unit>
401+
<trans-unit id="103">
402+
<source>The value of the netmask should be between {{ min }} and {{ max }}.</source>
403+
<target>Netmask'in değeri {{ min }} ve {{ max }} arasında olmaldır.</target>
404+
</trans-unit>
393405
</body>
394406
</file>
395407
</xliff>

0 commit comments

Comments
 (0)