7edb5e6756ccfbfe8f22422e18787a163ee99776
[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\Configuration\ExtensionConfiguration;
18 use TYPO3\CMS\Core\Messaging\FlashMessage;
19 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21
22 /**
23 * class providing configuration checks for saltedpasswords.
24 */
25 class ExtensionManagerConfigurationUtility
26 {
27 /**
28 * @var int
29 */
30 protected $errorType = FlashMessage::OK;
31
32 /**
33 * @var string
34 */
35 protected $header;
36
37 /**
38 * @var string
39 */
40 protected $preText;
41
42 /**
43 * @var array
44 */
45 protected $problems = [];
46
47 /**
48 * @var array
49 */
50 protected $extConf = [];
51
52 /**
53 * Set the error level if no higher level
54 * is set already
55 *
56 * @param string $level One out of error, ok, warning, info
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 private function init()
148 {
149 $requestSetup = $this->processPostData((array)$_REQUEST['data']);
150 $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('saltedpasswords');
151 $this->extConf['BE'] = array_merge((array)$extConf['BE'], (array)$requestSetup['BE']);
152 $this->extConf['FE'] = array_merge((array)$extConf['FE'], (array)$requestSetup['FE']);
153 $this->getLanguageService()->includeLLFile('EXT:saltedpasswords/Resources/Private/Language/locallang.xlf');
154 }
155
156 /**
157 * Checks the backend configuration and shows a message if necessary.
158 * The method returns an array or the HTML code depends on
159 * $params['propertyName'] is set or not.
160 *
161 * @param array $params Field information to be rendered
162 * @return array|string array with errorType and HTML or only the HTML as string
163 */
164 public function checkConfigurationBackend(array $params)
165 {
166 $this->init();
167 $extConf = $this->extConf['BE'];
168 // The backend is called over SSL
169 $isBackendCalledOverSsl = (bool)$GLOBALS['TYPO3_CONF_VARS']['BE']['lockSSL'];
170 $rsaAuthLoaded = ExtensionManagementUtility::isLoaded('rsaauth');
171 // SSL configured?
172 $lang = $this->getLanguageService();
173 if ($isBackendCalledOverSsl) {
174 $this->setErrorLevel('ok');
175 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.backendSsl');
176 } elseif ($rsaAuthLoaded) {
177 $loginSecurityLevel = trim($GLOBALS['TYPO3_CONF_VARS']['BE']['loginSecurityLevel']) ?: 'normal';
178 if ($loginSecurityLevel === 'rsa') {
179 if ($this->isRsaAuthBackendAvailable()) {
180 $this->setErrorLevel('ok');
181 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.backendRsa');
182 } else {
183 // This means that login would fail because rsaauth is not working properly
184 $this->setErrorLevel('error');
185 $problems[] = '<strong>' .
186 $lang->getLL('ext.saltedpasswords.configuration.message.openSslMissing') .
187 '<a href="http://php.net/manual/en/openssl.installation.php" target="_blank">PHP.net</a></strong>.';
188 }
189 } else {
190 // This means that rsaauth is enabled but not used
191 $this->setErrorLevel('warning');
192 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.backendSecurityLevelNotRsa');
193 }
194 } else {
195 // This means that we don't use any encryption method
196 $this->setErrorLevel('warning');
197 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.rsaInstructionsIntro') . '<br />
198 <ul>
199 <li>' . $lang->getLL('ext.saltedpasswords.configuration.message.rsaInstructionsFirstItem') . '</li>
200
201 <li>' . nl2br($lang->getLL('ext.saltedpasswords.configuration.message.rsaInstructionsSecondItem')) .
202 '</li>
203 </ul>
204 <br />
205 ' . $lang->getLL('ext.saltedpasswords.configuration.message.rsaInstructionsFootnote');
206 }
207 // Only saltedpasswords as authsservice
208 if ($extConf['onlyAuthService']) {
209 // Warn user that the combination with "forceSalted" may lock him
210 // out from Backend
211 if ($extConf['forceSalted']) {
212 $this->setErrorLevel('warning');
213 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.warningForceSalted') . '<br />
214 <strong><i>' . $lang->getLL('ext.saltedpasswords.configuration.label.warning') . '</i></strong> ' .
215 $lang->getLL('ext.saltedpasswords.configuration.message.warningForceSaltedNoteForBackend');
216 } else {
217 // Inform the user that things like openid won't work anymore
218 $this->setErrorLevel('info');
219 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.infoOnlyBackendAuthService');
220 }
221 }
222 // forceSalted is set
223 if ($extConf['forceSalted'] && !$extConf['onlyAuthService']) {
224 $this->setErrorLevel('info');
225 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.infoForceSalted') .
226 ' <br /> ' . $lang->getLL('ext.saltedpasswords.configuration.message.infoForceSaltedNote');
227 }
228 // updatePasswd wont work with "forceSalted"
229 if ($extConf['updatePasswd'] && $extConf['forceSalted']) {
230 $this->setErrorLevel('error');
231 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.errorForceSaltedAndUpdatePassword') .
232 '<br /> ' .
233 $lang->getLL('ext.saltedpasswords.configuration.message.errorForceSaltedAndUpdatePasswordReason');
234 }
235 // Check if the configured hash-method is available on system
236 $instance = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance(null, 'BE');
237 if ($instance === null || !$instance->isAvailable()) {
238 $this->setErrorLevel('error');
239 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.errorHashMethodNotAvailable');
240 }
241 $this->problems = $problems;
242 $result = $this->renderMessage();
243 if (!empty($params['propertyName'])) {
244 return $result['html'];
245 }
246 return $result;
247 }
248
249 /**
250 * Checks if rsaauth is able to obtain a backend
251 *
252 * @return bool
253 */
254 protected function isRsaAuthBackendAvailable()
255 {
256 // Try to instantiate an RSAauth backend. If this does not work,
257 // it means that OpenSSL is not usable
258 /** @var \TYPO3\CMS\Rsaauth\Backend\BackendFactory $rsaauthBackendFactory */
259 $rsaauthBackendFactory = GeneralUtility::makeInstance(\TYPO3\CMS\Rsaauth\Backend\BackendFactory::class);
260 $backend = $rsaauthBackendFactory->getBackend();
261 return $backend !== null;
262 }
263
264 /**
265 * Checks the frontend configuration and shows a message if necessary.
266 * The method returns an array or the HTML code depends on
267 * $params['propertyName'] is set or not.
268 *
269 * @param array $params Field information to be rendered
270 * @return array|string array with errorType and HTML or only the HTML as string
271 */
272 public function checkConfigurationFrontend(array $params)
273 {
274 $this->init();
275 $extConf = $this->extConf['FE'];
276 $problems = [];
277 $lang = $this->getLanguageService();
278 $loginSecurityLevel = trim($GLOBALS['TYPO3_CONF_VARS']['FE']['loginSecurityLevel']) ?: 'normal';
279 if ($loginSecurityLevel !== 'normal' && $loginSecurityLevel !== 'rsa') {
280 $this->setErrorLevel('info');
281 $problems[] = '<strong>' . $lang->getLL('ext.saltedpasswords.configuration.label.important') .
282 '</strong><br /> ' .
283 $lang->getLL('ext.saltedpasswords.configuration.message.infoLoginSecurityLevelDifferent') .
284 '<br />
285 <ul>
286 <li>' .
287 $lang->getLL('ext.saltedpasswords.configuration.message.infoLoginSecurityLevelDifferentFirstItem') .
288 '</li>
289
290 <li>' .
291 $lang->getLL('ext.saltedpasswords.configuration.message.infoLoginSecurityLevelDifferentSecondItem') .
292 '</li>
293 </ul>
294 <br />
295 ' . $lang->getLL('ext.saltedpasswords.configuration.message.infoLoginSecurityLevelDifferentNote');
296 } elseif ($loginSecurityLevel === 'rsa') {
297 if (ExtensionManagementUtility::isLoaded('rsaauth')) {
298 if ($this->isRsaAuthBackendAvailable()) {
299 $this->setErrorLevel('ok');
300 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.okFeRsaauthLoaded');
301 } else {
302 // This means that login would fail because rsaauth is not working properly
303 $this->setErrorLevel('error');
304 $problems[] = '<strong>' . $lang->getLL('ext.saltedpasswords.configuration.message.openSslMissing') .
305 ' <a href="http://php.net/manual/en/openssl.installation.php" target="_blank">PHP.net</a></strong>.';
306 }
307 } else {
308 // Rsaauth is not installed but configured to be used
309 $this->setErrorLevel('warning');
310 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.warningRsaauthNotInstalledButConfigured');
311 }
312 }
313 // Only saltedpasswords as authsservice
314 if ($extConf['onlyAuthService']) {
315 // Warn user that the combination with "forceSalted" may lock
316 // him out from frontend
317 if ($extConf['forceSalted']) {
318 $this->setErrorLevel('warning');
319 $problems[] = nl2br($lang->getLL('ext.saltedpasswords.configuration.message.infoForceSalted')) .
320 '<strong><i>' . $lang->getLL('ext.saltedpasswords.configuration.label.important') .
321 '</i></strong> ' . $lang->getLL('ext.saltedpasswords.configuration.message.warningForceSaltedNoteForFrontend');
322 } else {
323 // Inform the user that things like openid won't work anymore
324 $this->setErrorLevel('info');
325 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.infoOnlyFrontendAuthService');
326 }
327 }
328 // forceSalted is set
329 if ($extConf['forceSalted'] && !$extConf['onlyAuthService']) {
330 $this->setErrorLevel('warning');
331 $problems[] = nl2br($lang->getLL('ext.saltedpasswords.configuration.message.infoForceSalted')) .
332 '<strong><i>' . $lang->getLL('ext.saltedpasswords.configuration.label.important') .
333 '</i></strong> ' . $lang->getLL('ext.saltedpasswords.configuration.message.warningForceSaltedNote2');
334 }
335 // updatePasswd wont work with "forceSalted"
336 if ($extConf['updatePasswd'] && $extConf['forceSalted']) {
337 $this->setErrorLevel('error');
338 $problems[] = nl2br($lang->getLL('ext.saltedpasswords.configuration.message.errorForceSaltedAndUpdatePassword'));
339 }
340 $this->problems = $problems;
341 $result = $this->renderMessage();
342 if (!empty($params['propertyName'])) {
343 return $result['html'];
344 }
345 return $result;
346 }
347
348 /**
349 * Renders a selector element that allows to select the hash method to be used.
350 *
351 * @param array $params Field information to be rendered
352 * @param string $disposal The configuration disposal ('FE' or 'BE')
353 * @return string The HTML selector
354 */
355 protected function buildHashMethodSelector(array $params, $disposal)
356 {
357 $this->init();
358 $propertyName = $params['propertyName'];
359 $unknownVariablePleaseRenameMe = '\'' . substr(md5($propertyName), 0, 10) . '\'';
360 $pField = '';
361 $registeredMethods = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getRegisteredSaltedHashingMethods();
362 foreach ($registeredMethods as $class => $reference) {
363 $classInstance = GeneralUtility::makeInstance($reference);
364 if ($classInstance instanceof \TYPO3\CMS\Saltedpasswords\Salt\SaltInterface && $classInstance->isAvailable()) {
365 $sel = $this->extConf[$disposal]['saltedPWHashingMethod'] == $class ? ' selected="selected" ' : '';
366 $label = 'ext.saltedpasswords.title.' . strtolower(end(explode('\\', $class)));
367 $pField .= '<option value="' . htmlspecialchars($class) . '"' . $sel . '>' . $GLOBALS['LANG']->getLL($label) . '</option>';
368 }
369 }
370 $pField = '<select class="form-control" id="' . $propertyName . '" name="' . $params['fieldName'] .
371 '" onChange="uFormUrl(' . $unknownVariablePleaseRenameMe . ')">' . $pField . '</select>';
372 return $pField;
373 }
374
375 /**
376 * Renders a selector element that allows to select the hash method to be
377 * used (frontend disposal).
378 *
379 * @param array $params Field information to be rendered
380 * @return string The HTML selector
381 */
382 public function buildHashMethodSelectorFE(array $params)
383 {
384 return $this->buildHashMethodSelector($params, 'FE');
385 }
386
387 /**
388 * Renders a selector element that allows to select the hash method to
389 * be used (backend disposal)
390 *
391 * @param array $params Field information to be rendered
392 * @return string The HTML selector
393 */
394 public function buildHashMethodSelectorBE(array $params)
395 {
396 return $this->buildHashMethodSelector($params, 'BE');
397 }
398
399 /**
400 * Processes the information submitted by the user using a POST request and
401 * transforms it to a TypoScript node notation.
402 *
403 * @param array $postArray Incoming POST information
404 * @return array Processed and transformed POST information
405 */
406 protected function processPostData(array $postArray = [])
407 {
408 foreach ($postArray as $key => $value) {
409 // @todo Explain
410 $parts = explode('.', $key, 2);
411 if (count($parts) == 2) {
412 // @todo Explain
413 $value = $this->processPostData([$parts[1] => $value]);
414 $postArray[$parts[0] . '.'] = array_merge((array)$postArray[$parts[0] . '.'], $value);
415 } else {
416 // @todo Explain
417 $postArray[$parts[0]] = $value;
418 }
419 }
420 return $postArray;
421 }
422
423 /**
424 * @return \TYPO3\CMS\Core\Localization\LanguageService
425 */
426 protected function getLanguageService()
427 {
428 return $GLOBALS['LANG'];
429 }
430 }