From eeb08b493ec50013ebc3d5195fd50b3db151190d Mon Sep 17 00:00:00 2001 From: Konrad Oboza Date: Thu, 23 May 2024 14:45:24 +0200 Subject: [PATCH] IBX-8140: Enabled authenticator manager-based security (#368) * IBX-8140: Enabled authenticator manager-based security * adjusted tests after removing obsolete services * added proper access denied listener grouping all access issues and redirecting to the login form * [TMP][Behat] Added @javascript to failed scenarios to see screenshots * fixed phpstan * renamed AccessDeniedListener to *Subscriber and made it more loose when detecting permission issues * [TMP] Added dependencies.json * [TMP] Dropped core self-reference from dependencies.json * regenerated phpstan baseline * cr remarks * adjusted unit tests * removed unused methods due to SecurityPass changes * reverted decorating `DefaultAuthenticationSuccessHandler` * cr remarks * dropped @javascript due to performance reasons * removed dependencies.json --------- Co-authored-by: Andrew Longosz --- phpstan-baseline.neon | 222 +--------- .../Compiler/SecurityPass.php | 68 +-- .../Security/HttpBasicFactory.php | 23 - .../Features/Content/content_preview.feature | 2 + src/bundle/Core/IbexaCoreBundle.php | 11 +- src/bundle/Core/Resources/config/security.yml | 17 +- src/lib/.gitkeep | 0 .../AnonymousAuthenticationProvider.php | 41 -- .../DefaultAuthenticationSuccessHandler.php | 42 +- .../AccessDeniedSubscriber.php | 46 ++ .../EventListener/SecurityListener.php | 252 ----------- src/lib/MVC/Symfony/Security/User.php | 60 +-- src/lib/MVC/Symfony/SiteAccess/Router.php | 5 +- .../Compiler/SecurityPassTest.php | 33 +- tests/lib/.gitkeep | 0 .../AnonymousAuthenticationProviderTest.php | 57 --- ...efaultAuthenticationSuccessHandlerTest.php | 65 --- ...MeRepositoryAuthenticationProviderTest.php | 23 +- .../EventListener/SecurityListenerTest.php | 415 ------------------ 19 files changed, 149 insertions(+), 1233 deletions(-) delete mode 100644 src/bundle/Core/DependencyInjection/Security/HttpBasicFactory.php delete mode 100644 src/lib/.gitkeep delete mode 100644 src/lib/MVC/Symfony/Security/Authentication/AnonymousAuthenticationProvider.php create mode 100644 src/lib/MVC/Symfony/Security/Authentication/EventSubscriber/AccessDeniedSubscriber.php delete mode 100644 src/lib/MVC/Symfony/Security/EventListener/SecurityListener.php delete mode 100644 tests/lib/.gitkeep delete mode 100644 tests/lib/MVC/Symfony/Security/Authentication/AnonymousAuthenticationProviderTest.php delete mode 100644 tests/lib/MVC/Symfony/Security/Authentication/DefaultAuthenticationSuccessHandlerTest.php delete mode 100644 tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 43789d11a1..9491d2c49a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -620,11 +620,6 @@ parameters: count: 1 path: src/bundle/Core/DependencyInjection/Compiler/RouterPass.php - - - message: "#^Method Ibexa\\\\Bundle\\\\Core\\\\DependencyInjection\\\\Compiler\\\\SecurityPass\\:\\:process\\(\\) has no return type specified\\.$#" - count: 1 - path: src/bundle/Core/DependencyInjection/Compiler/SecurityPass.php - - message: "#^Method Ibexa\\\\Bundle\\\\Core\\\\DependencyInjection\\\\Compiler\\\\SessionConfigurationPass\\:\\:process\\(\\) has no return type specified\\.$#" count: 1 @@ -3315,16 +3310,6 @@ parameters: count: 1 path: src/bundle/Core/Fragment/InlineFragmentRenderer.php - - - message: "#^Call to an undefined method Symfony\\\\Component\\\\DependencyInjection\\\\Extension\\\\ExtensionInterface\\:\\:addSecurityListenerFactory\\(\\)\\.$#" - count: 1 - path: src/bundle/Core/IbexaCoreBundle.php - - - - message: "#^Method Ibexa\\\\Bundle\\\\Core\\\\IbexaCoreBundle\\:\\:build\\(\\) has no return type specified\\.$#" - count: 1 - path: src/bundle/Core/IbexaCoreBundle.php - - message: "#^Method Ibexa\\\\Bundle\\\\Core\\\\Imagine\\\\AliasCleaner\\:\\:removeAliases\\(\\) has no return type specified\\.$#" count: 1 @@ -12205,21 +12190,6 @@ parameters: count: 1 path: src/lib/MVC/Symfony/Routing/UrlWildcardRouter.php - - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\AnonymousAuthenticationProvider\\:\\:setConfigResolver\\(\\) has no return type specified\\.$#" - count: 1 - path: src/lib/MVC/Symfony/Security/Authentication/AnonymousAuthenticationProvider.php - - - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\AnonymousAuthenticationProvider\\:\\:setPermissionResolver\\(\\) has no return type specified\\.$#" - count: 1 - path: src/lib/MVC/Symfony/Security/Authentication/AnonymousAuthenticationProvider.php - - - - message: "#^Parameter \\#3 \\$firewallName of class Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\DetermineTargetUrlEvent constructor expects string, string\\|null given\\.$#" - count: 1 - path: src/lib/MVC/Symfony/Security/Authentication/DefaultAuthenticationSuccessHandler.php - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RememberMeRepositoryAuthenticationProvider\\:\\:setPermissionResolver\\(\\) has no return type specified\\.$#" count: 1 @@ -12305,31 +12275,6 @@ parameters: count: 1 path: src/lib/MVC/Symfony/Security/Authorization/Voter/ValueObjectVoter.php - - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListener\\:\\:__construct\\(\\) has parameter \\$fragmentPath with no type specified\\.$#" - count: 1 - path: src/lib/MVC/Symfony/Security/EventListener/SecurityListener.php - - - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListener\\:\\:checkSiteAccessPermission\\(\\) has no return type specified\\.$#" - count: 1 - path: src/lib/MVC/Symfony/Security/EventListener/SecurityListener.php - - - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListener\\:\\:isMasterRequest\\(\\) has parameter \\$requestType with no type specified\\.$#" - count: 1 - path: src/lib/MVC/Symfony/Security/EventListener/SecurityListener.php - - - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListener\\:\\:onInteractiveLogin\\(\\) has no return type specified\\.$#" - count: 1 - path: src/lib/MVC/Symfony/Security/EventListener/SecurityListener.php - - - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListener\\:\\:onKernelRequest\\(\\) has no return type specified\\.$#" - count: 1 - path: src/lib/MVC/Symfony/Security/EventListener/SecurityListener.php - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Exception\\\\UnauthorizedSiteAccessException\\:\\:__construct\\(\\) has parameter \\$username with no type specified\\.$#" count: 1 @@ -12375,26 +12320,6 @@ parameters: count: 1 path: src/lib/MVC/Symfony/Security/InteractiveLoginToken.php - - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\User\\:\\:__construct\\(\\) has parameter \\$roles with no value type specified in iterable type array\\.$#" - count: 1 - path: src/lib/MVC/Symfony/Security/User.php - - - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\User\\:\\:eraseCredentials\\(\\) has no return type specified\\.$#" - count: 1 - path: src/lib/MVC/Symfony/Security/User.php - - - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\User\\:\\:getSalt\\(\\) should return string but returns null\\.$#" - count: 1 - path: src/lib/MVC/Symfony/Security/User.php - - - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\User\\:\\:setAPIUser\\(\\) has no return type specified\\.$#" - count: 1 - path: src/lib/MVC/Symfony/Security/User.php - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\User\\\\EmailProvider\\:\\:loadUserByUsername\\(\\) has parameter \\$user with no type specified\\.$#" count: 1 @@ -23555,11 +23480,6 @@ parameters: count: 1 path: tests/bundle/Core/DependencyInjection/Compiler/RegisterStorageEnginePassTest.php - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Core\\\\DependencyInjection\\\\Compiler\\\\SecurityPassTest\\:\\:testAlteredDaoAuthenticationProvider\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Core/DependencyInjection/Compiler/SecurityPassTest.php - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Core\\\\DependencyInjection\\\\Compiler\\\\SlugConverterConfigurationPassTest\\:\\:configurationProvider\\(\\) has no return type specified\\.$#" count: 1 @@ -47045,21 +46965,6 @@ parameters: count: 1 path: tests/lib/MVC/Symfony/Routing/UrlAliasRouterTest.php - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\AnonymousAuthenticationProviderTest\\:\\:testAuthenticate\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/Authentication/AnonymousAuthenticationProviderTest.php - - - - message: "#^Parameter \\#1 \\$configResolver of method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\AnonymousAuthenticationProvider\\:\\:setConfigResolver\\(\\) expects Ibexa\\\\Contracts\\\\Core\\\\SiteAccess\\\\ConfigResolverInterface, PHPUnit\\\\Framework\\\\MockObject\\\\MockObject given\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/Authentication/AnonymousAuthenticationProviderTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\DefaultAuthenticationSuccessHandlerTest\\:\\:testSetConfigResolver\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/Authentication/DefaultAuthenticationSuccessHandlerTest.php - - message: "#^Call to an undefined method Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\TokenInterface\\:\\:getProviderKey\\(\\)\\.$#" count: 1 @@ -47086,7 +46991,7 @@ parameters: path: tests/lib/MVC/Symfony/Security/Authentication/GuardRepositoryAuthenticationProviderTest.php - - message: "#^Call to an undefined method Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\TokenInterface\\:\\:getProviderKey\\(\\)\\.$#" + 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 @@ -47095,26 +47000,6 @@ parameters: count: 1 path: tests/lib/MVC/Symfony/Security/Authentication/RememberMeRepositoryAuthenticationProviderTest.php - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RememberMeRepositoryAuthenticationProviderTest\\:\\:testAuthenticate\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/Authentication/RememberMeRepositoryAuthenticationProviderTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RememberMeRepositoryAuthenticationProviderTest\\:\\:testAuthenticateUnsupportedToken\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/Authentication/RememberMeRepositoryAuthenticationProviderTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RememberMeRepositoryAuthenticationProviderTest\\:\\:testAuthenticateWrongProviderKey\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/Authentication/RememberMeRepositoryAuthenticationProviderTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\RememberMeRepositoryAuthenticationProviderTest\\:\\:testAuthenticateWrongSecret\\(\\) has no return type specified\\.$#" - 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 @@ -47165,111 +47050,6 @@ parameters: count: 1 path: tests/lib/MVC/Symfony/Security/Authentication/RepositoryAuthenticationProviderTest.php - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:generateListener\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:testCheckSiteAccessNoSiteAccess\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:testCheckSiteAccessNotEzUser\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:testCheckSiteAccessPermissionDenied\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:testCheckSiteAccessPermissionGranted\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:testGetSubscribedEvents\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:testOnInteractiveLogin\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:testOnInteractiveLoginAlreadyEzUser\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:testOnInteractiveLoginNotUserObject\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:testOnKernelRequestAccessDenied\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:testOnKernelRequestAccessGranted\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:testOnKernelRequestLoginRoute\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:testOnKernelRequestNoSiteAccess\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:testOnKernelRequestNullToken\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:testOnKernelRequestSubRequest\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:testOnKernelRequestSubRequestFragment\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Parameter \\#3 \\$configResolver of class Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListener constructor expects Ibexa\\\\Contracts\\\\Core\\\\SiteAccess\\\\ConfigResolverInterface, PHPUnit\\\\Framework\\\\MockObject\\\\MockObject given\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Parameter \\#4 \\$eventDispatcher of class Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListener constructor expects Symfony\\\\Component\\\\EventDispatcher\\\\EventDispatcherInterface, PHPUnit\\\\Framework\\\\MockObject\\\\MockObject given\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Parameter \\#5 \\$tokenStorage of class Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListener constructor expects Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\Storage\\\\TokenStorageInterface, PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\Security\\\\Core\\\\Authorization\\\\AuthorizationCheckerInterface given\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Parameter \\#6 \\$authorizationChecker of class Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListener constructor expects Symfony\\\\Component\\\\Security\\\\Core\\\\Authorization\\\\AuthorizationCheckerInterface, PHPUnit\\\\Framework\\\\MockObject\\\\MockObject given\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\EventListener\\\\SecurityListenerTest\\:\\:\\$tokenStorage \\(PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\Security\\\\Core\\\\Authorization\\\\AuthorizationCheckerInterface\\) does not accept PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\Storage\\\\TokenStorageInterface\\.$#" - count: 1 - path: tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php - - message: "#^Method Ibexa\\\\Tests\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\HttpUtilsTest\\:\\:checkRequestPathProvider\\(\\) has no return type specified\\.$#" count: 1 diff --git a/src/bundle/Core/DependencyInjection/Compiler/SecurityPass.php b/src/bundle/Core/DependencyInjection/Compiler/SecurityPass.php index f08ac56054..ffcbc8774c 100644 --- a/src/bundle/Core/DependencyInjection/Compiler/SecurityPass.php +++ b/src/bundle/Core/DependencyInjection/Compiler/SecurityPass.php @@ -8,12 +8,10 @@ namespace Ibexa\Bundle\Core\DependencyInjection\Compiler; use Ibexa\Contracts\Core\Repository\PermissionResolver; -use Ibexa\Contracts\Core\Repository\UserService; -use Ibexa\Core\MVC\Symfony\Security\Authentication\AnonymousAuthenticationProvider; +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\Authentication\RepositoryAuthenticationProvider; use Ibexa\Core\MVC\Symfony\Security\HttpUtils; use Ibexa\Core\MVC\Symfony\SiteAccess; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; @@ -22,52 +20,24 @@ /** * Security related compiler pass. - * Manipulates Symfony core security services to adapt them to eZ security needs. + * Manipulates Symfony core security services to adapt them to Ibexa security needs. */ -class SecurityPass implements CompilerPassInterface +final class SecurityPass implements CompilerPassInterface { - public const CONSTANT_AUTH_TIME_SETTING = 'ibexa.security.authentication.constant_auth_time'; + public const string CONSTANT_AUTH_TIME_SETTING = 'ibexa.security.authentication.constant_auth_time'; - public const CONSTANT_AUTH_TIME_DEFAULT = 1.0; + public const float CONSTANT_AUTH_TIME_DEFAULT = 1.0; - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { - if (!($container->hasDefinition('security.authentication.provider.dao') && - $container->hasDefinition('security.authentication.provider.rememberme') && - $container->hasDefinition('security.authentication.provider.guard') && - $container->hasDefinition('security.authentication.provider.anonymous'))) { + if ( + !$container->hasDefinition('security.authentication.provider.rememberme') || + !$container->hasDefinition('security.authentication.provider.guard') + ) { return; } - $configResolverRef = new Reference('ibexa.config.resolver'); $permissionResolverRef = new Reference(PermissionResolver::class); - $userServiceRef = new Reference(UserService::class); - $loggerRef = new Reference('logger'); - - // Override and inject the Repository in the authentication provider. - // We need it for checking user credentials - $daoAuthenticationProviderDef = $container->findDefinition('security.authentication.provider.dao'); - $daoAuthenticationProviderDef->setClass(RepositoryAuthenticationProvider::class); - $daoAuthenticationProviderDef->addMethodCall( - 'setPermissionResolver', - [$permissionResolverRef] - ); - $daoAuthenticationProviderDef->addMethodCall( - 'setUserService', - [$userServiceRef] - ); - $daoAuthenticationProviderDef->addMethodCall( - 'setConstantAuthTime', - [ - $container->hasParameter(self::CONSTANT_AUTH_TIME_SETTING) ? - (float)$container->getParameter(self::CONSTANT_AUTH_TIME_SETTING) : - self::CONSTANT_AUTH_TIME_DEFAULT, - ] - ); - $daoAuthenticationProviderDef->addMethodCall( - 'setLogger', - [$loggerRef] - ); $rememberMeAuthenticationProviderDef = $container->findDefinition('security.authentication.provider.rememberme'); $rememberMeAuthenticationProviderDef->setClass(RememberMeRepositoryAuthenticationProvider::class); @@ -83,18 +53,6 @@ public function process(ContainerBuilder $container) [$permissionResolverRef] ); - $anonymousAuthenticationProviderDef = $container->findDefinition('security.authentication.provider.anonymous'); - $anonymousAuthenticationProviderDef->setClass(AnonymousAuthenticationProvider::class); - $anonymousAuthenticationProviderDef->addMethodCall( - 'setPermissionResolver', - [$permissionResolverRef] - ); - - $anonymousAuthenticationProviderDef->addMethodCall( - 'setConfigResolver', - [$configResolverRef] - ); - if (!$container->hasDefinition('security.http_utils')) { return; } @@ -114,11 +72,15 @@ public function process(ContainerBuilder $container) $successHandlerDef->setClass(DefaultAuthenticationSuccessHandler::class); $successHandlerDef->addMethodCall( 'setConfigResolver', - [$configResolverRef] + [new Reference(ConfigResolverInterface::class)] ); $successHandlerDef->addMethodCall( 'setEventDispatcher', [new Reference('event_dispatcher')] ); + $successHandlerDef->addMethodCall( + 'setPermissionResolver', + [$permissionResolverRef] + ); } } diff --git a/src/bundle/Core/DependencyInjection/Security/HttpBasicFactory.php b/src/bundle/Core/DependencyInjection/Security/HttpBasicFactory.php deleted file mode 100644 index a052de46d2..0000000000 --- a/src/bundle/Core/DependencyInjection/Security/HttpBasicFactory.php +++ /dev/null @@ -1,23 +0,0 @@ -addCompilerPass(new GenericFieldTypeConverterPass(), PassConfig::TYPE_OPTIMIZE); @@ -92,15 +93,13 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new RoleLimitationConverterPass()); $container->addCompilerPass(new QueryTypePass()); - $securityExtension = $container->getExtension('security'); - $securityExtension->addSecurityListenerFactory(new HttpBasicFactory()); $container->addCompilerPass(new TranslationCollectorPass()); $container->addCompilerPass(new SlugConverterConfigurationPass()); $container->registerForAutoconfiguration(VariableProvider::class)->addTag('ezplatform.view.variable_provider'); } - public function getContainerExtension() + public function getContainerExtension(): ?ExtensionInterface { if (!isset($this->extension)) { $this->extension = new IbexaCoreExtension( diff --git a/src/bundle/Core/Resources/config/security.yml b/src/bundle/Core/Resources/config/security.yml index 3d96abf9cb..5ca47846d9 100644 --- a/src/bundle/Core/Resources/config/security.yml +++ b/src/bundle/Core/Resources/config/security.yml @@ -40,19 +40,10 @@ services: class: Ibexa\Core\MVC\Symfony\Controller\SecurityController arguments: ["@twig", '@ibexa.config.resolver', "@security.authentication_utils"] - Ibexa\Core\MVC\Symfony\Security\EventListener\SecurityListener: - class: Ibexa\Core\MVC\Symfony\Security\EventListener\SecurityListener - arguments: - - '@Ibexa\Contracts\Core\Repository\PermissionResolver' - - '@Ibexa\Contracts\Core\Repository\UserService' - - '@ibexa.config.resolver' - - "@event_dispatcher" - - "@security.token_storage" - - "@security.authorization_checker" - - "%fragment.path%" - tags: - - { name: kernel.event_subscriber } - ibexa.security.user_provider: '@Ibexa\Core\MVC\Symfony\Security\User\UsernameProvider' 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\AccessDeniedSubscriber: + autowire: true + autoconfigure: true diff --git a/src/lib/.gitkeep b/src/lib/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/lib/MVC/Symfony/Security/Authentication/AnonymousAuthenticationProvider.php b/src/lib/MVC/Symfony/Security/Authentication/AnonymousAuthenticationProvider.php deleted file mode 100644 index a57a5585dc..0000000000 --- a/src/lib/MVC/Symfony/Security/Authentication/AnonymousAuthenticationProvider.php +++ /dev/null @@ -1,41 +0,0 @@ -configResolver = $configResolver; - } - - public function setPermissionResolver(PermissionResolver $permissionResolver) - { - $this->permissionResolver = $permissionResolver; - } - - public function authenticate(TokenInterface $token) - { - $token = parent::authenticate($token); - $this->permissionResolver->setCurrentUserReference(new UserReference($this->configResolver->getParameter('anonymous_user_id'))); - - return $token; - } -} diff --git a/src/lib/MVC/Symfony/Security/Authentication/DefaultAuthenticationSuccessHandler.php b/src/lib/MVC/Symfony/Security/Authentication/DefaultAuthenticationSuccessHandler.php index 87552b8854..5b45e65228 100644 --- a/src/lib/MVC/Symfony/Security/Authentication/DefaultAuthenticationSuccessHandler.php +++ b/src/lib/MVC/Symfony/Security/Authentication/DefaultAuthenticationSuccessHandler.php @@ -4,23 +4,27 @@ * @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; +use Ibexa\Contracts\Core\Repository\PermissionResolver; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; +use Ibexa\Core\MVC\Symfony\Security\UserInterface; use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler as BaseSuccessHandler; -class DefaultAuthenticationSuccessHandler extends BaseSuccessHandler +final class DefaultAuthenticationSuccessHandler extends BaseSuccessHandler { private EventDispatcherInterface $eventDispatcher; private ConfigResolverInterface $configResolver; - /** - * Injects the ConfigResolver to potentially override default_target_path for redirections after authentication success. - */ + private PermissionResolver $permissionResolver; + public function setConfigResolver(ConfigResolverInterface $configResolver): void { $this->configResolver = $configResolver; @@ -31,20 +35,42 @@ public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): v $this->eventDispatcher = $eventDispatcher; } - protected function determineTargetUrl(Request $request) + public function setPermissionResolver(PermissionResolver $permissionResolver): void + { + $this->permissionResolver = $permissionResolver; + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token): ?Response + { + $user = $token->getUser(); + if ($user instanceof UserInterface && isset($this->permissionResolver)) { + $this->permissionResolver->setCurrentUserReference($user->getAPIUser()); + } + + return parent::onAuthenticationSuccess($request, $token); + } + + protected function determineTargetUrl(Request $request): string { if (isset($this->configResolver)) { $defaultPage = $this->configResolver->getParameter('default_page'); if ($defaultPage !== null) { - $this->options['default_target_path'] = $defaultPage; + $this->setOptions([ + 'default_target_path' => $defaultPage, + ]); } } if (isset($this->eventDispatcher)) { - $event = new DetermineTargetUrlEvent($request, $this->options, $this->getFirewallName()); + $event = new DetermineTargetUrlEvent( + $request, + $this->getOptions(), + $this->getFirewallName() ?? '' + ); + $this->eventDispatcher->dispatch($event); - $this->options = $event->getOptions(); + $this->setOptions($event->getOptions()); } return parent::determineTargetUrl($request); diff --git a/src/lib/MVC/Symfony/Security/Authentication/EventSubscriber/AccessDeniedSubscriber.php b/src/lib/MVC/Symfony/Security/Authentication/EventSubscriber/AccessDeniedSubscriber.php new file mode 100644 index 0000000000..892ce86ebc --- /dev/null +++ b/src/lib/MVC/Symfony/Security/Authentication/EventSubscriber/AccessDeniedSubscriber.php @@ -0,0 +1,46 @@ + ['onKernelException', 2], + ]; + } + + public function onKernelException(ExceptionEvent $event): void + { + $exception = $event->getThrowable()->getPrevious(); + + if ($exception instanceof IbexaRepositoryException) { + return; + } + + $event->setResponse(new RedirectResponse( + $this->urlGenerator->generate('login') + )); + } +} diff --git a/src/lib/MVC/Symfony/Security/EventListener/SecurityListener.php b/src/lib/MVC/Symfony/Security/EventListener/SecurityListener.php deleted file mode 100644 index 0cc43aeeb7..0000000000 --- a/src/lib/MVC/Symfony/Security/EventListener/SecurityListener.php +++ /dev/null @@ -1,252 +0,0 @@ -permissionResolver = $permissionResolver; - $this->userService = $userService; - $this->configResolver = $configResolver; - $this->eventDispatcher = $eventDispatcher; - $this->tokenStorage = $tokenStorage; - $this->authorizationChecker = $authorizationChecker; - $this->fragmentPath = $fragmentPath; - } - - public static function getSubscribedEvents() - { - return [ - SecurityEvents::INTERACTIVE_LOGIN => [ - ['onInteractiveLogin', 10], - ['checkSiteAccessPermission', 9], - ], - // Priority 7, so that it occurs just after firewall (priority 8) - KernelEvents::REQUEST => ['onKernelRequest', 7], - ]; - } - - /** - * Tries to retrieve a valid Ibexa user if authenticated user doesn't come from the repository (foreign user provider). - * Will dispatch an event allowing listeners to return a valid Ibexa user for current authenticated user. - * Will by default let the repository load the anonymous user. - * - * @param \Symfony\Component\Security\Http\Event\InteractiveLoginEvent $event - */ - public function onInteractiveLogin(BaseInteractiveLoginEvent $event) - { - $token = $event->getAuthenticationToken(); - $originalUser = $token->getUser(); - if ($originalUser instanceof IbexaUser || !$originalUser instanceof UserInterface) { - return; - } - - /* - * 1. Send the event. - * 2. If no Ibexa user is returned, load Anonymous user. - * 3. Inject Ibexa user in repository. - * 4. Create the UserWrapped user object (implementing Ibexa UserInterface) with loaded Ibexa user. - * 5. Create new token with UserWrapped user - * 6. Inject the new token in security context - */ - $subLoginEvent = new InteractiveLoginEvent($event->getRequest(), $token); - $this->eventDispatcher->dispatch($subLoginEvent, MVCEvents::INTERACTIVE_LOGIN); - - if ($subLoginEvent->hasAPIUser()) { - $apiUser = $subLoginEvent->getAPIUser(); - } else { - $apiUser = $this->userService->loadUser( - $this->configResolver->getParameter('anonymous_user_id') - ); - } - - $this->permissionResolver->setCurrentUserReference($apiUser); - - $providerKey = method_exists($token, 'getProviderKey') ? $token->getProviderKey() : __CLASS__; - $interactiveToken = new InteractiveLoginToken( - $this->getUser($originalUser, $apiUser), - get_class($token), - $token->getCredentials(), - $providerKey, - $token->getRoleNames() - ); - $interactiveToken->setOriginalToken($token); - $interactiveToken->setAttributes($token->getAttributes()); - $this->tokenStorage->setToken($interactiveToken); - } - - /** - * Returns new user object based on original user and provided API user. - * One may want to override this method to use their own user class. - * - * @param \Symfony\Component\Security\Core\User\UserInterface $originalUser - * @param \Ibexa\Contracts\Core\Repository\Values\User\User $apiUser - * - * @return \Ibexa\Core\MVC\Symfony\Security\UserInterface - */ - protected function getUser(UserInterface $originalUser, APIUser $apiUser) - { - return new UserWrapped($originalUser, $apiUser); - } - - /** - * Throws an UnauthorizedSiteAccessException if current user doesn't have permission to current SiteAccess. - * - * @param \Symfony\Component\Security\Http\Event\InteractiveLoginEvent $event - * - * @throws \Ibexa\Core\MVC\Symfony\Security\Exception\UnauthorizedSiteAccessException - */ - public function checkSiteAccessPermission(BaseInteractiveLoginEvent $event) - { - $token = $event->getAuthenticationToken(); - $originalUser = $token->getUser(); - $request = $event->getRequest(); - $siteAccess = $request->attributes->get('siteaccess'); - if (!($originalUser instanceof IbexaUser && $siteAccess instanceof SiteAccess)) { - return; - } - - if (!$this->hasAccess($siteAccess)) { - throw new UnauthorizedSiteAccessException($siteAccess, $originalUser->getUsername()); - } - } - - /** - * Throws an UnauthorizedSiteAccessException if current user doesn't have access to current SiteAccess. - * - * @param \Symfony\Component\HttpKernel\Event\RequestEvent $event - * - * @throws \Ibexa\Core\MVC\Symfony\Security\Exception\UnauthorizedSiteAccessException - */ - public function onKernelRequest(RequestEvent $event) - { - $request = $event->getRequest(); - // Ignore sub-requests, including fragments. - if (!$this->isMasterRequest($request, $event->getRequestType())) { - return; - } - - $siteAccess = $request->attributes->get('siteaccess'); - if (!$siteAccess instanceof SiteAccess) { - return; - } - - $token = $this->tokenStorage->getToken(); - if ($token === null) { - return; - } - - if ( - // Leave access to login route, so that user can attempt re-authentication. - $request->attributes->get('_route') !== 'login' - && !$this->hasAccess($siteAccess) - ) { - throw new UnauthorizedSiteAccessException($siteAccess, $token->getUsername()); - } - } - - /** - * Returns true if given request is considered as a master request. - * Fragments are considered as sub-requests (i.e. ESI, Hinclude...). - * - * @param \Symfony\Component\HttpFoundation\Request $request - * @param $requestType - * - * @return bool - */ - private function isMasterRequest(Request $request, $requestType) - { - if ( - $requestType !== HttpKernelInterface::MASTER_REQUEST - || substr($request->getPathInfo(), -strlen($this->fragmentPath)) === $this->fragmentPath - ) { - return false; - } - - return true; - } - - /** - * Returns true if current user has access to given SiteAccess. - * - * @param \Ibexa\Core\MVC\Symfony\SiteAccess $siteAccess - * - * @return bool - */ - protected function hasAccess(SiteAccess $siteAccess) - { - return $this->authorizationChecker->isGranted( - new Attribute('user', 'login', ['valueObject' => $siteAccess]) - ); - } -} diff --git a/src/lib/MVC/Symfony/Security/User.php b/src/lib/MVC/Symfony/Security/User.php index 7aaea6fd4f..64f30d76e1 100644 --- a/src/lib/MVC/Symfony/Security/User.php +++ b/src/lib/MVC/Symfony/Security/User.php @@ -10,20 +10,23 @@ use Ibexa\Contracts\Core\Repository\Values\User\User as APIUser; use Ibexa\Core\Repository\Values\User\UserReference; +use Stringable; use Symfony\Component\Security\Core\User\EquatableInterface; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface as BaseUserInterface; -class User implements ReferenceUserInterface, EquatableInterface +class User implements ReferenceUserInterface, EquatableInterface, PasswordAuthenticatedUserInterface, Stringable { - /** @var \Ibexa\Contracts\Core\Repository\Values\User\User */ - private $user; + private APIUser $user; - /** @var \Ibexa\Contracts\Core\Repository\Values\User\UserReference */ - private $reference; + private UserReference $reference; /** @var string[] */ - private $roles; + private array $roles; + /** + * @param string[] $roles + */ public function __construct(APIUser $user, array $roles = []) { $this->user = $user; @@ -45,9 +48,9 @@ public function __construct(APIUser $user, array $roles = []) * and populated in any number of different ways when the user object * is created. * - * @return string[] The user roles + * @return string[] */ - public function getRoles() + public function getRoles(): array { return $this->roles; } @@ -57,10 +60,8 @@ public function getRoles() * * This should be the encoded password. On authentication, a plain-text * password will be salted, encoded, and then compared to this value. - * - * @return string The password */ - public function getPassword() + public function getPassword(): ?string { return $this->getAPIUser()->passwordHash; } @@ -69,20 +70,16 @@ public function getPassword() * Returns the salt that was originally used to encode the password. * * This can return null if the password was not encoded using a salt. - * - * @return string The salt */ - public function getSalt() + public function getSalt(): ?string { return null; } /** * Returns the username used to authenticate the user. - * - * @return string The username */ - public function getUsername() + public function getUsername(): string { return $this->getAPIUser()->login; } @@ -93,22 +90,16 @@ public function getUsername() * This is important if, at any given point, sensitive information like * the plain-text password is stored on this object. */ - public function eraseCredentials() + public function eraseCredentials(): void { } - /** - * @return \Ibexa\Contracts\Core\Repository\Values\User\UserReference - */ - public function getAPIUserReference() + public function getAPIUserReference(): UserReference { return $this->reference; } - /** - * @return \Ibexa\Contracts\Core\Repository\Values\User\User - */ - public function getAPIUser() + public function getAPIUser(): APIUser { if (!$this->user instanceof APIUser) { throw new \LogicException( @@ -119,16 +110,13 @@ public function getAPIUser() return $this->user; } - /** - * @param \Ibexa\Contracts\Core\Repository\Values\User\User $user - */ - public function setAPIUser(APIUser $user) + public function setAPIUser(APIUser $apiUser): void { - $this->user = $user; - $this->reference = new UserReference($user->getUserId()); + $this->user = $apiUser; + $this->reference = new UserReference($apiUser->getUserId()); } - public function isEqualTo(BaseUserInterface $user) + public function isEqualTo(BaseUserInterface $user): bool { // Check for the lighter ReferenceUserInterface first if ($user instanceof ReferenceUserInterface) { @@ -140,7 +128,7 @@ public function isEqualTo(BaseUserInterface $user) return false; } - public function __toString() + public function __toString(): string { return $this->getAPIUser()->contentInfo->name; } @@ -150,9 +138,9 @@ public function __toString() * (& either way refresh) the user object in {@see \Ibexa\Core\MVC\Symfony\Security\User\BaseProvider::refreshUser} * when object wakes back up from session. * - * @return array + * @return string[] */ - public function __sleep() + public function __sleep(): array { return ['reference', 'roles']; } diff --git a/src/lib/MVC/Symfony/SiteAccess/Router.php b/src/lib/MVC/Symfony/SiteAccess/Router.php index 23cb043735..270511c552 100644 --- a/src/lib/MVC/Symfony/SiteAccess/Router.php +++ b/src/lib/MVC/Symfony/SiteAccess/Router.php @@ -210,11 +210,10 @@ private function doMatch(SimplifiedRequest $request) * @param string $siteAccessName * * @throws \InvalidArgumentException If $siteAccessName is invalid (i.e. not present in configured list). - * - * @return \Ibexa\Core\MVC\Symfony\SiteAccess|null */ - public function matchByName($siteAccessName) + public function matchByName($siteAccessName): ?SiteAccess { + $siteAccessName = $siteAccessName instanceof SiteAccess ? $siteAccessName->name : $siteAccessName; if (!$this->siteAccessProvider->isDefined($siteAccessName)) { throw new InvalidArgumentException("Invalid SiteAccess name provided for reverse matching: $siteAccessName"); } diff --git a/tests/bundle/Core/DependencyInjection/Compiler/SecurityPassTest.php b/tests/bundle/Core/DependencyInjection/Compiler/SecurityPassTest.php index 858ab220dc..d6689f86d4 100644 --- a/tests/bundle/Core/DependencyInjection/Compiler/SecurityPassTest.php +++ b/tests/bundle/Core/DependencyInjection/Compiler/SecurityPassTest.php @@ -4,6 +4,7 @@ * @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\Tests\Bundle\Core\DependencyInjection\Compiler; @@ -16,15 +17,13 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; -class SecurityPassTest extends AbstractCompilerPassTestCase +final class SecurityPassTest extends AbstractCompilerPassTestCase { protected function setUp(): void { parent::setUp(); - $this->setDefinition('security.authentication.provider.dao', new Definition()); $this->setDefinition('security.authentication.provider.rememberme', new Definition()); $this->setDefinition('security.authentication.provider.guard', new Definition()); - $this->setDefinition('security.authentication.provider.anonymous', new Definition()); $this->setDefinition('security.http_utils', new Definition()); $this->setDefinition('security.authentication.success_handler', new Definition()); $this->setDefinition('ibexa.config.resolver', new Definition()); @@ -38,19 +37,10 @@ protected function registerCompilerPass(ContainerBuilder $container): void $container->addCompilerPass(new SecurityPass()); } - public function testAlteredDaoAuthenticationProvider() + public function testAlteredDaoAuthenticationProvider(): void { $this->compile(); - $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( - 'security.authentication.provider.dao', - 'setPermissionResolver', - [new Reference(PermissionResolver::class)] - ); - $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( - 'security.authentication.provider.dao', - 'setUserService', - [new Reference(UserService::class)] - ); + $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( 'security.authentication.provider.rememberme', 'setPermissionResolver', @@ -61,25 +51,10 @@ public function testAlteredDaoAuthenticationProvider() 'setPermissionResolver', [new Reference(PermissionResolver::class)] ); - $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( - 'security.authentication.provider.anonymous', - 'setPermissionResolver', - [new Reference(PermissionResolver::class)] - ); - $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( - 'security.authentication.provider.anonymous', - 'setConfigResolver', - [new Reference('ibexa.config.resolver')] - ); $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( 'security.http_utils', 'setSiteAccess', [new Reference(SiteAccess::class)] ); - $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( - 'security.authentication.success_handler', - 'setConfigResolver', - [new Reference('ibexa.config.resolver')] - ); } } diff --git a/tests/lib/.gitkeep b/tests/lib/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/lib/MVC/Symfony/Security/Authentication/AnonymousAuthenticationProviderTest.php b/tests/lib/MVC/Symfony/Security/Authentication/AnonymousAuthenticationProviderTest.php deleted file mode 100644 index dea321bd2d..0000000000 --- a/tests/lib/MVC/Symfony/Security/Authentication/AnonymousAuthenticationProviderTest.php +++ /dev/null @@ -1,57 +0,0 @@ -configResolver = $this->createMock(ConfigResolverInterface::class); - $this->permissionResolver = $this->createMock(PermissionResolver::class); - } - - public function testAuthenticate() - { - $anonymousUserId = 10; - $this->configResolver - ->expects(self::once()) - ->method('getParameter') - ->with('anonymous_user_id') - ->will(self::returnValue($anonymousUserId)); - - $this->permissionResolver - ->expects(self::once()) - ->method('setCurrentUserReference') - ->with(new UserReference($anonymousUserId)); - - $key = 'some_key'; - $authProvider = new AnonymousAuthenticationProvider($key); - $authProvider->setPermissionResolver($this->permissionResolver); - $authProvider->setConfigResolver($this->configResolver); - $anonymousToken = $this - ->getMockBuilder(AnonymousToken::class) - ->setConstructorArgs([$key, $this->createMock(UserInterface::class)]) - ->getMockForAbstractClass(); - self::assertSame($anonymousToken, $authProvider->authenticate($anonymousToken)); - } -} diff --git a/tests/lib/MVC/Symfony/Security/Authentication/DefaultAuthenticationSuccessHandlerTest.php b/tests/lib/MVC/Symfony/Security/Authentication/DefaultAuthenticationSuccessHandlerTest.php deleted file mode 100644 index 0f225f7849..0000000000 --- a/tests/lib/MVC/Symfony/Security/Authentication/DefaultAuthenticationSuccessHandlerTest.php +++ /dev/null @@ -1,65 +0,0 @@ -createMock(Matcher::class) - ); - $httpUtils = new HttpUtils(); - $httpUtils->setSiteAccess($siteAccess); - $successHandler = new DefaultAuthenticationSuccessHandler($httpUtils, []); - $successHandler->setFirewallName('test_firewall'); - - $refHandler = new ReflectionObject($successHandler); - $refOptions = $refHandler->getProperty('options'); - $refOptions->setAccessible(true); - $options = $refOptions->getValue($successHandler); - self::assertSame('/', $options['default_target_path']); - - $defaultPage = '/foo/bar'; - $configResolver = $this->createMock(ConfigResolverInterface::class); - $configResolver - ->expects(self::once()) - ->method('getParameter') - ->with('default_page') - ->will(self::returnValue($defaultPage)); - $successHandler->setConfigResolver($configResolver); - $successHandler->setEventDispatcher($this->createMock(EventDispatcherInterface::class)); - - $request = $this->createMock(Request::class); - $request - ->method('getSession') - ->willReturn($this->createMock(Session::class)); - - $request - ->method('getUriForPath') - ->willReturn($defaultPage); - - $successHandler->onAuthenticationSuccess($request, $this->createMock(TokenInterface::class)); - $options = $refOptions->getValue($successHandler); - self::assertSame($defaultPage, $options['default_target_path']); - } -} diff --git a/tests/lib/MVC/Symfony/Security/Authentication/RememberMeRepositoryAuthenticationProviderTest.php b/tests/lib/MVC/Symfony/Security/Authentication/RememberMeRepositoryAuthenticationProviderTest.php index 71b035a4f8..a4bd8d69f1 100644 --- a/tests/lib/MVC/Symfony/Security/Authentication/RememberMeRepositoryAuthenticationProviderTest.php +++ b/tests/lib/MVC/Symfony/Security/Authentication/RememberMeRepositoryAuthenticationProviderTest.php @@ -4,6 +4,7 @@ * @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\Tests\Core\MVC\Symfony\Security\Authentication; @@ -18,13 +19,12 @@ use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; -class RememberMeRepositoryAuthenticationProviderTest extends TestCase +final class RememberMeRepositoryAuthenticationProviderTest extends TestCase { - /** @var \Ibexa\Core\MVC\Symfony\Security\Authentication\RememberMeRepositoryAuthenticationProvider */ - private $authProvider; + private RememberMeRepositoryAuthenticationProvider $authProvider; - /** @var \Ibexa\Contracts\Core\Repository\PermissionResolver|\PHPUnit\Framework\MockObject\MockObject */ - private $permissionResolver; + /** @var \Ibexa\Contracts\Core\Repository\PermissionResolver&\PHPUnit\Framework\MockObject\MockObject */ + private PermissionResolver $permissionResolver; protected function setUp(): void { @@ -39,7 +39,7 @@ protected function setUp(): void $this->authProvider->setPermissionResolver($this->permissionResolver); } - public function testAuthenticateUnsupportedToken() + public function testAuthenticateUnsupportedToken(): void { $this->expectException(AuthenticationException::class); $this->expectExceptionMessage('The token is not supported by this authentication provider.'); @@ -51,7 +51,7 @@ public function testAuthenticateUnsupportedToken() $this->authProvider->authenticate($anonymousToken); } - public function testAuthenticateWrongProviderKey() + public function testAuthenticateWrongProviderKey(): void { $this->expectException(AuthenticationException::class); $this->expectExceptionMessage('The token is not supported by this authentication provider.'); @@ -74,7 +74,7 @@ public function testAuthenticateWrongProviderKey() $this->authProvider->authenticate($rememberMeToken); } - public function testAuthenticateWrongSecret() + public function testAuthenticateWrongSecret(): void { $this->expectException(AuthenticationException::class); @@ -100,7 +100,7 @@ public function testAuthenticateWrongSecret() $this->authProvider->authenticate($rememberMeToken); } - public function testAuthenticate() + public function testAuthenticate(): void { $apiUser = $this->createMock(ApiUser::class); $apiUser @@ -112,9 +112,10 @@ public function testAuthenticate() $rememberMeToken = new RememberMeToken($tokenUser, 'my provider secret', 'my secret'); $authenticatedToken = $this->authProvider->authenticate($rememberMeToken); + self::assertEquals( - [$rememberMeToken->getProviderKey(), $rememberMeToken->getSecret(), $rememberMeToken->getUsername()], - [$authenticatedToken->getProviderKey(), $authenticatedToken->getSecret(), $authenticatedToken->getUsername()] + [$rememberMeToken->getFirewallName(), $rememberMeToken->getSecret()], + [$authenticatedToken->getFirewallName(), $authenticatedToken->getSecret()] ); } } diff --git a/tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php b/tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php deleted file mode 100644 index 9b4de62073..0000000000 --- a/tests/lib/MVC/Symfony/Security/EventListener/SecurityListenerTest.php +++ /dev/null @@ -1,415 +0,0 @@ -configResolver = $this->createMock(ConfigResolverInterface::class); - $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); - $this->tokenStorage = $this->createMock(TokenStorageInterface::class); - $this->authChecker = $this->createMock(AuthorizationCheckerInterface::class); - $this->permissionResolver = $this->createMock(PermissionResolver::class); - $this->userService = $this->createMock(UserService::class); - $this->listener = $this->generateListener(); - } - - protected function generateListener() - { - return new SecurityListener( - $this->permissionResolver, - $this->userService, - $this->configResolver, - $this->eventDispatcher, - $this->tokenStorage, - $this->authChecker - ); - } - - public function testGetSubscribedEvents() - { - self::assertSame( - [ - SecurityEvents::INTERACTIVE_LOGIN => [ - ['onInteractiveLogin', 10], - ['checkSiteAccessPermission', 9], - ], - KernelEvents::REQUEST => ['onKernelRequest', 7], - ], - SecurityListener::getSubscribedEvents() - ); - } - - public function testOnInteractiveLoginAlreadyEzUser() - { - $user = $this->createMock(UserInterface::class); - $token = $this->createMock(TokenInterface::class); - $token - ->expects(self::once()) - ->method('getUser') - ->will(self::returnValue($user)); - $event = new BaseInteractiveLoginEvent(new Request(), $token); - - $this->eventDispatcher - ->expects(self::never()) - ->method('dispatch'); - - $this->listener->onInteractiveLogin($event); - } - - public function testOnInteractiveLoginNotUserObject() - { - $user = 'foobar'; - $token = $this->createMock(TokenInterface::class); - $token - ->expects(self::once()) - ->method('getUser') - ->will(self::returnValue($user)); - $event = new BaseInteractiveLoginEvent(new Request(), $token); - - $this->eventDispatcher - ->expects(self::never()) - ->method('dispatch'); - - $this->listener->onInteractiveLogin($event); - } - - public function testOnInteractiveLogin() - { - $user = $this->createMock(SymfonyUserInterface::class); - $token = $this->createMock(TokenInterface::class); - $token - ->expects(self::once()) - ->method('getUser') - ->will(self::returnValue($user)); - $token - ->expects(self::once()) - ->method('getRoleNames') - ->will(self::returnValue(['ROLE_USER'])); - $token - ->expects(self::once()) - ->method('getAttributes') - ->will(self::returnValue(['foo' => 'bar'])); - - $event = new BaseInteractiveLoginEvent(new Request(), $token); - - $anonymousUserId = 10; - $this->configResolver - ->expects(self::once()) - ->method('getParameter') - ->with('anonymous_user_id') - ->will(self::returnValue($anonymousUserId)); - - $apiUser = $this->createMock(APIUser::class); - $this->userService - ->expects(self::once()) - ->method('loadUser') - ->with($anonymousUserId) - ->will(self::returnValue($apiUser)); - - $this->permissionResolver - ->expects(self::once()) - ->method('setCurrentUserReference') - ->with($apiUser); - - $this->tokenStorage - ->expects(self::once()) - ->method('setToken') - ->with(self::isInstanceOf(InteractiveLoginToken::class)); - - $this->listener->onInteractiveLogin($event); - } - - public function testCheckSiteAccessPermissionDenied() - { - $this->expectException(UnauthorizedSiteAccessException::class); - - $user = $this->createMock(UserInterface::class); - $token = $this->createMock(TokenInterface::class); - $token - ->expects(self::once()) - ->method('getUser') - ->will(self::returnValue($user)); - - $request = new Request(); - $siteAccess = new SiteAccess('test'); - $request->attributes->set('siteaccess', $siteAccess); - - $this->authChecker - ->expects(self::once()) - ->method('isGranted') - ->with(self::equalTo(new Attribute('user', 'login', ['valueObject' => $siteAccess]))) - ->will(self::returnValue(false)); - - $this->listener->checkSiteAccessPermission(new BaseInteractiveLoginEvent($request, $token)); - } - - public function testCheckSiteAccessPermissionGranted() - { - $user = $this->createMock(UserInterface::class); - $token = $this->createMock(TokenInterface::class); - $token - ->expects(self::once()) - ->method('getUser') - ->will(self::returnValue($user)); - - $request = new Request(); - $siteAccess = new SiteAccess('test'); - $request->attributes->set('siteaccess', $siteAccess); - - $this->authChecker - ->expects(self::once()) - ->method('isGranted') - ->with(self::equalTo(new Attribute('user', 'login', ['valueObject' => $siteAccess]))) - ->will(self::returnValue(true)); - - // Nothing should happen or should be returned. - $this->listener->checkSiteAccessPermission(new BaseInteractiveLoginEvent($request, $token)); - } - - public function testCheckSiteAccessNotEzUser() - { - $user = $this->createMock(SymfonyUserInterface::class); - $token = $this->createMock(TokenInterface::class); - $token - ->expects(self::once()) - ->method('getUser') - ->will(self::returnValue($user)); - - $request = new Request(); - $siteAccess = new SiteAccess('test'); - $request->attributes->set('siteaccess', $siteAccess); - - $this->authChecker - ->expects(self::never()) - ->method('isGranted'); - - $this->listener->checkSiteAccessPermission(new BaseInteractiveLoginEvent($request, $token)); - } - - public function testCheckSiteAccessNoSiteAccess() - { - $user = $this->createMock(UserInterface::class); - $token = $this->createMock(TokenInterface::class); - $token - ->expects(self::once()) - ->method('getUser') - ->will(self::returnValue($user)); - - $this->authChecker - ->expects(self::never()) - ->method('isGranted'); - - $this->listener->checkSiteAccessPermission(new BaseInteractiveLoginEvent(new Request(), $token)); - } - - public function testOnKernelRequestSubRequest() - { - $event = new RequestEvent( - $this->createMock(HttpKernelInterface::class), - new Request(), - HttpKernelInterface::SUB_REQUEST - ); - - $this->tokenStorage - ->expects(self::never()) - ->method('getToken'); - $this->authChecker - ->expects(self::never()) - ->method('isGranted'); - - $this->listener->onKernelRequest($event); - } - - public function testOnKernelRequestSubRequestFragment() - { - $event = new RequestEvent( - $this->createMock(HttpKernelInterface::class), - Request::create('/_fragment'), - HttpKernelInterface::MASTER_REQUEST - ); - $this->configResolver - ->expects(self::never()) - ->method('getParameter'); - - $this->tokenStorage - ->expects(self::never()) - ->method('getToken'); - $this->authChecker - ->expects(self::never()) - ->method('isGranted'); - - $this->listener->onKernelRequest($event); - } - - public function testOnKernelRequestNoSiteAccess() - { - $event = new RequestEvent( - $this->createMock(HttpKernelInterface::class), - new Request(), - HttpKernelInterface::MASTER_REQUEST - ); - - $this->tokenStorage - ->expects(self::never()) - ->method('getToken'); - $this->authChecker - ->expects(self::never()) - ->method('isGranted'); - - $this->listener->onKernelRequest($event); - } - - public function testOnKernelRequestNullToken() - { - $request = new Request(); - $request->attributes->set('siteaccess', new SiteAccess('test')); - $event = new RequestEvent( - $this->createMock(HttpKernelInterface::class), - $request, - HttpKernelInterface::MASTER_REQUEST - ); - - $this->tokenStorage - ->expects(self::once()) - ->method('getToken') - ->will(self::returnValue(null)); - $this->authChecker - ->expects(self::never()) - ->method('isGranted'); - - $this->listener->onKernelRequest($event); - } - - public function testOnKernelRequestLoginRoute() - { - $request = new Request(); - $request->attributes->set('siteaccess', new SiteAccess('test')); - $request->attributes->set('_route', 'login'); - $event = new RequestEvent( - $this->createMock(HttpKernelInterface::class), - $request, - HttpKernelInterface::MASTER_REQUEST - ); - - $this->tokenStorage - ->expects(self::once()) - ->method('getToken') - ->will(self::returnValue(null)); - $this->authChecker - ->expects(self::never()) - ->method('isGranted'); - - $this->listener->onKernelRequest($event); - } - - public function testOnKernelRequestAccessDenied() - { - $this->expectException(UnauthorizedSiteAccessException::class); - - $request = new Request(); - $request->attributes->set('siteaccess', new SiteAccess('test')); - $event = new RequestEvent( - $this->createMock(HttpKernelInterface::class), - $request, - HttpKernelInterface::MASTER_REQUEST - ); - - $token = $this->createMock(TokenInterface::class); - $token - ->expects(self::any()) - ->method('getUsername') - ->will(self::returnValue('foo')); - - $this->tokenStorage - ->expects(self::once()) - ->method('getToken') - ->will(self::returnValue($token)); - $this->authChecker - ->expects(self::once()) - ->method('isGranted') - ->will(self::returnValue(false)); - - $this->listener->onKernelRequest($event); - } - - public function testOnKernelRequestAccessGranted() - { - $request = new Request(); - $request->attributes->set('siteaccess', new SiteAccess('test')); - $event = new RequestEvent( - $this->createMock(HttpKernelInterface::class), - $request, - HttpKernelInterface::MASTER_REQUEST - ); - - $token = $this->createMock(TokenInterface::class); - $token - ->expects(self::any()) - ->method('getUsername') - ->will(self::returnValue('foo')); - - $this->tokenStorage - ->expects(self::once()) - ->method('getToken') - ->will(self::returnValue($token)); - $this->authChecker - ->expects(self::once()) - ->method('isGranted') - ->will(self::returnValue(true)); - - // Nothing should happen or should be returned. - $this->listener->onKernelRequest($event); - } -}