diff --git a/typo3/sysext/backend/Tests/Functional/Controller/MfaConfigurationControllerTest.php b/typo3/sysext/backend/Tests/Functional/Controller/MfaConfigurationControllerTest.php index 00062f189f9211c904e8310077ef5989effa09a1..46385fa792ebc6e37ffe0d8fa10acfebb89d9f43 100644 --- a/typo3/sysext/backend/Tests/Functional/Controller/MfaConfigurationControllerTest.php +++ b/typo3/sysext/backend/Tests/Functional/Controller/MfaConfigurationControllerTest.php @@ -27,7 +27,6 @@ use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Core\Bootstrap; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder; -use TYPO3\CMS\Core\FormProtection\FormProtectionFactory; use TYPO3\CMS\Core\Http\NormalizedParams; use TYPO3\CMS\Core\Http\ServerRequest; use TYPO3\CMS\Core\Imaging\IconFactory; @@ -68,12 +67,6 @@ class MfaConfigurationControllerTest extends FunctionalTestCase $this->normalizedParams = new NormalizedParams([], [], '', ''); } - protected function tearDown(): void - { - FormProtectionFactory::purgeInstances(); - parent::tearDown(); - } - /** * @test */ diff --git a/typo3/sysext/backend/Tests/Functional/Controller/MfaControllerTest.php b/typo3/sysext/backend/Tests/Functional/Controller/MfaControllerTest.php index 1bb4fdf0e82280402be2cf3b62440e301a157615..ca59bb603504c2c3879e6a0db9e5aa14c54a6021 100644 --- a/typo3/sysext/backend/Tests/Functional/Controller/MfaControllerTest.php +++ b/typo3/sysext/backend/Tests/Functional/Controller/MfaControllerTest.php @@ -28,7 +28,6 @@ use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Core\Bootstrap; use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder; -use TYPO3\CMS\Core\FormProtection\FormProtectionFactory; use TYPO3\CMS\Core\Http\ServerRequest; use TYPO3\CMS\Core\Log\Logger; use TYPO3\CMS\Core\Page\PageRenderer; @@ -76,12 +75,6 @@ class MfaControllerTest extends FunctionalTestCase ->withAttribute('route', new Route('path', ['packageName' => 'typo3/cms-backend'])); } - protected function tearDown(): void - { - FormProtectionFactory::purgeInstances(); - parent::tearDown(); - } - /** * @test */ diff --git a/typo3/sysext/core/Classes/FormProtection/FormProtectionFactory.php b/typo3/sysext/core/Classes/FormProtection/FormProtectionFactory.php index d72d74b9a1dc7a9d258cda195ec4a76f1cdaa38d..1d2af659cec0975c07289194fb4df26d87793007 100644 --- a/typo3/sysext/core/Classes/FormProtection/FormProtectionFactory.php +++ b/typo3/sysext/core/Classes/FormProtection/FormProtectionFactory.php @@ -19,6 +19,7 @@ namespace TYPO3\CMS\Core\FormProtection; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder; use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Localization\LanguageServiceFactory; @@ -41,17 +42,11 @@ use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; */ class FormProtectionFactory { - /** - * created instances of form protections using the type as array key - * - * @var array<string, AbstractFormProtection> - */ - protected static array $instances = []; - public function __construct( protected readonly FlashMessageService $flashMessageService, protected readonly LanguageServiceFactory $languageServiceFactory, - protected readonly Registry $registry + protected readonly Registry $registry, + protected readonly FrontendInterface $runtimeCache ) { } @@ -65,12 +60,13 @@ class FormProtectionFactory if (!in_array($type, ['installtool', 'frontend', 'backend', 'disabled'], true)) { $type = 'disabled'; } - if (isset(self::$instances[$type])) { - return self::$instances[$type]; + $identifier = $this->getIdentifierForType($type); + if ($this->runtimeCache->has($identifier)) { + return $this->runtimeCache->get($identifier); } $classNameAndConstructorArguments = $this->getClassNameAndConstructorArguments($type, $GLOBALS['TYPO3_REQUEST'] ?? null); - self::$instances[$type] = self::createInstance(...$classNameAndConstructorArguments); - return self::$instances[$type]; + $this->runtimeCache->set($identifier, $this->createInstance(...$classNameAndConstructorArguments)); + return $this->runtimeCache->get($identifier); } /** @@ -80,12 +76,13 @@ class FormProtectionFactory public function createFromRequest(ServerRequestInterface $request): AbstractFormProtection { $type = $this->determineTypeFromRequest($request); - if (isset(self::$instances[$type])) { - return self::$instances[$type]; + $identifier = $this->getIdentifierForType($type); + if ($this->runtimeCache->has($identifier)) { + return $this->runtimeCache->get($identifier); } $classNameAndConstructorArguments = $this->getClassNameAndConstructorArguments($type, $request); - self::$instances[$type] = self::createInstance(...$classNameAndConstructorArguments); - return self::$instances[$type]; + $this->runtimeCache->set($identifier, $this->createInstance(...$classNameAndConstructorArguments)); + return $this->runtimeCache->get($identifier); } /** @@ -93,13 +90,13 @@ class FormProtectionFactory */ protected function determineTypeFromRequest(ServerRequestInterface $request): string { - if (self::isInstallToolSession($request)) { + if ($this->isInstallToolSession($request)) { return 'installtool'; } - if (self::isFrontendSession($request)) { + if ($this->isFrontendSession($request)) { return 'frontend'; } - if (self::isBackendSession($request)) { + if ($this->isBackendSession($request)) { return 'backend'; } return 'disabled'; @@ -137,7 +134,7 @@ class FormProtectionFactory BackendFormProtection::class, $user, $this->registry, - self::getMessageClosure( + $this->getMessageClosure( $this->languageServiceFactory->createFromUserPreferences($user), $this->flashMessageService->getMessageQueueByIdentifier(), $isAjaxCall @@ -151,6 +148,14 @@ class FormProtectionFactory ]; } + /** + * Conveniant method to create a deterministic cache identifier. + */ + protected function getIdentifierForType(string $type): string + { + return 'formprotection-instance-' . hash('xxh3', $type); + } + /** * Gets a form protection instance for the requested type or class. * @@ -163,114 +168,82 @@ class FormProtectionFactory * frontend, backend, installtool * @param array<int,mixed> $constructorArguments Arguments for the class-constructor * @return \TYPO3\CMS\Core\FormProtection\AbstractFormProtection the requested instance + * + * @deprecated since v12, will be removed in v13 together with createForTypeWithArguments. Use a instance of FormProtectionFactory directly. + * @see self::createFromRequest() + * @see self::createForType() + * @see self::createForClass() */ public static function get($classNameOrType = 'default', ...$constructorArguments) { - if (isset(self::$instances[$classNameOrType])) { - return self::$instances[$classNameOrType]; - } - if ($classNameOrType === 'default' || $classNameOrType === 'installtool' || $classNameOrType === 'frontend' || $classNameOrType === 'backend') { - $classNameAndConstructorArguments = self::getClassNameAndConstructorArgumentsByType($classNameOrType); - self::$instances[$classNameOrType] = self::createInstance(...$classNameAndConstructorArguments); - } else { - self::$instances[$classNameOrType] = self::createInstance($classNameOrType, ...$constructorArguments); + trigger_error( + __METHOD__ . ' will be removed in TYPO3 v13.0. Use a instance of ' . __CLASS__ . ' directly.', + E_USER_DEPRECATED + ); + if (($classNameOrType === 'default' || $classNameOrType === 'installtool' || $classNameOrType === 'frontend' || $classNameOrType === 'backend')) { + return GeneralUtility::makeInstance(FormProtectionFactory::class) + ->createForType($classNameOrType); } - return self::$instances[$classNameOrType]; + return GeneralUtility::makeInstance(FormProtectionFactory::class) + ->createForClass($classNameOrType, ...$constructorArguments); } /** - * Returns the class name and parameters depending on the given type. - * If the type cannot be used currently, protection is disabled. + * Create a concrete FormProtection implementation, using the provided arguments as constructor arguments. + * Should be used instead of FormProtectionFactory::get() is a custom FormProtection implementation must + * be instantiated. * - * @param string $type Valid types: default, installtool, frontend, backend. "default" makes an autodetection on the current state - * @return array Array of arguments + * For provided core implementation use + * + * // auto-detected based on request + * GeneralUtility::makeInstance(FormProtectionFactory::class) + * ->createFromRequest($GLOBAL['TYPO3_REQUEST']); + * or + * // concrete implementation + * GeneralUtility::makeInstance(FormProtectionFactory::class) + * ->createForType($type); // 'installtool', 'backend', 'frontend', 'disabled' + * + * @param string $className Name of a form protection class + * @param array<int,mixed> $constructorArguments Arguments for the class-constructor + * @deprecated since v12, will be removed in v13 together with get(). */ - protected static function getClassNameAndConstructorArgumentsByType($type, ServerRequestInterface $request = null) + protected function createForClass(string $className, ...$constructorArguments): AbstractFormProtection { - if (self::isInstallToolSession($request) && ($type === 'default' || $type === 'installtool')) { - $classNameAndConstructorArguments = [ - InstallToolFormProtection::class, - ]; - } elseif (self::isFrontendSession($request) && ($type === 'default' || $type === 'frontend')) { - $classNameAndConstructorArguments = [ - FrontendFormProtection::class, - $GLOBALS['TSFE']->fe_user, - ]; - } elseif (self::isBackendSession($request) && ($type === 'default' || $type === 'backend')) { - $isAjaxCall = false; - $request = $request ?? $GLOBALS['TYPO3_REQUEST'] ?? null; - if ($request instanceof ServerRequestInterface - && (bool)($request->getAttribute('route')?->getOption('ajax')) - ) { - $isAjaxCall = true; - } - $classNameAndConstructorArguments = [ - BackendFormProtection::class, - $GLOBALS['BE_USER'], - GeneralUtility::makeInstance(Registry::class), - self::getMessageClosure( - $GLOBALS['LANG'], - GeneralUtility::makeInstance(FlashMessageService::class)->getMessageQueueByIdentifier(), - $isAjaxCall - ), - ]; - } else { - // failed to use preferred type, disable form protection - $classNameAndConstructorArguments = [ - DisabledFormProtection::class, - ]; + $identifier = $this->getIdentifierForType($className); + if ($this->runtimeCache->has($identifier)) { + return $this->runtimeCache->get($identifier); } - return $classNameAndConstructorArguments; + $this->runtimeCache->set($identifier, $this->createInstance($className, ...$constructorArguments)); + return $this->runtimeCache->get($identifier); } /** * Check if we are in the install tool */ - protected static function isInstallToolSession(?ServerRequestInterface $request = null): bool + protected function isInstallToolSession(ServerRequestInterface $request): bool { - $isInstallTool = false; - $request = $request ?? $GLOBALS['TYPO3_REQUEST'] ?? null; - if ($request instanceof ServerRequestInterface - && (bool)((int)$request->getAttribute('applicationType') & SystemEnvironmentBuilder::REQUESTTYPE_INSTALL) - ) { - $isInstallTool = true; - } - return $isInstallTool; + return (bool)((int)$request->getAttribute('applicationType') & SystemEnvironmentBuilder::REQUESTTYPE_INSTALL); } /** * Checks if a user is logged in and the session is active. */ - protected static function isBackendSession(?ServerRequestInterface $request = null): bool + protected function isBackendSession(ServerRequestInterface $request): bool { - if ($request instanceof ServerRequestInterface) { - $user = $request->getAttribute('backend.user', $GLOBALS['BE_USER'] ?? null); - } else { - $user = $GLOBALS['BE_USER'] ?? null; - } + $user = $request->getAttribute('backend.user', $GLOBALS['BE_USER'] ?? null); return $user instanceof BackendUserAuthentication && isset($user->user['uid']); } /** * Checks if a frontend user is logged in and the session is active. */ - protected static function isFrontendSession(?ServerRequestInterface $request = null): bool + protected function isFrontendSession(ServerRequestInterface $request): bool { - if ($request instanceof ServerRequestInterface) { - $user = $request->getAttribute('frontend.user'); - } else { - $user = ($GLOBALS['TSFE'] ?? null) instanceof TypoScriptFrontendController ? $GLOBALS['TSFE']->fe_user : null; - } + $user = $request->getAttribute('frontend.user'); return $user instanceof FrontendUserAuthentication && isset($user->user['uid']); } - /** - * @param LanguageService $languageService - * @param FlashMessageQueue $messageQueue - * @param bool $isAjaxCall - * @return \Closure - */ - protected static function getMessageClosure(LanguageService $languageService, FlashMessageQueue $messageQueue, bool $isAjaxCall) + protected function getMessageClosure(LanguageService $languageService, FlashMessageQueue $messageQueue, bool $isAjaxCall): \Closure { return static function () use ($languageService, $messageQueue, $isAjaxCall) { $flashMessage = GeneralUtility::makeInstance( @@ -288,12 +261,11 @@ class FormProtectionFactory * Creates an instance for the requested class $className * and stores it internally. * - * @param string $className + * @param class-string $className * @param array<int,mixed> $constructorArguments * @throws \InvalidArgumentException - * @return AbstractFormProtection */ - protected static function createInstance($className, ...$constructorArguments) + protected function createInstance(string $className, ...$constructorArguments): AbstractFormProtection { if (!class_exists($className)) { throw new \InvalidArgumentException('$className must be the name of an existing class, but actually was "' . $className . '".', 1285352962); @@ -309,20 +281,14 @@ class FormProtectionFactory * Purges all existing instances. * * This function is particularly useful when cleaning up in unit testing. + * + * @deprecated since v12, will be removed in v13. Internal cache has been replaced by runtime cache. */ - public static function purgeInstances() - { - foreach (self::$instances as $key => $instance) { - unset(self::$instances[$key]); - } - } - - /** - * Only used in test for non-static calls - * @internal - */ - public function clearInstances(): void + public static function purgeInstances(): void { - self::$instances = []; + trigger_error( + __METHOD__ . ' will be removed in TYPO3 v13.0. Cache has been replaced with runtime cache.', + E_USER_DEPRECATED + ); } } diff --git a/typo3/sysext/core/Classes/ServiceProvider.php b/typo3/sysext/core/Classes/ServiceProvider.php index 94846244e7a5f7ebda4d18143014edf2fd4a6c40..d5860759c84b61340d1095a989942ddd34bbbed5 100644 --- a/typo3/sysext/core/Classes/ServiceProvider.php +++ b/typo3/sysext/core/Classes/ServiceProvider.php @@ -23,6 +23,7 @@ use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Console\Command\HelpCommand; use Symfony\Component\EventDispatcher\EventDispatcher as SymfonyEventDispatcher; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as SymfonyEventDispatcherInterface; +use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\DependencyInjection\ContainerBuilder; @@ -512,6 +513,7 @@ class ServiceProvider extends AbstractServiceProvider $container->get(Messaging\FlashMessageService::class), $container->get(Localization\LanguageServiceFactory::class), $container->get(Registry::class), + $container->get(CacheManager::class)->getCache('runtime'), ] ); } diff --git a/typo3/sysext/core/Documentation/Changelog/12.1/Deprecation-99098-StaticUsageOfFormProtectionFactory.rst b/typo3/sysext/core/Documentation/Changelog/12.1/Deprecation-99098-StaticUsageOfFormProtectionFactory.rst new file mode 100644 index 0000000000000000000000000000000000000000..49d43fb0732ab1f66d7a584d6e967ff0fc4fe91c --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/12.1/Deprecation-99098-StaticUsageOfFormProtectionFactory.rst @@ -0,0 +1,140 @@ +.. include:: /Includes.rst.txt + +.. _deprecation-99098-1668546853: + +=========================================================== +Deprecation: #99098 - Static usage of FormProtectionFactory +=========================================================== + +See :issue:`99098` + +Description +=========== + +:php:`\TYPO3\CMS\Core\FormProtection\FormProtectionFactory` has been +constructed in a static class manner in TYPO3 v6.2, using a static property +based instance cache to avoid recreating instances for a specific typed +FormProtection implementation. This design made it impossible to retrieve an +instance of this class via dependency injection. Another side-effect was that +ensuring properly cleared state between tests has been hard and often +populated to other tests and thus influencing them. + +To mitigate these issues, :php:`\TYPO3\CMS\Core\FormProtection\FormProtectionFactory` +is now transformed to a non-static class usage with injected services +and the core runtime cache, removing the static property cache. + +Based on these changes, the old static methods :php:`get()` and +:php:`purgeInstances()` are now deprecated. + +There are two general ways to get a specific FormProtection implementation: + +* auto-detected from request: :php:`$formProtectionFactory->createFromRequest()` +* create for a specific type: :php:`$formProtectionFactory->createForType()` + +Possible types for :php:`$formProtectionFactory->createForType()` are `frontend` +`backend`, `installtool` or `disabled`. + + +Impact +====== + +Using any of following class methods + +* :php:`\TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get()` +* :php:`\TYPO3\CMS\Core\FormProtection\FormProtectionFactory::purgeInstances()` + +will trigger a PHP deprecation notice and will throw a fatal PHP error in +TYPO3 v13. + + +Affected installations +====================== + +The extension scanner will find extensions calling :php:`FormProtectionFactory::get()` +or :php:`FormProtectionFactory::purgeInstances()` as "strong" matches. + + +Migration +========= + +Provided implementation by TYPO3 core +------------------------------------- + +Before + +.. code-block:: php + + // use TYPO3\CMS\Core\FormProtection\FormProtectionFactory; + + // BackendFormProtection + $formProtection = FormProtectionFactory::get(BackendFormProtection::class); + $formProtection = FormProtectionFactory::get('backend'); + + // FrontendFormProtection + $formProtection = FormProtectionFactory::get(FrontedFormProtection::class); + $formProtection = FormProtectionFactory::get('frontend'); + + // Default / Disabled FormProtection + $formProtection = FormProtectionFactory::get(DisabledFormProtection::class); + $formProtection = FormProtectionFactory::get('default'); + +After + +It is recommended to use :php:`FormProtectionFactory->createForRequest()` to +auto-detect which type is needed and return the corresponding instance: + +.. code-block:: php + + // use TYPO3\CMS\Core\FormProtection\FormProtectionFactory; + + // Better: Get FormProtectionFactory injected by DI. + $formProtectionFactory = GeneralUtility::makeInstance(FormProtectionFactory::class); + // $request is assumed to be available, for instance in controller classes. + $formProtection = $formProtectionFactory->createFromRequest($request); + +To create a specific type directly, using following replacements: + +.. code-block:: php + + // use TYPO3\CMS\Core\FormProtection\FormProtectionFactory; + // Better: Get FormProtectionFactory injected by DI. + $formProtectionFactory = GeneralUtility::makeInstance(FormProtectionFactory::class); + + // BackendFormProtection + $formProtection = $formProtectionFactory->createFromType('backend'); + + // FrontendFormProtection + $formProtection = $formProtectionFactory->createFromType('frontend'); + + // Default / Disabled FormProtection + $formProtection = $formProtectionFactory->createFromType('disabled'); + +Custom FormProtection based implementation +------------------------------------------ + +Before + +.. code-block:: php + + // use TYPO3\CMS\Core\FormProtection\FormProtectionFactory; + + $formProtection = FormProtectionFactory::get( + Vendor\ExtensionKey\FormProtection\CustomFormProtection::class, + $customService, + 'someDirectValue', + ... + ); + +After + +.. code-block:: php + + // Create an instance of the class yourself, take care of an + // instance cache if needed. + GeneralUtility::makeInstance( + Vendor\ExtensionKey\FormProtection\CustomFormProtection::class, + $constructorArguments + ); + + +.. index:: PHP-API, FullyScanned, ext:core diff --git a/typo3/sysext/core/Tests/Functional/Authentication/Mfa/Provider/RecoveryCodesProviderTest.php b/typo3/sysext/core/Tests/Functional/Authentication/Mfa/Provider/RecoveryCodesProviderTest.php index 014f980db276dfea615213159e4fa624f9ef2d0c..83d58079f63b7c7743f0daef8721ae2aec72b416 100644 --- a/typo3/sysext/core/Tests/Functional/Authentication/Mfa/Provider/RecoveryCodesProviderTest.php +++ b/typo3/sysext/core/Tests/Functional/Authentication/Mfa/Provider/RecoveryCodesProviderTest.php @@ -25,7 +25,6 @@ use TYPO3\CMS\Core\Authentication\Mfa\MfaViewType; use TYPO3\CMS\Core\Authentication\Mfa\Provider\RecoveryCodes; use TYPO3\CMS\Core\Crypto\PasswordHashing\Argon2iPasswordHash; use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory; -use TYPO3\CMS\Core\FormProtection\FormProtectionFactory; use TYPO3\CMS\Core\Http\PropagateResponseException; use TYPO3\CMS\Core\Http\ServerRequest; use TYPO3\CMS\Core\Localization\LanguageServiceFactory; @@ -61,12 +60,6 @@ class RecoveryCodesProviderTest extends FunctionalTestCase $this->subject = $this->get(MfaProviderRegistry::class)->getProvider('recovery-codes'); } - protected function tearDown(): void - { - FormProtectionFactory::purgeInstances(); - parent::tearDown(); - } - /** * @test */ diff --git a/typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php b/typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php index a67ed5824ea009a0d40ddad585f37a946de67adf..d24e57e27d64bd1d7e700392512e697e25915097 100644 --- a/typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php +++ b/typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php @@ -20,6 +20,8 @@ namespace TYPO3\CMS\Core\Tests\Unit\Authentication; use Psr\Log\NullLogger; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Authentication\IpLocker; +use TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend; +use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder; @@ -40,12 +42,6 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase; class BackendUserAuthenticationTest extends UnitTestCase { - public function tearDown(): void - { - FormProtectionFactory::purgeInstances(); - parent::tearDown(); - } - /** * @test */ @@ -63,10 +59,12 @@ class BackendUserAuthenticationTest extends UnitTestCase $formProtectionMock = $this->createMock(BackendFormProtection::class); $formProtectionMock->expects(self::once())->method('clean'); + $runtimeCache = new VariableFrontend('null', new TransientMemoryBackend('null', ['logger' => new NullLogger()])); $formProtectionFactory = new FormProtectionFactory( $this->createMock(FlashMessageService::class), $this->createMock(LanguageServiceFactory::class), - $this->createMock(Registry::class) + $this->createMock(Registry::class), + $runtimeCache ); GeneralUtility::addInstance(FormProtectionFactory::class, $formProtectionFactory); GeneralUtility::addInstance(BackendFormProtection::class, $formProtectionMock); diff --git a/typo3/sysext/core/Tests/Unit/FormProtection/FormProtectionFactoryTest.php b/typo3/sysext/core/Tests/Unit/FormProtection/FormProtectionFactoryTest.php index 7f0537106d331e410cf4595ebbbb3b1a5392fba5..a31b3c3ec6c5e5e2953eafcb94198136d90b1280 100644 --- a/typo3/sysext/core/Tests/Unit/FormProtection/FormProtectionFactoryTest.php +++ b/typo3/sysext/core/Tests/Unit/FormProtection/FormProtectionFactoryTest.php @@ -17,8 +17,12 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Tests\Unit\FormProtection; +use Psr\Log\NullLogger; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend; +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; use TYPO3\CMS\Core\Cache\Frontend\NullFrontend; +use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend; use TYPO3\CMS\Core\FormProtection\BackendFormProtection; use TYPO3\CMS\Core\FormProtection\DisabledFormProtection; use TYPO3\CMS\Core\FormProtection\FormProtectionFactory; @@ -34,9 +38,11 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase; class FormProtectionFactoryTest extends UnitTestCase { protected FormProtectionFactory $subject; + protected FrontendInterface $runtimeCacheMock; protected function setUp(): void { + $this->runtimeCacheMock = new VariableFrontend('null', new TransientMemoryBackend('null', ['logger' => new NullLogger()])); $this->subject = new FormProtectionFactory( new FlashMessageService(), new LanguageServiceFactory( @@ -44,104 +50,18 @@ class FormProtectionFactoryTest extends UnitTestCase $this->createMock(LocalizationFactory::class), new NullFrontend('null') ), - new Registry() + new Registry(), + $this->runtimeCacheMock ); parent::setUp(); } protected function tearDown(): void { - FormProtectionFactory::purgeInstances(); + $this->runtimeCacheMock->flush(); parent::tearDown(); } - ///////////////////////// - // Tests concerning get - ///////////////////////// - /** - * @test - */ - public function getForNotExistingClassThrowsException(): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionCode(1285352962); - - FormProtectionFactory::get('noSuchClass'); - } - - /** - * @test - */ - public function getForClassThatIsNoFormProtectionSubclassThrowsException(): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionCode(1285353026); - - FormProtectionFactory::get(self::class); - } - - /** - * @test - */ - public function getForTypeBackEndWithExistingBackEndReturnsBackEndFormProtection(): void - { - $userMock = $this->createMock(BackendUserAuthentication::class); - $userMock->user = ['uid' => 4711]; - self::assertInstanceOf( - BackendFormProtection::class, - FormProtectionFactory::get( - BackendFormProtection::class, - $userMock, - $this->createMock(Registry::class) - ) - ); - } - - /** - * @test - */ - public function getForTypeBackEndCalledTwoTimesReturnsTheSameInstance(): void - { - $userMock = $this->createMock(BackendUserAuthentication::class); - $userMock->user = ['uid' => 4711]; - $arguments = [ - BackendFormProtection::class, - $userMock, - $this->createMock(Registry::class), - ]; - self::assertSame( - FormProtectionFactory::get(...$arguments), - FormProtectionFactory::get(...$arguments) - ); - } - - /** - * @test - */ - public function getForTypeInstallToolReturnsInstallToolFormProtection(): void - { - self::assertInstanceOf( - InstallToolFormProtection::class, - FormProtectionFactory::get(InstallToolFormProtection::class) - ); - } - - /** - * @test - */ - public function getForTypeInstallToolCalledTwoTimesReturnsTheSameInstance(): void - { - self::assertSame(FormProtectionFactory::get(InstallToolFormProtection::class), FormProtectionFactory::get(InstallToolFormProtection::class)); - } - - /** - * @test - */ - public function getForTypesInstallToolAndDisabledReturnsDifferentInstances(): void - { - self::assertNotSame(FormProtectionFactory::get(InstallToolFormProtection::class), FormProtectionFactory::get(DisabledFormProtection::class)); - } - /** * @test */ @@ -225,7 +145,8 @@ class FormProtectionFactoryTest extends UnitTestCase $formProtection = $this->subject->createForType('backend'); // User is now logged in, but we still get the disabled form protection due to the "singleton" concept self::assertInstanceOf(DisabledFormProtection::class, $formProtection); - $this->subject->clearInstances(); + // we need to manually flush this here, aas next test should expect a cleared state. + $this->runtimeCacheMock->flush(); $formProtection = $this->subject->createForType('backend'); // User is now logged in, now we get a backend form protection, due to the "purge instance" concept. self::assertInstanceOf(BackendFormProtection::class, $formProtection); diff --git a/typo3/sysext/core/Tests/UnitDeprecated/FormProtection/FormProtectionFactoryTest.php b/typo3/sysext/core/Tests/UnitDeprecated/FormProtection/FormProtectionFactoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6aabb4d687413bd84b9e1f1180988e70c2dbac65 --- /dev/null +++ b/typo3/sysext/core/Tests/UnitDeprecated/FormProtection/FormProtectionFactoryTest.php @@ -0,0 +1,167 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Core\Tests\UnitDeprecated\FormProtection; + +use Psr\Log\NullLogger; +use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend; +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Core\Cache\Frontend\NullFrontend; +use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend; +use TYPO3\CMS\Core\FormProtection\BackendFormProtection; +use TYPO3\CMS\Core\FormProtection\DisabledFormProtection; +use TYPO3\CMS\Core\FormProtection\FormProtectionFactory; +use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection; +use TYPO3\CMS\Core\Localization\LanguageServiceFactory; +use TYPO3\CMS\Core\Localization\Locales; +use TYPO3\CMS\Core\Localization\LocalizationFactory; +use TYPO3\CMS\Core\Messaging\FlashMessageService; +use TYPO3\CMS\Core\Registry; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\TestingFramework\Core\Unit\UnitTestCase; + +class FormProtectionFactoryTest extends UnitTestCase +{ + protected FormProtectionFactory $subject; + protected FrontendInterface $runtimeCacheMock; + + protected function setUp(): void + { + $this->runtimeCacheMock = new VariableFrontend('null', new TransientMemoryBackend('null', ['logger' => new NullLogger()])); + $this->subject = new FormProtectionFactory( + new FlashMessageService(), + new LanguageServiceFactory( + new Locales(), + $this->createMock(LocalizationFactory::class), + new NullFrontend('null') + ), + new Registry(), + $this->runtimeCacheMock + ); + parent::setUp(); + } + + protected function tearDown(): void + { + $this->runtimeCacheMock->flush(); + parent::tearDown(); + } + + ///////////////////////// + // Tests concerning get + ///////////////////////// + /** + * @test + */ + public function getForNotExistingClassThrowsException(): void + { + GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(1285352962); + + FormProtectionFactory::get('noSuchClass'); + } + + /** + * @test + */ + public function getForClassThatIsNoFormProtectionSubclassThrowsException(): void + { + GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(1285353026); + + FormProtectionFactory::get(self::class); + } + + /** + * @test + */ + public function getForTypeBackEndWithExistingBackEndReturnsBackEndFormProtection(): void + { + GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject); + $userMock = $this->createMock(BackendUserAuthentication::class); + $userMock->user = ['uid' => 4711]; + self::assertInstanceOf( + BackendFormProtection::class, + FormProtectionFactory::get( + BackendFormProtection::class, + $userMock, + $this->createMock(Registry::class) + ) + ); + } + + /** + * @test + */ + public function getForTypeBackEndCalledTwoTimesReturnsTheSameInstance(): void + { + GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject); + GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject); + $userMock = $this->createMock(BackendUserAuthentication::class); + $userMock->user = ['uid' => 4711]; + $arguments = [ + BackendFormProtection::class, + $userMock, + $this->createMock(Registry::class), + ]; + self::assertSame( + FormProtectionFactory::get(...$arguments), + FormProtectionFactory::get(...$arguments) + ); + } + + /** + * @test + */ + public function getForTypeInstallToolReturnsInstallToolFormProtection(): void + { + GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject); + self::assertInstanceOf( + InstallToolFormProtection::class, + FormProtectionFactory::get(InstallToolFormProtection::class) + ); + } + + /** + * @test + */ + public function getForTypeInstallToolCalledTwoTimesReturnsTheSameInstance(): void + { + GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject); + GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject); + self::assertSame( + FormProtectionFactory::get(InstallToolFormProtection::class), + FormProtectionFactory::get(InstallToolFormProtection::class) + ); + } + + /** + * @test + */ + public function getForTypesInstallToolAndDisabledReturnsDifferentInstances(): void + { + GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject); + GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject); + self::assertNotSame( + FormProtectionFactory::get(InstallToolFormProtection::class), + FormProtectionFactory::get(DisabledFormProtection::class) + ); + } +} diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php index 06e09a3fe193c125fea7d38b23b24e3b02b35854..0de5e39e5baef563ba8b344f0ee58ab4f9d3beab 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php @@ -1408,4 +1408,18 @@ return [ 'Feature-98487-TCAOptionCtrlsecurityignorePageTypeRestriction.rst', ], ], + 'TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get' => [ + 'numberOfMandatoryArguments' => 1, + 'maximumNumberOfArguments' => 99, + 'restFiles' => [ + 'Deprecation-99098-StaticUsageOfFormProtectionFactory.rst', + ], + ], + 'TYPO3\CMS\Core\FormProtection\FormProtectionFactory::purgeInstances' => [ + 'numberOfMandatoryArguments' => 1, + 'maximumNumberOfArguments' => 99, + 'restFiles' => [ + 'Deprecation-99098-StaticUsageOfFormProtectionFactory.rst', + ], + ], ];