TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\FormProtection;
|
||||
|
||||
use TYPO3\CMS\Core\Crypto\HashAlgo;
|
||||
use TYPO3\CMS\Core\Crypto\HashService;
|
||||
use TYPO3\CMS\Core\Crypto\Random;
|
||||
use TYPO3\CMS\Core\Security\BlockSerializationTrait;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* This class provides protection against cross-site request forgery (XSRF/CSRF)
|
||||
* for forms.
|
||||
*
|
||||
* For documentation on how to use this class, please see the documentation of
|
||||
* the corresponding subclasses
|
||||
*/
|
||||
abstract class AbstractFormProtection
|
||||
{
|
||||
use BlockSerializationTrait;
|
||||
|
||||
/**
|
||||
* @var \Closure|null
|
||||
*/
|
||||
protected $validationFailedCallback;
|
||||
|
||||
/**
|
||||
* The session token which is used to be hashed during token generation.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $sessionToken;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getSessionToken()
|
||||
{
|
||||
$this->sessionToken = $this->sessionToken ?? $this->retrieveSessionToken();
|
||||
return $this->sessionToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the session token and persists the (empty) token.
|
||||
*
|
||||
* This function is intended to be called when a user logs on or off.
|
||||
*/
|
||||
public function clean()
|
||||
{
|
||||
$this->sessionToken = '';
|
||||
$this->persistSessionToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a token for a form by hashing the given parameters
|
||||
* with the secret session token.
|
||||
*
|
||||
* Calling this function two times with the same parameters will create
|
||||
* the same valid token during one user session.
|
||||
*
|
||||
* @param string $formName
|
||||
* @param string $action
|
||||
* @param string $formInstanceName
|
||||
* @return string the 32-character hex ID of the generated token
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function generateToken($formName, $action = '', $formInstanceName = '')
|
||||
{
|
||||
if ($formName == '') {
|
||||
throw new \InvalidArgumentException('$formName must not be empty.', 1294586643);
|
||||
}
|
||||
$hashService = GeneralUtility::makeInstance(HashService::class);
|
||||
return $hashService->hmac($formName . $action . $formInstanceName . $this->getSessionToken(), self::class, HashAlgo::SHA3_256);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the token $tokenId is valid in the form $formName with
|
||||
* $formInstanceName.
|
||||
*
|
||||
* @param string $tokenId
|
||||
* @param string $formName
|
||||
* @param string $action
|
||||
* @param string $formInstanceName
|
||||
* @return bool
|
||||
*/
|
||||
public function validateToken($tokenId, $formName, $action = '', $formInstanceName = '')
|
||||
{
|
||||
$hashService = GeneralUtility::makeInstance(HashService::class);
|
||||
$validTokenId = $hashService->hmac(((string)$formName . (string)$action) . (string)$formInstanceName . $this->getSessionToken(), self::class, HashAlgo::SHA3_256);
|
||||
if (hash_equals($validTokenId, (string)$tokenId)) {
|
||||
$isValid = true;
|
||||
} else {
|
||||
$isValid = false;
|
||||
}
|
||||
if (!$isValid) {
|
||||
$this->createValidationErrorMessage();
|
||||
}
|
||||
return $isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the random token which is used in the hash for the form tokens.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateSessionToken()
|
||||
{
|
||||
return GeneralUtility::makeInstance(Random::class)->generateRandomHexString(64);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or displays an error message telling the user that the submitted
|
||||
* form token is invalid.
|
||||
*/
|
||||
protected function createValidationErrorMessage()
|
||||
{
|
||||
if ($this->validationFailedCallback !== null) {
|
||||
$this->validationFailedCallback->__invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the session token.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function retrieveSessionToken();
|
||||
|
||||
/**
|
||||
* Saves the session token so that it can be used by a later incarnation
|
||||
* of this class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract public function persistSessionToken();
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\FormProtection;
|
||||
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Error\Exception;
|
||||
use TYPO3\CMS\Core\Registry;
|
||||
|
||||
/**
|
||||
* This class provides protection against cross-site request forgery (XSRF/CSRF)
|
||||
* for forms in the BE.
|
||||
*
|
||||
* How to use:
|
||||
*
|
||||
* For each form in the BE (or link that changes some data), create a token and
|
||||
* insert is as a hidden form element. The name of the form element does not
|
||||
* matter; you only need it to get the form token for verifying it.
|
||||
*
|
||||
* <pre>
|
||||
* $formToken = GeneralUtility::makeInstance(FormProtectionFactory::class)->createFromType('backend')
|
||||
* ->generateToken(
|
||||
* 'BE user setup', 'edit'
|
||||
* );
|
||||
* $this->content .= '<input type="hidden" name="formToken" value="' .
|
||||
* $formToken . '" />';
|
||||
* </pre>
|
||||
*
|
||||
* The three parameters $formName, $action and $formInstanceName can be
|
||||
* arbitrary strings, but they should make the form token as specific as
|
||||
* possible. For different forms (e.g. BE user setup and editing a tt_content
|
||||
* record) or different records (with different UIDs) from the same table,
|
||||
* those values should be different.
|
||||
*
|
||||
* For editing a tt_content record, the call could look like this:
|
||||
*
|
||||
* <pre>
|
||||
* $formToken = GeneralUtility::makeInstance(FormProtectionFactory::class)->createFromType('backend')
|
||||
* ->getFormProtection()->generateToken(
|
||||
* 'tt_content', 'edit', $uid
|
||||
* );
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* When processing the data that has been submitted by the form, you can check
|
||||
* that the form token is valid like this:
|
||||
*
|
||||
* <pre>
|
||||
* if ($dataHasBeenSubmitted && GeneralUtility::makeInstance(FormProtectionFactory::class)->createFromType('backend')
|
||||
* ->validateToken(
|
||||
* \TYPO3\CMS\Core\Utility\GeneralUtility::_POST('formToken'),
|
||||
* 'BE user setup', 'edit
|
||||
* )
|
||||
* ) {
|
||||
* processes the data
|
||||
* } else {
|
||||
* no need to do anything here as the BE form protection will create a
|
||||
* flash message for an invalid token
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
class BackendFormProtection extends AbstractFormProtection
|
||||
{
|
||||
/**
|
||||
* Keeps the instance of the user which existed during creation
|
||||
* of the object.
|
||||
*
|
||||
* @var BackendUserAuthentication
|
||||
*/
|
||||
protected $backendUser;
|
||||
|
||||
/**
|
||||
* Instance of the registry, which is used to permanently persist
|
||||
* the session token so that it can be restored during re-login.
|
||||
*
|
||||
* @var Registry
|
||||
*/
|
||||
protected $registry;
|
||||
|
||||
/**
|
||||
* Only allow construction if we have an authorized backend session
|
||||
*
|
||||
* @throws \TYPO3\CMS\Core\Error\Exception
|
||||
*/
|
||||
public function __construct(BackendUserAuthentication $backendUser, Registry $registry, ?\Closure $validationFailedCallback = null)
|
||||
{
|
||||
$this->backendUser = $backendUser;
|
||||
$this->registry = $registry;
|
||||
$this->validationFailedCallback = $validationFailedCallback;
|
||||
if (!$this->isAuthorizedBackendSession()) {
|
||||
throw new Exception('A back-end form protection may only be instantiated if there is an active back-end session.', 1285067843);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the saved session token or generates a new one.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function retrieveSessionToken()
|
||||
{
|
||||
$this->sessionToken = $this->backendUser->getSessionData('formProtectionSessionToken');
|
||||
if (empty($this->sessionToken)) {
|
||||
$this->sessionToken = $this->generateSessionToken();
|
||||
$this->persistSessionToken();
|
||||
}
|
||||
return $this->sessionToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the tokens so that they can be used by a later incarnation of this
|
||||
* class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function persistSessionToken()
|
||||
{
|
||||
$this->backendUser->setAndSaveSessionData('formProtectionSessionToken', $this->sessionToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the session token for the user from the registry
|
||||
* and returns it additionally.
|
||||
*
|
||||
* @internal
|
||||
* @return string
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function setSessionTokenFromRegistry()
|
||||
{
|
||||
$this->sessionToken = $this->registry->get('core', 'formProtectionSessionToken:' . $this->backendUser->user['uid']);
|
||||
if (empty($this->sessionToken)) {
|
||||
throw new \UnexpectedValueException('Failed to restore the session token from the registry.', 1301827270);
|
||||
}
|
||||
return $this->sessionToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the session token in the registry to have it
|
||||
* available during re-login of the user.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function storeSessionTokenInRegistry()
|
||||
{
|
||||
$this->registry->set('core', 'formProtectionSessionToken:' . $this->backendUser->user['uid'], $this->getSessionToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the session token for the user from the registry.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function removeSessionTokenFromRegistry()
|
||||
{
|
||||
$this->registry->remove('core', 'formProtectionSessionToken:' . $this->backendUser->user['uid']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a user is logged in and the session is active.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAuthorizedBackendSession()
|
||||
{
|
||||
return !empty($this->backendUser->user['uid']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\FormProtection;
|
||||
|
||||
/**
|
||||
* This class is a dummy implementation of the form protection,
|
||||
* which is used when no authentication is used.
|
||||
*/
|
||||
class DisabledFormProtection extends AbstractFormProtection
|
||||
{
|
||||
/**
|
||||
* Disable parent method
|
||||
*
|
||||
* @param string $formName
|
||||
* @param string $action
|
||||
* @param string $formInstanceName
|
||||
* @return string
|
||||
*/
|
||||
public function generateToken($formName, $action = '', $formInstanceName = '')
|
||||
{
|
||||
return 'dummyToken';
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable parent method.
|
||||
* Always return TRUE.
|
||||
*
|
||||
* @param string $tokenId
|
||||
* @param string $formName
|
||||
* @param string $action
|
||||
* @param string $formInstanceName
|
||||
* @return bool
|
||||
*/
|
||||
public function validateToken($tokenId, $formName, $action = '', $formInstanceName = '')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dummy implementation
|
||||
*/
|
||||
protected function retrieveSessionToken(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Dummy implementation
|
||||
*/
|
||||
public function persistSessionToken() {}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\FormProtection;
|
||||
|
||||
/**
|
||||
* Invalid token exception
|
||||
*/
|
||||
class Exception extends \UnexpectedValueException {}
|
||||
@@ -0,0 +1,215 @@
|
||||
<?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\FormProtection;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
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;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\Registry;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
|
||||
|
||||
/**
|
||||
* This class creates and manages instances of the various form protection classes.
|
||||
*
|
||||
* Previously this class provides only provided static methods and could not be instantiated.
|
||||
*
|
||||
* Since TYPO3 v12, this class can and should be used as a factory to be injected into other
|
||||
* controllers or middlewares, to handle FormProtections for HTTP Requests.
|
||||
*/
|
||||
readonly class FormProtectionFactory
|
||||
{
|
||||
public function __construct(
|
||||
protected FlashMessageService $flashMessageService,
|
||||
protected LanguageServiceFactory $languageServiceFactory,
|
||||
protected FrontendInterface $runtimeCache,
|
||||
protected ContainerInterface $container,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Method should be used whenever you do not have direct access to the request object.
|
||||
* It is however recommended to use createFromRequest() whenever you have a PSR-7
|
||||
* request object available.
|
||||
*/
|
||||
public function createForType(string $type): AbstractFormProtection
|
||||
{
|
||||
if (!in_array($type, ['installtool', 'frontend', 'backend', 'disabled'], true)) {
|
||||
$type = 'disabled';
|
||||
}
|
||||
$identifier = $this->getIdentifierForType($type);
|
||||
if ($this->runtimeCache->has($identifier)) {
|
||||
return $this->runtimeCache->get($identifier);
|
||||
}
|
||||
$classNameAndConstructorArguments = $this->getClassNameAndConstructorArguments($type, $GLOBALS['TYPO3_REQUEST'] ?? null);
|
||||
$this->runtimeCache->set($identifier, $this->createInstance(...$classNameAndConstructorArguments));
|
||||
return $this->runtimeCache->get($identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the right FormProtection implementation based on the request.
|
||||
*/
|
||||
public function createFromRequest(ServerRequestInterface $request): AbstractFormProtection
|
||||
{
|
||||
$type = $this->determineTypeFromRequest($request);
|
||||
$identifier = $this->getIdentifierForType($type);
|
||||
if ($this->runtimeCache->has($identifier)) {
|
||||
return $this->runtimeCache->get($identifier);
|
||||
}
|
||||
$classNameAndConstructorArguments = $this->getClassNameAndConstructorArguments($type, $request);
|
||||
$this->runtimeCache->set($identifier, $this->createInstance(...$classNameAndConstructorArguments));
|
||||
return $this->runtimeCache->get($identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the type of FormProtection which should be instantiated, based on the request.
|
||||
*/
|
||||
protected function determineTypeFromRequest(ServerRequestInterface $request): string
|
||||
{
|
||||
if ($this->isInstallToolSession($request)) {
|
||||
return 'installtool';
|
||||
}
|
||||
if ($this->isFrontendSession($request)) {
|
||||
return 'frontend';
|
||||
}
|
||||
if ($this->isBackendSession()) {
|
||||
return 'backend';
|
||||
}
|
||||
return 'disabled';
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the equivalent to getClassNameAndConstructorArgumentsByType() but non-static.
|
||||
* It also does not handle "default" or class names, but is based on types previously resolved by
|
||||
* the request. See determineTypeFromRequest()
|
||||
*
|
||||
* @param string $type Valid types: installtool, frontend, backend.
|
||||
* @return array Array of arguments
|
||||
*/
|
||||
protected function getClassNameAndConstructorArguments(string $type, ?ServerRequestInterface $request): array
|
||||
{
|
||||
if ($type === 'installtool') {
|
||||
return [
|
||||
InstallToolFormProtection::class,
|
||||
];
|
||||
}
|
||||
if ($type === 'frontend') {
|
||||
$user = $request?->getAttribute('frontend.user');
|
||||
if ($user && isset($user->user['uid'])) {
|
||||
return [
|
||||
FrontendFormProtection::class,
|
||||
$user,
|
||||
];
|
||||
}
|
||||
}
|
||||
if ($type === 'backend') {
|
||||
$user = $GLOBALS['BE_USER'] ?? null;
|
||||
$isAjaxCall = (bool)($request ? $request->getAttribute('route')?->getOption('ajax') : false);
|
||||
if ($user && isset($user->user['uid'])) {
|
||||
return [
|
||||
BackendFormProtection::class,
|
||||
$user,
|
||||
$this->container->get(Registry::class),
|
||||
$this->getMessageClosure(
|
||||
$this->languageServiceFactory->createFromUserPreferences($user),
|
||||
$this->flashMessageService->getMessageQueueByIdentifier(),
|
||||
$isAjaxCall
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
// failed to use preferred type, disable form protection
|
||||
return [
|
||||
DisabledFormProtection::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Conveniant method to create a deterministic cache identifier.
|
||||
*/
|
||||
protected function getIdentifierForType(string $type): string
|
||||
{
|
||||
return 'formprotection-instance-' . hash('xxh3', $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we are in the install tool
|
||||
*/
|
||||
protected function isInstallToolSession(ServerRequestInterface $request): bool
|
||||
{
|
||||
return (bool)((int)$request->getAttribute('applicationType') & SystemEnvironmentBuilder::REQUESTTYPE_INSTALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a user is logged in and the session is active.
|
||||
*/
|
||||
protected function isBackendSession(): bool
|
||||
{
|
||||
$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 function isFrontendSession(ServerRequestInterface $request): bool
|
||||
{
|
||||
$user = $request->getAttribute('frontend.user');
|
||||
return $user instanceof FrontendUserAuthentication && isset($user->user['uid']);
|
||||
}
|
||||
|
||||
protected function getMessageClosure(LanguageService $languageService, FlashMessageQueue $messageQueue, bool $isAjaxCall): \Closure
|
||||
{
|
||||
return static function () use ($languageService, $messageQueue, $isAjaxCall) {
|
||||
$flashMessage = new FlashMessage(
|
||||
$languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.formProtection.tokenInvalid'),
|
||||
'',
|
||||
ContextualFeedbackSeverity::ERROR,
|
||||
!$isAjaxCall
|
||||
);
|
||||
$messageQueue->enqueue($flashMessage);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for the requested class $className
|
||||
* and stores it internally.
|
||||
*
|
||||
* @param class-string $className
|
||||
* @param array<int,mixed> $constructorArguments
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
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);
|
||||
}
|
||||
$instance = GeneralUtility::makeInstance($className, ...$constructorArguments);
|
||||
if (!$instance instanceof AbstractFormProtection) {
|
||||
throw new \InvalidArgumentException('$className must be a subclass of ' . AbstractFormProtection::class . ', but actually was "' . $className . '".', 1285353026);
|
||||
}
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\FormProtection;
|
||||
|
||||
use TYPO3\CMS\Core\Error\Exception;
|
||||
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
|
||||
|
||||
/**
|
||||
* This class provides protection against cross-site request forgery (XSRF/CSRF)
|
||||
* for actions in the frontend that change data.
|
||||
*
|
||||
* How to use:
|
||||
*
|
||||
* For each form (or link that changes some data), create a token and
|
||||
* insert is as a hidden form element or use it as GET argument. The name of the form element does not
|
||||
* matter; you only need it to get the form token for verifying it.
|
||||
*
|
||||
* <pre>
|
||||
* $formToken = GeneralUtility::makeInstance(FormProtectionFactory::class)->createFromType('frontend')
|
||||
* ->generateToken(
|
||||
* 'User setup', 'edit'
|
||||
* );
|
||||
* $this->content .= '<input type="hidden" name="formToken" value="' .
|
||||
* $formToken . '" />';
|
||||
* </pre>
|
||||
*
|
||||
* The three parameters $formName, $action and $formInstanceName can be
|
||||
* arbitrary strings, but they should make the form token as specific as
|
||||
* possible. For different forms (e.g. User setup and editing a news
|
||||
* record) or different records (with different UIDs) from the same table,
|
||||
* those values should be different.
|
||||
*
|
||||
* For editing a news record, the call could look like this:
|
||||
*
|
||||
* <pre>
|
||||
* $formToken = GeneralUtility::makeInstance(FormProtectionFactory::class)->createFromType('frontend')
|
||||
* ->getFormProtection()->generateToken(
|
||||
* 'news', 'edit', $uid
|
||||
* );
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* When processing the data that has been submitted by the form, you can check
|
||||
* that the form token is valid like this:
|
||||
*
|
||||
* <pre>
|
||||
* if ($dataHasBeenSubmitted && GeneralUtility::makeInstance(FormProtectionFactory::class)->createFromType('frontend')
|
||||
* ->validateToken(
|
||||
* \TYPO3\CMS\Core\Utility\GeneralUtility::_POST('formToken'),
|
||||
* 'User setup', 'edit
|
||||
* )
|
||||
* ) {
|
||||
* Processes the data.
|
||||
* } else {
|
||||
* Create a flash message for the invalid token or just discard this request.
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
class FrontendFormProtection extends AbstractFormProtection
|
||||
{
|
||||
/**
|
||||
* Keeps the instance of the user which existed during creation
|
||||
* of the object.
|
||||
*
|
||||
* @var FrontendUserAuthentication
|
||||
*/
|
||||
protected $frontendUser;
|
||||
|
||||
/**
|
||||
* Only allow construction if we have an authorized frontend session
|
||||
*
|
||||
* @throws \TYPO3\CMS\Core\Error\Exception
|
||||
*/
|
||||
public function __construct(FrontendUserAuthentication $frontendUser, ?\Closure $validationFailedCallback = null)
|
||||
{
|
||||
$this->frontendUser = $frontendUser;
|
||||
$this->validationFailedCallback = $validationFailedCallback;
|
||||
if (!$this->isAuthorizedFrontendSession()) {
|
||||
throw new Exception('A front-end form protection may only be instantiated if there is an active front-end session.', 1460975777);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the saved session token or generates a new one.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function retrieveSessionToken()
|
||||
{
|
||||
$this->sessionToken = $this->frontendUser->getSessionData('formProtectionSessionToken');
|
||||
if (empty($this->sessionToken)) {
|
||||
$this->sessionToken = $this->generateSessionToken();
|
||||
$this->persistSessionToken();
|
||||
}
|
||||
return $this->sessionToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the tokens so that they can be used by a later incarnation of this
|
||||
* class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function persistSessionToken()
|
||||
{
|
||||
$this->frontendUser->setAndSaveSessionData('formProtectionSessionToken', $this->sessionToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a user is logged in and the session is active.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAuthorizedFrontendSession()
|
||||
{
|
||||
return !empty($this->frontendUser->user['uid']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\FormProtection;
|
||||
|
||||
/**
|
||||
* This class provides protection against cross-site request forgery (XSRF/CSRF)
|
||||
* in the install tool.
|
||||
*
|
||||
*
|
||||
* How to use this in the install tool:
|
||||
*
|
||||
* For each form in the install tool (or link that changes some data), create a
|
||||
* token and insert is as a hidden form element. The name of the form element
|
||||
* does not matter; you only need it to get the form token for verifying it.
|
||||
*
|
||||
* <pre>
|
||||
* $formToken = $this->formProtection->generateToken(
|
||||
* 'installToolPassword', 'change'
|
||||
* );
|
||||
* then puts the generated form token in a hidden field in the template
|
||||
* </pre>
|
||||
*
|
||||
* The three parameters $formName, $action and $formInstanceName can be
|
||||
* arbitrary strings, but they should make the form token as specific as
|
||||
* possible. For different forms (e.g. the password change and editing a the
|
||||
* configuration), those values should be different.
|
||||
*
|
||||
* When processing the data that has been submitted by the form, you can check
|
||||
* that the form token is valid like this:
|
||||
*
|
||||
* <pre>
|
||||
* if ($dataHasBeenSubmitted && $this->formProtection()->validateToken(
|
||||
* $_POST['formToken'],
|
||||
* 'installToolPassword',
|
||||
* 'change'
|
||||
* ) {
|
||||
* processes the data
|
||||
* } else {
|
||||
* no need to do anything here as the install tool form protection will
|
||||
* create an error message for an invalid token
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
/**
|
||||
* Install Tool form protection
|
||||
*/
|
||||
class InstallToolFormProtection extends AbstractFormProtection
|
||||
{
|
||||
/**
|
||||
* Retrieves or generates the session token.
|
||||
*/
|
||||
protected function retrieveSessionToken(): string
|
||||
{
|
||||
if (isset($_SESSION['installToolFormToken']) && !empty($_SESSION['installToolFormToken'])) {
|
||||
$this->sessionToken = $_SESSION['installToolFormToken'];
|
||||
} else {
|
||||
$this->sessionToken = $this->generateSessionToken();
|
||||
$this->persistSessionToken();
|
||||
}
|
||||
return $this->sessionToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the tokens so that they can be used by a later incarnation of this
|
||||
* class.
|
||||
*/
|
||||
public function persistSessionToken()
|
||||
{
|
||||
$_SESSION['installToolFormToken'] = $this->sessionToken;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user