Skip to content

Commit 0c1a674

Browse files
committed
IBX-8323: Reworked RepositoryAuthenticationProvider and moved its logic to a dedicated subscriber
1 parent 232ee8a commit 0c1a674

File tree

6 files changed

+112
-504
lines changed

6 files changed

+112
-504
lines changed

phpstan-baseline.neon

Lines changed: 0 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -12275,36 +12275,6 @@ parameters:
1227512275
count: 1
1227612276
path: src/lib/MVC/Symfony/Security/Authentication/RememberMeRepositoryAuthenticationProvider.php
1227712277

12278-
-
12279-
message: "#^Dead catch \\- Ibexa\\\\Core\\\\Repository\\\\User\\\\Exception\\\\UnsupportedPasswordHashType is never thrown in the try block\\.$#"
12280-
count: 1
12281-
path: src/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProvider.php
12282-
12283-
-
12284-
message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProvider\\:\\:checkAuthentication\\(\\) has no return type specified\\.$#"
12285-
count: 1
12286-
path: src/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProvider.php
12287-
12288-
-
12289-
message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProvider\\:\\:setConstantAuthTime\\(\\) has no return type specified\\.$#"
12290-
count: 1
12291-
path: src/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProvider.php
12292-
12293-
-
12294-
message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProvider\\:\\:setPermissionResolver\\(\\) has no return type specified\\.$#"
12295-
count: 1
12296-
path: src/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProvider.php
12297-
12298-
-
12299-
message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProvider\\:\\:setUserService\\(\\) has no return type specified\\.$#"
12300-
count: 1
12301-
path: src/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProvider.php
12302-
12303-
-
12304-
message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProvider\\:\\:startConstantTimer\\(\\) has no return type specified\\.$#"
12305-
count: 1
12306-
path: src/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProvider.php
12307-
1230812278
-
1230912279
message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authorization\\\\Attribute\\:\\:__construct\\(\\) has parameter \\$function with no type specified\\.$#"
1231012280
count: 1
@@ -47095,56 +47065,6 @@ parameters:
4709547065
count: 1
4709647066
path: tests/lib/MVC/Symfony/Security/Authentication/RememberMeRepositoryAuthenticationProviderTest.php
4709747067

47098-
-
47099-
message: "#^Call to an undefined method Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserProviderInterface\\:\\:method\\(\\)\\.$#"
47100-
count: 1
47101-
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php
47102-
47103-
-
47104-
message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProviderTest\\:\\:testAuthenticationNotEzUser\\(\\) has no return type specified\\.$#"
47105-
count: 1
47106-
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php
47107-
47108-
-
47109-
message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProviderTest\\:\\:testCheckAuthentication\\(\\) has no return type specified\\.$#"
47110-
count: 1
47111-
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php
47112-
47113-
-
47114-
message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProviderTest\\:\\:testCheckAuthenticationAlreadyLoggedIn\\(\\) has no return type specified\\.$#"
47115-
count: 1
47116-
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php
47117-
47118-
-
47119-
message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProviderTest\\:\\:testCheckAuthenticationCredentialsChanged\\(\\) has no return type specified\\.$#"
47120-
count: 1
47121-
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php
47122-
47123-
-
47124-
message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProviderTest\\:\\:testCheckAuthenticationFailed\\(\\) has no return type specified\\.$#"
47125-
count: 1
47126-
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php
47127-
47128-
-
47129-
message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProviderTest\\:\\:testCheckAuthenticationFailedWhenPasswordInUnsupportedFormat\\(\\) has no return type specified\\.$#"
47130-
count: 1
47131-
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php
47132-
47133-
-
47134-
message: "#^Parameter \\#1 \\$user of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\UsernamePasswordToken constructor expects Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface, string given\\.$#"
47135-
count: 6
47136-
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php
47137-
47138-
-
47139-
message: "#^Parameter \\#3 \\$roles of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\UsernamePasswordToken constructor expects array\\<string\\>, string given\\.$#"
47140-
count: 9
47141-
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php
47142-
47143-
-
47144-
message: "#^Parameter \\#4 \\$hasherFactory of class Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProvider constructor expects Symfony\\\\Component\\\\PasswordHasher\\\\Hasher\\\\PasswordHasherFactoryInterface, PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\Security\\\\Core\\\\Encoder\\\\EncoderFactoryInterface given\\.$#"
47145-
count: 1
47146-
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php
47147-
4714847068
-
4714947069
message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\HttpUtilsTest\\:\\:checkRequestPathProvider\\(\\) has no return type specified\\.$#"
4715047070
count: 1

src/bundle/Core/DependencyInjection/Compiler/SecurityPass.php

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface;
1212
use Ibexa\Core\MVC\Symfony\Security\Authentication\DefaultAuthenticationSuccessHandler;
1313
use Ibexa\Core\MVC\Symfony\Security\Authentication\GuardRepositoryAuthenticationProvider;
14-
use Ibexa\Core\MVC\Symfony\Security\Authentication\RememberMeRepositoryAuthenticationProvider;
1514
use Ibexa\Core\MVC\Symfony\Security\HttpUtils;
1615
use Ibexa\Core\MVC\Symfony\SiteAccess;
1716
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@@ -24,28 +23,16 @@
2423
*/
2524
final class SecurityPass implements CompilerPassInterface
2625
{
27-
public const string CONSTANT_AUTH_TIME_SETTING = 'ibexa.security.authentication.constant_auth_time';
28-
29-
public const float CONSTANT_AUTH_TIME_DEFAULT = 1.0;
30-
3126
public function process(ContainerBuilder $container): void
3227
{
3328
if (
34-
!$container->hasDefinition('security.authentication.provider.rememberme') ||
3529
!$container->hasDefinition('security.authentication.provider.guard')
3630
) {
3731
return;
3832
}
3933

4034
$permissionResolverRef = new Reference(PermissionResolver::class);
4135

42-
$rememberMeAuthenticationProviderDef = $container->findDefinition('security.authentication.provider.rememberme');
43-
$rememberMeAuthenticationProviderDef->setClass(RememberMeRepositoryAuthenticationProvider::class);
44-
$rememberMeAuthenticationProviderDef->addMethodCall(
45-
'setPermissionResolver',
46-
[$permissionResolverRef]
47-
);
48-
4936
$guardAuthenticationProviderDef = $container->findDefinition('security.authentication.provider.guard');
5037
$guardAuthenticationProviderDef->setClass(GuardRepositoryAuthenticationProvider::class);
5138
$guardAuthenticationProviderDef->addMethodCall(

src/bundle/Core/Resources/config/security.yml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
parameters:
2-
# Constant authentication execution time in seconds (float). Blocks timing attacks.
3-
# Must be larger than expected real execution time, with a good margin.
4-
# If set to zero, constant time authentication is disabled. Do not do this on production environments.
5-
ibexa.security.authentication.constant_auth_time: !php/const Ibexa\Bundle\Core\DependencyInjection\Compiler\SecurityPass::CONSTANT_AUTH_TIME_DEFAULT
6-
71
services:
82
Ibexa\Core\MVC\Symfony\Security\User\UsernameProvider:
93
class: Ibexa\Core\MVC\Symfony\Security\User\UsernameProvider
@@ -47,3 +41,7 @@ services:
4741
Ibexa\Core\MVC\Symfony\Security\Authentication\EventSubscriber\AccessDeniedSubscriber:
4842
autowire: true
4943
autoconfigure: true
44+
45+
Ibexa\Core\MVC\Symfony\Security\Authentication\EventSubscriber\RepositoryUserAuthenticationSubscriber:
46+
autowire: true
47+
autoconfigure: true
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Core\MVC\Symfony\Security\Authentication\EventSubscriber;
10+
11+
use Exception;
12+
use Ibexa\Contracts\Core\Repository\Exceptions\PasswordInUnsupportedFormatException;
13+
use Ibexa\Contracts\Core\Repository\UserService;
14+
use Ibexa\Core\MVC\Symfony\Security\UserInterface as IbexaUserInterface;
15+
use Ibexa\Core\Repository\User\Exception\UnsupportedPasswordHashType;
16+
use Psr\Log\LoggerAwareTrait;
17+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
18+
use Symfony\Component\HttpFoundation\RequestStack;
19+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
20+
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
21+
22+
final class RepositoryUserAuthenticationSubscriber implements EventSubscriberInterface
23+
{
24+
use LoggerAwareTrait;
25+
26+
// Constant authentication execution time in seconds (float). Blocks timing attacks.
27+
// Must be larger than expected real execution time, with a good margin.
28+
// If set to zero, constant time authentication is disabled. Do not do this on production environments.
29+
public const float CONSTANT_AUTH_TIME_DEFAULT = 1.0;
30+
31+
public const string CONSTANT_AUTH_TIME_SETTING = 'ibexa.security.authentication.constant_auth_time';
32+
33+
private const int USLEEP_MULTIPLIER = 1000000;
34+
35+
public function __construct(
36+
private readonly RequestStack $requestStack,
37+
private readonly UserService $userService,
38+
) {
39+
}
40+
41+
public static function getSubscribedEvents(): array
42+
{
43+
return [
44+
CheckPassportEvent::class => ['validateRepositoryUser'],
45+
];
46+
}
47+
48+
public function validateRepositoryUser(CheckPassportEvent $event): void
49+
{
50+
$request = $this->requestStack->getCurrentRequest();
51+
if ($request === null) {
52+
return;
53+
}
54+
55+
$badge = $event->getPassport()->getBadge(UserBadge::class);
56+
if (!$badge instanceof UserBadge) {
57+
return;
58+
}
59+
60+
$user = $badge->getUser();
61+
if (!$user instanceof IbexaUserInterface) {
62+
return;
63+
}
64+
65+
$startTime = $this->startConstantTimer();
66+
try {
67+
$this->userService->checkUserCredentials(
68+
$user->getAPIUser(),
69+
$user->getPassword() ?? ''
70+
);
71+
72+
$event->getAuthenticator()->authenticate($request);
73+
} catch (UnsupportedPasswordHashType $exception) {
74+
$this->sleepUsingConstantTimer($startTime);
75+
76+
throw new PasswordInUnsupportedFormatException($exception);
77+
} catch (Exception $e) {
78+
$this->sleepUsingConstantTimer($startTime);
79+
80+
throw $e;
81+
}
82+
83+
$this->sleepUsingConstantTimer($startTime);
84+
}
85+
86+
private function startConstantTimer(): float
87+
{
88+
return microtime(true);
89+
}
90+
91+
private function sleepUsingConstantTimer(float $startTime): void
92+
{
93+
$remainingTime = self::CONSTANT_AUTH_TIME_DEFAULT - (microtime(true) - $startTime);
94+
if ($remainingTime > 0) {
95+
$microseconds = $remainingTime * self::USLEEP_MULTIPLIER;
96+
97+
usleep((int)$microseconds);
98+
} elseif ($this->logger) {
99+
$this->logger->warning(
100+
sprintf(
101+
'Authentication took longer than the configured constant time. Consider increasing the value of %s',
102+
self::CONSTANT_AUTH_TIME_SETTING
103+
),
104+
[__CLASS__]
105+
);
106+
}
107+
}
108+
}

src/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProvider.php

Lines changed: 0 additions & 133 deletions
This file was deleted.

0 commit comments

Comments
 (0)