[BUGFIX] Always use backend form protection in backend URI builder
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / FormProtection / FormProtectionFactory.php
1 <?php
2 namespace TYPO3\CMS\Core\FormProtection;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
18 use TYPO3\CMS\Core\Messaging\FlashMessage;
19 use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
20 use TYPO3\CMS\Core\Messaging\FlashMessageService;
21 use TYPO3\CMS\Core\Registry;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23 use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
24 use TYPO3\CMS\Lang\LanguageService;
25
26 /**
27 * This class creates and manages instances of the various form protection
28 * classes.
29 *
30 * This class provides only static methods. It can not be instantiated.
31 *
32 * Usage for the back-end form protection:
33 *
34 * <pre>
35 * $formProtection = \TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get();
36 * </pre>
37 *
38 * Usage for the install tool form protection:
39 *
40 * <pre>
41 * $formProtection = \TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get();
42 * </pre>
43 */
44 class FormProtectionFactory
45 {
46 /**
47 * created instances of form protections using the type as array key
48 *
49 * @var array<AbstracFormtProtection>
50 */
51 protected static $instances = array();
52
53 /**
54 * Private constructor to prevent instantiation.
55 */
56 private function __construct()
57 {
58 }
59
60 /**
61 * Gets a form protection instance for the requested type or class.
62 *
63 * If there already is an existing instance of the requested $classNameOrType, the
64 * existing instance will be returned. If no $classNameOrType is provided, the factory
65 * detects the scope and returns the appropriate form protection object.
66 *
67 * @param string $classNameOrType Name of a form protection class, or one
68 * of the pre-defined form protection types:
69 * frontend, backend, installtool
70 * @return \TYPO3\CMS\Core\FormProtection\AbstractFormProtection the requested instance
71 */
72 public static function get($classNameOrType = 'default')
73 {
74 if (isset(self::$instances[$classNameOrType])) {
75 return self::$instances[$classNameOrType];
76 }
77 if ($classNameOrType === 'default' || $classNameOrType === 'installtool' || $classNameOrType === 'frontend' || $classNameOrType === 'backend') {
78 $classNameAndConstructorArguments = self::getClassNameAndConstructorArgumentsByType($classNameOrType);
79 } else {
80 $classNameAndConstructorArguments = func_get_args();
81 }
82 self::$instances[$classNameOrType] = self::createInstance($classNameAndConstructorArguments);
83 return self::$instances[$classNameOrType];
84 }
85
86 /**
87 * Returns the class name and parameters depending on the given type.
88 * If the type cannot be used currently, protection is disabled.
89 *
90 * @param string $type Valid types: default, installtool, frontend, backend. "default" makes an autodection on the current state
91 * @return array Array of arguments
92 */
93 protected static function getClassNameAndConstructorArgumentsByType($type)
94 {
95 if (self::isInstallToolSession() && ($type === 'default' || $type === 'installtool')) {
96 $classNameAndConstructorArguments = [
97 InstallToolFormProtection::class
98 ];
99 } elseif (self::isFrontendSession() && ($type === 'default' || $type === 'frontend')) {
100 $classNameAndConstructorArguments = [
101 FrontendFormProtection::class,
102 $GLOBALS['TSFE']->fe_user
103 ];
104 } elseif (self::isBackendSession() && ($type === 'default' || $type === 'backend')) {
105 $classNameAndConstructorArguments = [
106 BackendFormProtection::class,
107 $GLOBALS['BE_USER'],
108 GeneralUtility::makeInstance(Registry::class),
109 self::getMessageClosure(
110 $GLOBALS['LANG'],
111 GeneralUtility::makeInstance(FlashMessageService::class)->getMessageQueueByIdentifier(),
112 (bool)(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX)
113 )
114 ];
115 } else {
116 // failed to use preferred type, disable form protection
117 $classNameAndConstructorArguments = [
118 DisabledFormProtection::class
119 ];
120 }
121 return $classNameAndConstructorArguments;
122 }
123
124 /**
125 * Check if we are in the install tool
126 *
127 * @return bool
128 */
129 protected static function isInstallToolSession()
130 {
131 return (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_INSTALL);
132 }
133
134 /**
135 * Checks if a user is logged in and the session is active.
136 *
137 * @return bool
138 */
139 protected static function isBackendSession()
140 {
141 return isset($GLOBALS['BE_USER']) && $GLOBALS['BE_USER'] instanceof BackendUserAuthentication && isset($GLOBALS['BE_USER']->user['uid']);
142 }
143
144 /**
145 * Checks if a frontend user is logged in and the session is active.
146 *
147 * @return bool
148 */
149 protected static function isFrontendSession()
150 {
151 return TYPO3_MODE === 'FE' && is_object($GLOBALS['TSFE']) && $GLOBALS['TSFE']->fe_user instanceof FrontendUserAuthentication && isset($GLOBALS['TSFE']->fe_user->user['uid']);
152 }
153
154 /**
155 * @param LanguageService $languageService
156 * @param FlashMessageQueue $messageQueue
157 * @param bool $isAjaxCall
158 * @internal Only public to be used in tests
159 * @return \Closure
160 */
161 public static function getMessageClosure(LanguageService $languageService, FlashMessageQueue $messageQueue, $isAjaxCall)
162 {
163 return function () use ($languageService, $messageQueue, $isAjaxCall) {
164 /** @var FlashMessage $flashMessage */
165 $flashMessage = GeneralUtility::makeInstance(
166 FlashMessage::class,
167 $languageService->sL('LLL:EXT:lang/locallang_core.xlf:error.formProtection.tokenInvalid'),
168 '',
169 FlashMessage::ERROR,
170 !$isAjaxCall
171 );
172 $messageQueue->enqueue($flashMessage);
173 };
174 }
175
176 /**
177 * Creates an instance for the requested class $className
178 * and stores it internally.
179 *
180 * @param array $classNameAndConstructorArguments
181 * @throws \InvalidArgumentException
182 * @return AbstractFormProtection
183 */
184 protected static function createInstance(array $classNameAndConstructorArguments)
185 {
186 $className = $classNameAndConstructorArguments[0];
187 if (!class_exists($className)) {
188 throw new \InvalidArgumentException('$className must be the name of an existing class, but ' . 'actually was "' . $className . '".', 1285352962);
189 }
190 $instance = call_user_func_array([GeneralUtility::class, 'makeInstance'], $classNameAndConstructorArguments);
191 if (!$instance instanceof AbstractFormProtection) {
192 throw new \InvalidArgumentException('$className must be a subclass of ' . AbstractFormProtection::class . ', but actually was "' . $className . '".', 1285353026);
193 }
194 return $instance;
195 }
196
197 /**
198 * Sets the instance that will be returned by get() for a specific class
199 * name.
200 *
201 * Note: This function is intended for testing purposes only.
202 *
203 * @access private
204 * @param string $classNameOrType
205 * @param AbstractFormProtection $instance
206 * @return void
207 */
208 public static function set($classNameOrType, AbstractFormProtection $instance)
209 {
210 self::$instances[$classNameOrType] = $instance;
211 }
212
213 /**
214 * Purges all existing instances.
215 *
216 * This function is particularly useful when cleaning up in unit testing.
217 *
218 * @return void
219 */
220 public static function purgeInstances()
221 {
222 foreach (self::$instances as $key => $instance) {
223 unset(self::$instances[$key]);
224 }
225 }
226 }