[TASK] Add more fixers for php-cs-fixer
[Packages/TYPO3.CMS.git] / typo3 / sysext / saltedpasswords / Classes / Utility / ExtensionManagerConfigurationUtility.php
1 <?php
2 namespace TYPO3\CMS\Saltedpasswords\Utility;
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\Messaging\FlashMessage;
18 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20
21 /**
22 * class providing configuration checks for saltedpasswords.
23 */
24 class ExtensionManagerConfigurationUtility
25 {
26 /**
27 * @var int
28 */
29 protected $errorType = FlashMessage::OK;
30
31 /**
32 * @var string
33 */
34 protected $header;
35
36 /**
37 * @var string
38 */
39 protected $preText;
40
41 /**
42 * @var array
43 */
44 protected $problems = [];
45
46 /**
47 * @var array
48 */
49 protected $extConf = [];
50
51 /**
52 * Set the error level if no higher level
53 * is set already
54 *
55 * @param string $level One out of error, ok, warning, info
56 * @return void
57 */
58 protected function setErrorLevel($level)
59 {
60 $lang = $this->getLanguageService();
61 switch ($level) {
62 case 'error':
63 $this->errorType = FlashMessage::ERROR;
64 $this->header = $lang->getLL('ext.saltedpasswords.configuration.header.errorsFound');
65 $this->preText = $lang->getLL('ext.saltedpasswords.configuration.message.errorsFound') . '<br />';
66 break;
67 case 'warning':
68 if ($this->errorType < FlashMessage::ERROR) {
69 $this->errorType = FlashMessage::WARNING;
70 $this->header = $lang->getLL('ext.saltedpasswords.configuration.header.warningsFound');
71 $this->preText = $lang->getLL('ext.saltedpasswords.configuration.message.warningsFound') . '<br />';
72 }
73 break;
74 case 'info':
75 if ($this->errorType < FlashMessage::WARNING) {
76 $this->errorType = FlashMessage::INFO;
77 $this->header = $lang->getLL('ext.saltedpasswords.configuration.header.additionalInformation');
78 $this->preText = '<br />';
79 }
80 break;
81 case 'ok':
82 // @todo Remove INFO condition as it has lower importance
83 if ($this->errorType < FlashMessage::WARNING && $this->errorType != FlashMessage::INFO) {
84 $this->errorType = FlashMessage::OK;
85 $this->header = $lang->getLL('ext.saltedpasswords.configuration.header.noErrorsFound');
86 $this->preText = $lang->getLL('ext.saltedpasswords.configuration.message.noErrorsFound') . '<br />';
87 }
88 break;
89 default:
90 }
91 }
92
93 /**
94 * Renders the messages if problems have been found.
95 *
96 * @return array an array with errorType and html code
97 */
98 protected function renderMessage()
99 {
100 $message = '';
101 // If there are problems, render them into an unordered list
102 if (!empty($this->problems)) {
103 $message = '<ul><li>###PROBLEMS###</li></ul>';
104 $message = str_replace('###PROBLEMS###', implode('<br />&nbsp;</li><li>', $this->problems), $message);
105 if ($this->errorType > FlashMessage::OK) {
106 $message .= '<br />' .
107 $this->getLanguageService()->getLL('ext.saltedpasswords.configuration.message.securityWarning');
108 }
109 }
110 if (empty($message)) {
111 $this->setErrorLevel('ok');
112 }
113 $message = $this->preText . $message;
114
115 $class = 'default';
116 switch ($this->errorType) {
117 case FlashMessage::NOTICE:
118 $class = 'notice';
119 break;
120 case FlashMessage::INFO:
121 $class = 'info';
122 break;
123 case FlashMessage::OK:
124 $class = 'success';
125 break;
126 case FlashMessage::WARNING:
127 $class = 'warning';
128 break;
129 case FlashMessage::ERROR:
130 $class = 'danger';
131 break;
132 default:
133 }
134 $html = '<div class="panel panel-' . $class . '">' .
135 '<div class="panel-heading">' . $this->header . '</div>' .
136 '<div class="panel-body">' . $message . '</div>' .
137 '</div>';
138 return [
139 'errorType' => $this->errorType,
140 'html' => $html
141 ];
142 }
143
144 /**
145 * Initializes this object.
146 *
147 * @return void
148 */
149 private function init()
150 {
151 $requestSetup = $this->processPostData((array) $_REQUEST['data']);
152 $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['saltedpasswords'], ['allowed_classes' => false]);
153 $this->extConf['BE'] = array_merge((array)$extConf['BE.'], (array)$requestSetup['BE.']);
154 $this->extConf['FE'] = array_merge((array)$extConf['FE.'], (array)$requestSetup['FE.']);
155 $this->getLanguageService()->includeLLFile('EXT:saltedpasswords/Resources/Private/Language/locallang.xlf');
156 }
157
158 /**
159 * Checks the backend configuration and shows a message if necessary.
160 * The method returns an array or the HTML code depends on
161 * $params['propertyName'] is set or not.
162 *
163 * @param array $params Field information to be rendered
164 * @param \TYPO3\CMS\Core\TypoScript\ConfigurationForm $pObj The calling parent object.
165 * @return array|string array with errorType and HTML or only the HTML as string
166 */
167 public function checkConfigurationBackend(array $params, $pObj)
168 {
169 $this->init();
170 $extConf = $this->extConf['BE'];
171 // The backend is called over SSL
172 $isBackendCalledOverSsl = (bool)$GLOBALS['TYPO3_CONF_VARS']['BE']['lockSSL'];
173 $rsaAuthLoaded = ExtensionManagementUtility::isLoaded('rsaauth');
174 // SSL configured?
175 $lang = $this->getLanguageService();
176 if ($isBackendCalledOverSsl) {
177 $this->setErrorLevel('ok');
178 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.backendSsl');
179 } elseif ($rsaAuthLoaded) {
180 $loginSecurityLevel = trim($GLOBALS['TYPO3_CONF_VARS']['BE']['loginSecurityLevel']) ?: 'normal';
181 if ($loginSecurityLevel === 'rsa') {
182 if ($this->isRsaAuthBackendAvailable()) {
183 $this->setErrorLevel('ok');
184 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.backendRsa');
185 } else {
186 // This means that login would fail because rsaauth is not working properly
187 $this->setErrorLevel('error');
188 $problems[] = '<strong>' .
189 $lang->getLL('ext.saltedpasswords.configuration.message.openSslMissing') .
190 '<a href="http://php.net/manual/en/openssl.installation.php" target="_blank">PHP.net</a></strong>.';
191 }
192 } else {
193 // This means that rsaauth is enabled but not used
194 $this->setErrorLevel('warning');
195 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.backendSecurityLevelNotRsa');
196 }
197 } else {
198 // This means that we don't use any encryption method
199 $this->setErrorLevel('warning');
200 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.rsaInstructionsIntro') . '<br />
201 <ul>
202 <li>' . $lang->getLL('ext.saltedpasswords.configuration.message.rsaInstructionsFirstItem') . '</li>
203
204 <li>' . nl2br($lang->getLL('ext.saltedpasswords.configuration.message.rsaInstructionsSecondItem')) .
205 '</li>
206 </ul>
207 <br />
208 ' . $lang->getLL('ext.saltedpasswords.configuration.message.rsaInstructionsFootnote');
209 }
210 // Only saltedpasswords as authsservice
211 if ($extConf['onlyAuthService']) {
212 // Warn user that the combination with "forceSalted" may lock him
213 // out from Backend
214 if ($extConf['forceSalted']) {
215 $this->setErrorLevel('warning');
216 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.warningForceSalted') . '<br />
217 <strong><i>' . $lang->getLL('ext.saltedpasswords.configuration.label.warning') . '</i></strong> ' .
218 $lang->getLL('ext.saltedpasswords.configuration.message.warningForceSaltedNoteForBackend');
219 } else {
220 // Inform the user that things like openid won't work anymore
221 $this->setErrorLevel('info');
222 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.infoOnlyBackendAuthService');
223 }
224 }
225 // forceSalted is set
226 if ($extConf['forceSalted'] && !$extConf['onlyAuthService']) {
227 $this->setErrorLevel('info');
228 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.infoForceSalted') .
229 ' <br /> ' . $lang->getLL('ext.saltedpasswords.configuration.message.infoForceSaltedNote');
230 }
231 // updatePasswd wont work with "forceSalted"
232 if ($extConf['updatePasswd'] && $extConf['forceSalted']) {
233 $this->setErrorLevel('error');
234 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.errorForceSaltedAndUpdatePassword') .
235 '<br /> ' .
236 $lang->getLL('ext.saltedpasswords.configuration.message.errorForceSaltedAndUpdatePasswordReason');
237 }
238 // Check if the configured hash-method is available on system
239 $instance = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance(null, 'BE');
240 if ($instance === null || !$instance->isAvailable()) {
241 $this->setErrorLevel('error');
242 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.errorHashMethodNotAvailable');
243 }
244 $this->problems = $problems;
245 $result = $this->renderMessage();
246 if (!empty($params['propertyName'])) {
247 return $result['html'];
248 }
249 return $result;
250 }
251
252 /**
253 * Checks if rsaauth is able to obtain a backend
254 *
255 * @return bool
256 */
257 protected function isRsaAuthBackendAvailable()
258 {
259 // Try to instantiate an RSAauth backend. If this does not work,
260 // it means that OpenSSL is not usable
261 /** @var \TYPO3\CMS\Rsaauth\Backend\BackendFactory $rsaauthBackendFactory */
262 $rsaauthBackendFactory = GeneralUtility::makeInstance(\TYPO3\CMS\Rsaauth\Backend\BackendFactory::class);
263 $backend = $rsaauthBackendFactory->getBackend();
264 return $backend !== null;
265 }
266
267 /**
268 * Checks the frontend configuration and shows a message if necessary.
269 * The method returns an array or the HTML code depends on
270 * $params['propertyName'] is set or not.
271 *
272 * @param array $params Field information to be rendered
273 * @param \TYPO3\CMS\Core\TypoScript\ConfigurationForm $pObj The calling parent object.
274 * @return array|string array with errorType and HTML or only the HTML as string
275 */
276 public function checkConfigurationFrontend(array $params, $pObj)
277 {
278 $this->init();
279 $extConf = $this->extConf['FE'];
280 $problems = [];
281 $lang = $this->getLanguageService();
282 if ($extConf['enabled']) {
283 $loginSecurityLevel = trim($GLOBALS['TYPO3_CONF_VARS']['FE']['loginSecurityLevel']) ?: 'normal';
284 if ($loginSecurityLevel !== 'normal' && $loginSecurityLevel !== 'rsa') {
285 $this->setErrorLevel('info');
286 $problems[] = '<strong>' . $lang->getLL('ext.saltedpasswords.configuration.label.important') .
287 '</strong><br /> ' .
288 $lang->getLL('ext.saltedpasswords.configuration.message.infoLoginSecurityLevelDifferent') .
289 '<br />
290 <ul>
291 <li>' .
292 $lang->getLL('ext.saltedpasswords.configuration.message.infoLoginSecurityLevelDifferentFirstItem') .
293 '</li>
294
295 <li>' .
296 $lang->getLL('ext.saltedpasswords.configuration.message.infoLoginSecurityLevelDifferentSecondItem') .
297 '</li>
298 </ul>
299 <br />
300 ' . $lang->getLL('ext.saltedpasswords.configuration.message.infoLoginSecurityLevelDifferentNote');
301 } elseif ($loginSecurityLevel === 'rsa') {
302 if (ExtensionManagementUtility::isLoaded('rsaauth')) {
303 if ($this->isRsaAuthBackendAvailable()) {
304 $this->setErrorLevel('ok');
305 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.okFeRsaauthLoaded');
306 } else {
307 // This means that login would fail because rsaauth is not working properly
308 $this->setErrorLevel('error');
309 $problems[] = '<strong>' . $lang->getLL('ext.saltedpasswords.configuration.message.openSslMissing') .
310 ' <a href="http://php.net/manual/en/openssl.installation.php" target="_blank">PHP.net</a></strong>.';
311 }
312 } else {
313 // Rsaauth is not installed but configured to be used
314 $this->setErrorLevel('warning');
315 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.warningRsaauthNotInstalledButConfigured');
316 }
317 }
318 // Only saltedpasswords as authsservice
319 if ($extConf['onlyAuthService']) {
320 // Warn user that the combination with "forceSalted" may lock
321 // him out from frontend
322 if ($extConf['forceSalted']) {
323 $this->setErrorLevel('warning');
324 $problems[] = nl2br($lang->getLL('ext.saltedpasswords.configuration.message.infoForceSalted')) .
325 '<strong><i>' . $lang->getLL('ext.saltedpasswords.configuration.label.important') .
326 '</i></strong> ' . $lang->getLL('ext.saltedpasswords.configuration.message.warningForceSaltedNoteForFrontend');
327 } else {
328 // Inform the user that things like openid won't work anymore
329 $this->setErrorLevel('info');
330 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.infoOnlyFrontendAuthService');
331 }
332 }
333 // forceSalted is set
334 if ($extConf['forceSalted'] && !$extConf['onlyAuthService']) {
335 $this->setErrorLevel('warning');
336 $problems[] = nl2br($lang->getLL('ext.saltedpasswords.configuration.message.infoForceSalted')) .
337 '<strong><i>' . $lang->getLL('ext.saltedpasswords.configuration.label.important') .
338 '</i></strong> ' . $lang->getLL('ext.saltedpasswords.configuration.message.warningForceSaltedNote2');
339 }
340 // updatePasswd wont work with "forceSalted"
341 if ($extConf['updatePasswd'] && $extConf['forceSalted']) {
342 $this->setErrorLevel('error');
343 $problems[] = nl2br($lang->getLL('ext.saltedpasswords.configuration.message.errorForceSaltedAndUpdatePassword'));
344 }
345 } else {
346 // Not enabled warning
347 $this->setErrorLevel('info');
348 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.infoSaltedpasswordsFrontendDisabled');
349 }
350 $this->problems = $problems;
351 $result = $this->renderMessage();
352 if (!empty($params['propertyName'])) {
353 return $result['html'];
354 }
355 return $result;
356 }
357
358 /**
359 * Renders a selector element that allows to select the hash method to be used.
360 *
361 * @param array $params Field information to be rendered
362 * @param \TYPO3\CMS\Core\TypoScript\ConfigurationForm $pObj The calling parent object.
363 * @param string $disposal The configuration disposal ('FE' or 'BE')
364 * @return string The HTML selector
365 */
366 protected function buildHashMethodSelector(array $params, $pObj, $disposal)
367 {
368 $this->init();
369 $propertyName = $params['propertyName'];
370 $unknownVariablePleaseRenameMe = '\'' . substr(md5($propertyName), 0, 10) . '\'';
371 $pField = '';
372 $registeredMethods = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getRegisteredSaltedHashingMethods();
373 foreach ($registeredMethods as $class => $reference) {
374 $classInstance = GeneralUtility::getUserObj($reference);
375 if ($classInstance instanceof \TYPO3\CMS\Saltedpasswords\Salt\SaltInterface && $classInstance->isAvailable()) {
376 $sel = $this->extConf[$disposal]['saltedPWHashingMethod'] == $class ? ' selected="selected" ' : '';
377 $label = 'ext.saltedpasswords.title.' . strtolower(end(explode('\\', $class)));
378 $pField .= '<option value="' . htmlspecialchars($class) . '"' . $sel . '>' . $GLOBALS['LANG']->getLL($label) . '</option>';
379 }
380 }
381 $pField = '<select id="' . $propertyName . '" name="' . $params['fieldName'] .
382 '" onChange="uFormUrl(' . $unknownVariablePleaseRenameMe . ')">' . $pField . '</select>';
383 return $pField;
384 }
385
386 /**
387 * Renders a selector element that allows to select the hash method to be
388 * used (frontend disposal).
389 *
390 * @param array $params Field information to be rendered
391 * @param \TYPO3\CMS\Core\TypoScript\ConfigurationForm $pObj The calling parent object.
392 * @return string The HTML selector
393 */
394 public function buildHashMethodSelectorFE(array $params, $pObj)
395 {
396 return $this->buildHashMethodSelector($params, $pObj, 'FE');
397 }
398
399 /**
400 * Renders a selector element that allows to select the hash method to
401 * be used (backend disposal)
402 *
403 * @param array $params Field information to be rendered
404 * @param \TYPO3\CMS\Core\TypoScript\ConfigurationForm $pObj The calling parent object.
405 * @return string The HTML selector
406 */
407 public function buildHashMethodSelectorBE(array $params, $pObj)
408 {
409 return $this->buildHashMethodSelector($params, $pObj, 'BE');
410 }
411
412 /**
413 * Processes the information submitted by the user using a POST request and
414 * transforms it to a TypoScript node notation.
415 *
416 * @param array $postArray Incoming POST information
417 * @return array Processed and transformed POST information
418 */
419 protected function processPostData(array $postArray = [])
420 {
421 foreach ($postArray as $key => $value) {
422 // @todo Explain
423 $parts = explode('.', $key, 2);
424 if (count($parts) == 2) {
425 // @todo Explain
426 $value = $this->processPostData([$parts[1] => $value]);
427 $postArray[$parts[0] . '.'] = array_merge((array)$postArray[$parts[0] . '.'], $value);
428 } else {
429 // @todo Explain
430 $postArray[$parts[0]] = $value;
431 }
432 }
433 return $postArray;
434 }
435
436 /**
437 * @return \TYPO3\CMS\Lang\LanguageService
438 */
439 protected function getLanguageService()
440 {
441 return $GLOBALS['LANG'];
442 }
443 }