685c3736513560f517223b3c5a2f877c8825af82
[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->setErrorLevel('ok');
196 }
197 // Only saltedpasswords as authsservice
198 if ($extConf['onlyAuthService']) {
199 // Warn user that the combination with "forceSalted" may lock him
200 // out from Backend
201 if ($extConf['forceSalted']) {
202 $this->setErrorLevel('warning');
203 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.warningForceSalted') . '<br />
204 <strong><i>' . $lang->getLL('ext.saltedpasswords.configuration.label.warning') . '</i></strong> ' .
205 $lang->getLL('ext.saltedpasswords.configuration.message.warningForceSaltedNoteForBackend');
206 } else {
207 // Inform the user that things like openid won't work anymore
208 $this->setErrorLevel('info');
209 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.infoOnlyBackendAuthService');
210 }
211 }
212 // forceSalted is set
213 if ($extConf['forceSalted'] && !$extConf['onlyAuthService']) {
214 $this->setErrorLevel('info');
215 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.infoForceSalted') .
216 ' <br /> ' . $lang->getLL('ext.saltedpasswords.configuration.message.infoForceSaltedNote');
217 }
218 // updatePasswd wont work with "forceSalted"
219 if ($extConf['updatePasswd'] && $extConf['forceSalted']) {
220 $this->setErrorLevel('error');
221 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.errorForceSaltedAndUpdatePassword') .
222 '<br /> ' .
223 $lang->getLL('ext.saltedpasswords.configuration.message.errorForceSaltedAndUpdatePasswordReason');
224 }
225 // Check if the configured hash-method is available on system
226 $instance = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance(null, 'BE');
227 if ($instance === null || !$instance->isAvailable()) {
228 $this->setErrorLevel('error');
229 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.errorHashMethodNotAvailable');
230 }
231 $this->problems = $problems;
232 $result = $this->renderMessage();
233 if (!empty($params['propertyName'])) {
234 return $result['html'];
235 }
236 return $result;
237 }
238
239 /**
240 * Checks if rsaauth is able to obtain a backend
241 *
242 * @return bool
243 */
244 protected function isRsaAuthBackendAvailable()
245 {
246 // Try to instantiate an RSAauth backend. If this does not work,
247 // it means that OpenSSL is not usable
248 /** @var \TYPO3\CMS\Rsaauth\Backend\BackendFactory $rsaauthBackendFactory */
249 $rsaauthBackendFactory = GeneralUtility::makeInstance(\TYPO3\CMS\Rsaauth\Backend\BackendFactory::class);
250 $backend = $rsaauthBackendFactory->getBackend();
251 return $backend !== null;
252 }
253
254 /**
255 * Checks the frontend configuration and shows a message if necessary.
256 * The method returns an array or the HTML code depends on
257 * $params['propertyName'] is set or not.
258 *
259 * @param array $params Field information to be rendered
260 * @return array|string array with errorType and HTML or only the HTML as string
261 */
262 public function checkConfigurationFrontend(array $params)
263 {
264 $this->init();
265 $extConf = $this->extConf['FE'];
266 $problems = [];
267 $lang = $this->getLanguageService();
268 $loginSecurityLevel = trim($GLOBALS['TYPO3_CONF_VARS']['FE']['loginSecurityLevel']) ?: 'normal';
269 if ($loginSecurityLevel !== 'normal' && $loginSecurityLevel !== 'rsa') {
270 $this->setErrorLevel('info');
271 $problems[] = '<strong>' . $lang->getLL('ext.saltedpasswords.configuration.label.important') .
272 '</strong><br /> ' .
273 $lang->getLL('ext.saltedpasswords.configuration.message.infoLoginSecurityLevelDifferent') .
274 '<br />
275 <ul>
276 <li>' .
277 $lang->getLL('ext.saltedpasswords.configuration.message.infoLoginSecurityLevelDifferentFirstItem') .
278 '</li>
279
280 <li>' .
281 $lang->getLL('ext.saltedpasswords.configuration.message.infoLoginSecurityLevelDifferentSecondItem') .
282 '</li>
283 </ul>
284 <br />
285 ' . $lang->getLL('ext.saltedpasswords.configuration.message.infoLoginSecurityLevelDifferentNote');
286 } elseif ($loginSecurityLevel === 'rsa') {
287 if (ExtensionManagementUtility::isLoaded('rsaauth')) {
288 if ($this->isRsaAuthBackendAvailable()) {
289 $this->setErrorLevel('ok');
290 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.okFeRsaauthLoaded');
291 } else {
292 // This means that login would fail because rsaauth is not working properly
293 $this->setErrorLevel('error');
294 $problems[] = '<strong>' . $lang->getLL('ext.saltedpasswords.configuration.message.openSslMissing') .
295 ' <a href="http://php.net/manual/en/openssl.installation.php" target="_blank">PHP.net</a></strong>.';
296 }
297 } else {
298 // Rsaauth is not installed but configured to be used
299 $this->setErrorLevel('warning');
300 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.warningRsaauthNotInstalledButConfigured');
301 }
302 }
303 // Only saltedpasswords as authsservice
304 if ($extConf['onlyAuthService']) {
305 // Warn user that the combination with "forceSalted" may lock
306 // him out from frontend
307 if ($extConf['forceSalted']) {
308 $this->setErrorLevel('warning');
309 $problems[] = nl2br($lang->getLL('ext.saltedpasswords.configuration.message.infoForceSalted')) .
310 '<strong><i>' . $lang->getLL('ext.saltedpasswords.configuration.label.important') .
311 '</i></strong> ' . $lang->getLL('ext.saltedpasswords.configuration.message.warningForceSaltedNoteForFrontend');
312 } else {
313 // Inform the user that things like openid won't work anymore
314 $this->setErrorLevel('info');
315 $problems[] = $lang->getLL('ext.saltedpasswords.configuration.message.infoOnlyFrontendAuthService');
316 }
317 }
318 // forceSalted is set
319 if ($extConf['forceSalted'] && !$extConf['onlyAuthService']) {
320 $this->setErrorLevel('warning');
321 $problems[] = nl2br($lang->getLL('ext.saltedpasswords.configuration.message.infoForceSalted')) .
322 '<strong><i>' . $lang->getLL('ext.saltedpasswords.configuration.label.important') .
323 '</i></strong> ' . $lang->getLL('ext.saltedpasswords.configuration.message.warningForceSaltedNote2');
324 }
325 // updatePasswd wont work with "forceSalted"
326 if ($extConf['updatePasswd'] && $extConf['forceSalted']) {
327 $this->setErrorLevel('error');
328 $problems[] = nl2br($lang->getLL('ext.saltedpasswords.configuration.message.errorForceSaltedAndUpdatePassword'));
329 }
330 $this->problems = $problems;
331 $result = $this->renderMessage();
332 if (!empty($params['propertyName'])) {
333 return $result['html'];
334 }
335 return $result;
336 }
337
338 /**
339 * Renders a selector element that allows to select the hash method to be used.
340 *
341 * @param array $params Field information to be rendered
342 * @param string $disposal The configuration disposal ('FE' or 'BE')
343 * @return string The HTML selector
344 */
345 protected function buildHashMethodSelector(array $params, $disposal)
346 {
347 $this->init();
348 $propertyName = $params['propertyName'];
349 $unknownVariablePleaseRenameMe = '\'' . substr(md5($propertyName), 0, 10) . '\'';
350 $pField = '';
351 $registeredMethods = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getRegisteredSaltedHashingMethods();
352 foreach ($registeredMethods as $class => $reference) {
353 $classInstance = GeneralUtility::makeInstance($reference);
354 if ($classInstance instanceof \TYPO3\CMS\Saltedpasswords\Salt\SaltInterface && $classInstance->isAvailable()) {
355 $sel = $this->extConf[$disposal]['saltedPWHashingMethod'] == $class ? ' selected="selected" ' : '';
356 $label = 'ext.saltedpasswords.title.' . strtolower(end(explode('\\', $class)));
357 $pField .= '<option value="' . htmlspecialchars($class) . '"' . $sel . '>' . $GLOBALS['LANG']->getLL($label) . '</option>';
358 }
359 }
360 $pField = '<select class="form-control" id="' . $propertyName . '" name="' . $params['fieldName'] .
361 '" onChange="uFormUrl(' . $unknownVariablePleaseRenameMe . ')">' . $pField . '</select>';
362 return $pField;
363 }
364
365 /**
366 * Renders a selector element that allows to select the hash method to be
367 * used (frontend disposal).
368 *
369 * @param array $params Field information to be rendered
370 * @return string The HTML selector
371 */
372 public function buildHashMethodSelectorFE(array $params)
373 {
374 return $this->buildHashMethodSelector($params, 'FE');
375 }
376
377 /**
378 * Renders a selector element that allows to select the hash method to
379 * be used (backend disposal)
380 *
381 * @param array $params Field information to be rendered
382 * @return string The HTML selector
383 */
384 public function buildHashMethodSelectorBE(array $params)
385 {
386 return $this->buildHashMethodSelector($params, 'BE');
387 }
388
389 /**
390 * Processes the information submitted by the user using a POST request and
391 * transforms it to a TypoScript node notation.
392 *
393 * @param array $postArray Incoming POST information
394 * @return array Processed and transformed POST information
395 */
396 protected function processPostData(array $postArray = [])
397 {
398 foreach ($postArray as $key => $value) {
399 // @todo Explain
400 $parts = explode('.', $key, 2);
401 if (count($parts) == 2) {
402 // @todo Explain
403 $value = $this->processPostData([$parts[1] => $value]);
404 $postArray[$parts[0] . '.'] = array_merge((array)$postArray[$parts[0] . '.'], $value);
405 } else {
406 // @todo Explain
407 $postArray[$parts[0]] = $value;
408 }
409 }
410 return $postArray;
411 }
412
413 /**
414 * @return \TYPO3\CMS\Core\Localization\LanguageService
415 */
416 protected function getLanguageService()
417 {
418 return $GLOBALS['LANG'];
419 }
420 }