Skip to content

Commit

Permalink
IBX-8323: Reworked RepositoryAuthenticationProvider and moved its log…
Browse files Browse the repository at this point in the history
…ic to a dedicated subscriber (#396)

For more details see https://issues.ibexa.co/browse/IBX-8323 and #396

Key changes:

* Reworked RepositoryAuthenticationProvider and moved its logic to a dedicated subscriber
  • Loading branch information
konradoboza authored Jul 9, 2024
1 parent d6b1921 commit fe4c34a
Show file tree
Hide file tree
Showing 10 changed files with 272 additions and 687 deletions.
95 changes: 0 additions & 95 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -12235,41 +12235,6 @@ parameters:
count: 1
path: src/lib/MVC/Symfony/Routing/UrlWildcardRouter.php

-
message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RememberMeRepositoryAuthenticationProvider\\:\\:setPermissionResolver\\(\\) has no return type specified\\.$#"
count: 1
path: src/lib/MVC/Symfony/Security/Authentication/RememberMeRepositoryAuthenticationProvider.php

-
message: "#^Dead catch \\- Ibexa\\\\Core\\\\Repository\\\\User\\\\Exception\\\\UnsupportedPasswordHashType is never thrown in the try block\\.$#"
count: 1
path: src/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProvider.php

-
message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProvider\\:\\:checkAuthentication\\(\\) has no return type specified\\.$#"
count: 1
path: src/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProvider.php

-
message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProvider\\:\\:setConstantAuthTime\\(\\) has no return type specified\\.$#"
count: 1
path: src/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProvider.php

-
message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProvider\\:\\:setPermissionResolver\\(\\) has no return type specified\\.$#"
count: 1
path: src/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProvider.php

-
message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProvider\\:\\:setUserService\\(\\) has no return type specified\\.$#"
count: 1
path: src/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProvider.php

-
message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProvider\\:\\:startConstantTimer\\(\\) has no return type specified\\.$#"
count: 1
path: src/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProvider.php

-
message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authorization\\\\Attribute\\:\\:__construct\\(\\) has parameter \\$function with no type specified\\.$#"
count: 1
Expand Down Expand Up @@ -47030,66 +46995,6 @@ parameters:
count: 1
path: tests/lib/MVC/Symfony/Security/Authentication/GuardRepositoryAuthenticationProviderTest.php

-
message: "#^Call to an undefined method Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\TokenInterface\\:\\:getFirewallName\\(\\)\\.$#"
count: 1
path: tests/lib/MVC/Symfony/Security/Authentication/RememberMeRepositoryAuthenticationProviderTest.php

-
message: "#^Call to an undefined method Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\TokenInterface\\:\\:getSecret\\(\\)\\.$#"
count: 1
path: tests/lib/MVC/Symfony/Security/Authentication/RememberMeRepositoryAuthenticationProviderTest.php

-
message: "#^Call to an undefined method Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserProviderInterface\\:\\:method\\(\\)\\.$#"
count: 1
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php

-
message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProviderTest\\:\\:testAuthenticationNotEzUser\\(\\) has no return type specified\\.$#"
count: 1
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php

-
message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProviderTest\\:\\:testCheckAuthentication\\(\\) has no return type specified\\.$#"
count: 1
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php

-
message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProviderTest\\:\\:testCheckAuthenticationAlreadyLoggedIn\\(\\) has no return type specified\\.$#"
count: 1
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php

-
message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProviderTest\\:\\:testCheckAuthenticationCredentialsChanged\\(\\) has no return type specified\\.$#"
count: 1
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php

-
message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProviderTest\\:\\:testCheckAuthenticationFailed\\(\\) has no return type specified\\.$#"
count: 1
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php

-
message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RepositoryAuthenticationProviderTest\\:\\:testCheckAuthenticationFailedWhenPasswordInUnsupportedFormat\\(\\) has no return type specified\\.$#"
count: 1
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php

-
message: "#^Parameter \\#1 \\$user of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\UsernamePasswordToken constructor expects Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface, string given\\.$#"
count: 6
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php

-
message: "#^Parameter \\#3 \\$roles of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\UsernamePasswordToken constructor expects array\\<string\\>, string given\\.$#"
count: 9
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php

-
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\\.$#"
count: 1
path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php

-
message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\HttpUtilsTest\\:\\:checkRequestPathProvider\\(\\) has no return type specified\\.$#"
count: 1
Expand Down
17 changes: 1 addition & 16 deletions src/bundle/Core/DependencyInjection/Compiler/SecurityPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Bundle\Core\DependencyInjection\Compiler;

use Ibexa\Contracts\Core\Repository\PermissionResolver;
use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface;
use Ibexa\Core\MVC\Symfony\Security\Authentication\DefaultAuthenticationSuccessHandler;
use Ibexa\Core\MVC\Symfony\Security\Authentication\GuardRepositoryAuthenticationProvider;
use Ibexa\Core\MVC\Symfony\Security\Authentication\RememberMeRepositoryAuthenticationProvider;
use Ibexa\Core\MVC\Symfony\Security\HttpUtils;
use Ibexa\Core\MVC\Symfony\SiteAccess;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
Expand All @@ -24,31 +24,16 @@
*/
final class SecurityPass implements CompilerPassInterface
{
/**
* @deprecated 4.6.7 CONSTANT_AUTH_TIME_SETTING constant is deprecated, will be removed in 5.0.
*/
public const string CONSTANT_AUTH_TIME_SETTING = 'ibexa.security.authentication.constant_auth_time';

public const float CONSTANT_AUTH_TIME_DEFAULT = 1.0;

public function process(ContainerBuilder $container): void
{
if (
!$container->hasDefinition('security.authentication.provider.rememberme') ||
!$container->hasDefinition('security.authentication.provider.guard')
) {
return;
}

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

$rememberMeAuthenticationProviderDef = $container->findDefinition('security.authentication.provider.rememberme');
$rememberMeAuthenticationProviderDef->setClass(RememberMeRepositoryAuthenticationProvider::class);
$rememberMeAuthenticationProviderDef->addMethodCall(
'setPermissionResolver',
[$permissionResolverRef]
);

$guardAuthenticationProviderDef = $container->findDefinition('security.authentication.provider.guard');
$guardAuthenticationProviderDef->setClass(GuardRepositoryAuthenticationProvider::class);
$guardAuthenticationProviderDef->addMethodCall(
Expand Down
8 changes: 7 additions & 1 deletion src/bundle/Core/Resources/config/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ parameters:
# Constant authentication execution time in seconds (float). Blocks timing attacks.
# Must be larger than expected real execution time, with a good margin.
# If set to zero, constant time authentication is disabled. Do not do this on production environments.
ibexa.security.authentication.constant_auth_time: !php/const Ibexa\Bundle\Core\DependencyInjection\Compiler\SecurityPass::CONSTANT_AUTH_TIME_DEFAULT
ibexa.security.authentication.constant_auth_time: 1.0

services:
Ibexa\Core\MVC\Symfony\Security\User\UsernameProvider:
Expand Down Expand Up @@ -44,6 +44,12 @@ services:
ibexa.security.user_provider.username: '@Ibexa\Core\MVC\Symfony\Security\User\UsernameProvider'
ibexa.security.user_provider.email: '@Ibexa\Core\MVC\Symfony\Security\User\EmailProvider'

Ibexa\Core\MVC\Symfony\Security\Authentication\EventSubscriber\RepositoryUserAuthenticationSubscriber:
autowire: true
autoconfigure: true
arguments:
$constantAuthTime: '%ibexa.security.authentication.constant_auth_time%'

Ibexa\Core\MVC\Symfony\Security\Authentication\EventSubscriber\OnAuthenticationTokenCreatedRepositoryUserSubscriber:
autowire: true
autoconfigure: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Core\MVC\Symfony\Security\Authentication\EventSubscriber;

use Exception;
use Ibexa\Contracts\Core\Repository\Exceptions\PasswordInUnsupportedFormatException;
use Ibexa\Contracts\Core\Repository\UserService;
use Ibexa\Core\MVC\Symfony\Security\UserInterface as IbexaUserInterface;
use Ibexa\Core\Repository\User\Exception\UnsupportedPasswordHashType;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Event\CheckPassportEvent;

final class RepositoryUserAuthenticationSubscriber implements EventSubscriberInterface
{
use LoggerAwareTrait;

public const string CONSTANT_AUTH_TIME_SETTING = 'ibexa.security.authentication.constant_auth_time';

private const int USLEEP_MULTIPLIER = 1000000;

public function __construct(
private readonly RequestStack $requestStack,
private readonly UserService $userService,
private readonly float $constantAuthTime,
?LoggerInterface $logger = null
) {
$this->logger = $logger ?? new NullLogger();
}

public static function getSubscribedEvents(): array
{
return [
CheckPassportEvent::class => ['validateRepositoryUser'],
];
}

public function validateRepositoryUser(CheckPassportEvent $event): void
{
$request = $this->requestStack->getCurrentRequest();
if ($request === null) {
return;
}

$badge = $event->getPassport()->getBadge(UserBadge::class);
if (!$badge instanceof UserBadge) {
return;
}

$user = $badge->getUser();
if (!$user instanceof IbexaUserInterface) {
return;
}

$startTime = $this->startConstantTimer();
try {
$this->userService->checkUserCredentials(
$user->getAPIUser(),
$user->getPassword() ?? ''
);

$event->getAuthenticator()->authenticate($request);
} catch (UnsupportedPasswordHashType $exception) {
$this->sleepUsingConstantTimer($startTime);

throw new PasswordInUnsupportedFormatException($exception);
} catch (Exception $e) {
$this->sleepUsingConstantTimer($startTime);

throw $e;
}

$this->sleepUsingConstantTimer($startTime);
}

private function startConstantTimer(): float
{
return microtime(true);
}

private function sleepUsingConstantTimer(float $startTime): void
{
if ($this->constantAuthTime <= 0.0) {
return;
}

$remainingTime = $this->constantAuthTime - (microtime(true) - $startTime);
if ($remainingTime > 0) {
$microseconds = $remainingTime * self::USLEEP_MULTIPLIER;

usleep((int)$microseconds);
} elseif ($this->logger) {
$this->logger->warning(
sprintf(
'Authentication took longer than the configured constant time. Consider increasing the value of %s',
self::CONSTANT_AUTH_TIME_SETTING
),
[__CLASS__]
);
}
}
}

This file was deleted.

Loading

0 comments on commit fe4c34a

Please sign in to comment.