Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IBX-8323: Reworked RepositoryAuthenticationProvider and moved its logic to a dedicated subscriber #396

Merged
merged 3 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
konradoboza marked this conversation as resolved.
Show resolved Hide resolved

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();
Steveb-p marked this conversation as resolved.
Show resolved Hide resolved
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
Loading