[BUGFIX] felogin: Invalid action url in changePassword form
[Packages/TYPO3.CMS.git] / typo3 / sysext / felogin / Classes / Controller / FrontendLoginController.php
1 <?php
2 namespace TYPO3\CMS\Felogin\Controller;
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\Utility\GeneralUtility;
18 use TYPO3\CMS\Core\Utility\StringUtility;
19
20 /**
21 * Plugin 'Website User Login' for the 'felogin' extension.
22 *
23 * @author Steffen Kamper <info@sk-typo3.de>
24 */
25 class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin {
26
27 /**
28 * Same as class name
29 *
30 * @todo Define visibility
31 */
32 public $prefixId = 'tx_felogin_pi1';
33
34 /**
35 * Path to this script relative to the extension dir.
36 *
37 * @todo Define visibility
38 */
39 public $scriptRelPath = 'pi1/class.tx_felogin_pi1.php';
40
41 /**
42 * The extension key.
43 *
44 * @todo Define visibility
45 */
46 public $extKey = 'felogin';
47
48 /**
49 * @var boolean
50 */
51 public $pi_checkCHash = FALSE;
52
53 /**
54 * @var boolean
55 */
56 public $pi_USER_INT_obj = TRUE;
57
58 /**
59 * Is user logged in?
60 *
61 * @var boolean
62 */
63 protected $userIsLoggedIn;
64
65 /**
66 * Holds the template for FE rendering
67 *
68 * @var string
69 */
70 protected $template;
71
72 /**
73 * Upload directory, used for flexform template files
74 *
75 * @var string
76 */
77 protected $uploadDir;
78
79 /**
80 * URL for the redirect
81 *
82 * @var string
83 */
84 protected $redirectUrl;
85
86 /**
87 * Flag for disable the redirect
88 *
89 * @var boolean
90 */
91 protected $noRedirect = FALSE;
92
93 /**
94 * Logintype (given as GPvar), possible: login, logout
95 *
96 * @var string
97 */
98 protected $logintype;
99
100 /**
101 * The main method of the plugin
102 *
103 * @param string $content The PlugIn content
104 * @param array $conf The PlugIn configuration
105 * @return string The content that is displayed on the website
106 */
107 public function main($content, $conf) {
108 // Loading TypoScript array into object variable:
109 $this->conf = $conf;
110 $this->uploadDir = 'uploads/tx_felogin/';
111 // Loading default pivars
112 $this->pi_setPiVarDefaults();
113 // Loading language-labels
114 $this->pi_loadLL();
115 // Init FlexForm configuration for plugin:
116 $this->pi_initPIflexForm();
117 $this->mergeflexFormValuesIntoConf();
118 // Get storage PIDs:
119 if ($this->conf['storagePid']) {
120 if ((int)$this->conf['recursive']) {
121 $this->spid = $this->pi_getPidList($this->conf['storagePid'], (int)$this->conf['recursive']);
122 } else {
123 $this->spid = $this->conf['storagePid'];
124 }
125 } else {
126 $pids = $GLOBALS['TSFE']->getStorageSiterootPids();
127 $this->spid = $pids['_STORAGE_PID'];
128 }
129 // GPvars:
130 $this->logintype = GeneralUtility::_GP('logintype');
131 $this->referer = $this->validateRedirectUrl(GeneralUtility::_GP('referer'));
132 $this->noRedirect = $this->piVars['noredirect'] || $this->conf['redirectDisable'];
133 // If config.typolinkLinkAccessRestrictedPages is set, the var is return_url
134 $returnUrl = GeneralUtility::_GP('return_url');
135 if ($returnUrl) {
136 $this->redirectUrl = $returnUrl;
137 } else {
138 $this->redirectUrl = GeneralUtility::_GP('redirect_url');
139 }
140 $this->redirectUrl = $this->validateRedirectUrl($this->redirectUrl);
141 // Get Template
142 $templateFile = $this->conf['templateFile'] ?: 'EXT:felogin/template.html';
143 $this->template = $this->cObj->fileResource($templateFile);
144 // Is user logged in?
145 $this->userIsLoggedIn = $GLOBALS['TSFE']->loginUser;
146 // Redirect
147 if ($this->conf['redirectMode'] && !$this->conf['redirectDisable'] && !$this->noRedirect) {
148 $redirectUrl = $this->processRedirect();
149 if (count($redirectUrl)) {
150 $this->redirectUrl = $this->conf['redirectFirstMethod'] ? array_shift($redirectUrl) : array_pop($redirectUrl);
151 } else {
152 $this->redirectUrl = '';
153 }
154 }
155 // What to display
156 $content = '';
157 if ($this->piVars['forgot'] && $this->conf['showForgotPasswordLink']) {
158 $content .= $this->showForgot();
159 } elseif ($this->piVars['forgothash']) {
160 $content .= $this->changePassword();
161 } else {
162 if ($this->userIsLoggedIn && !$this->logintype) {
163 $content .= $this->showLogout();
164 } else {
165 $content .= $this->showLogin();
166 }
167 }
168 // Process the redirect
169 if (($this->logintype === 'login' || $this->logintype === 'logout') && $this->redirectUrl && !$this->noRedirect) {
170 if (!$GLOBALS['TSFE']->fe_user->isCookieSet() && $this->userIsLoggedIn) {
171 $content .= $this->cObj->stdWrap($this->pi_getLL('cookie_warning', '', TRUE), $this->conf['cookieWarning_stdWrap.']);
172 } else {
173 // Add hook for extra processing before redirect
174 if (
175 isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['beforeRedirect']) &&
176 is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['beforeRedirect'])
177 ) {
178 $_params = array(
179 'loginType' => $this->logintype,
180 'redirectUrl' => &$this->redirectUrl
181 );
182 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['beforeRedirect'] as $_funcRef) {
183 if ($_funcRef) {
184 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
185 }
186 }
187 }
188 \TYPO3\CMS\Core\Utility\HttpUtility::redirect($this->redirectUrl);
189 }
190 }
191 // Adds hook for processing of extra item markers / special
192 if (
193 isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['postProcContent'])
194 && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['postProcContent'])
195 ) {
196 $_params = array(
197 'content' => $content
198 );
199 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['postProcContent'] as $_funcRef) {
200 $content = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
201 }
202 }
203 return $this->conf['wrapContentInBaseClass'] ? $this->pi_wrapInBaseClass($content) : $content;
204 }
205
206 /**
207 * Shows the forgot password form
208 *
209 * @return string Content
210 */
211 protected function showForgot() {
212 $subpart = $this->cObj->getSubpart($this->template, '###TEMPLATE_FORGOT###');
213 $subpartArray = ($linkpartArray = array());
214 $postData = GeneralUtility::_POST($this->prefixId);
215 if ($postData['forgot_email']) {
216 // Get hashes for compare
217 $postedHash = $postData['forgot_hash'];
218 $hashData = $GLOBALS['TSFE']->fe_user->getKey('ses', 'forgot_hash');
219 if ($postedHash === $hashData['forgot_hash']) {
220 $row = FALSE;
221 // Look for user record
222 $data = $GLOBALS['TYPO3_DB']->fullQuoteStr($this->piVars['forgot_email'], 'fe_users');
223 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
224 'uid, username, password, email',
225 'fe_users',
226 '(email=' . $data . ' OR username=' . $data . ') AND pid IN (' . $GLOBALS['TYPO3_DB']->cleanIntList($this->spid) . ') ' . $this->cObj->enableFields('fe_users')
227 );
228 if ($GLOBALS['TYPO3_DB']->sql_num_rows($res)) {
229 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
230 }
231 $error = NULL;
232 if ($row) {
233 // Generate an email with the hashed link
234 $error = $this->generateAndSendHash($row);
235 } elseif ($this->conf['exposeNonexistentUserInForgotPasswordDialog']) {
236 $error = $this->pi_getLL('ll_forgot_reset_message_error');
237 }
238 // Generate message
239 if ($error) {
240 $markerArray['###STATUS_MESSAGE###'] = $this->cObj->stdWrap($error, $this->conf['forgotErrorMessage_stdWrap.']);
241 } else {
242 $markerArray['###STATUS_MESSAGE###'] = $this->cObj->stdWrap(
243 $this->pi_getLL('ll_forgot_reset_message_emailSent', '', TRUE),
244 $this->conf['forgotResetMessageEmailSentMessage_stdWrap.']
245 );
246 }
247 $subpartArray['###FORGOT_FORM###'] = '';
248 } else {
249 // Wrong email
250 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('forgot_reset_message', $this->conf['forgotMessage_stdWrap.']);
251 $markerArray['###BACKLINK_LOGIN###'] = '';
252 }
253 } else {
254 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('forgot_reset_message', $this->conf['forgotMessage_stdWrap.']);
255 $markerArray['###BACKLINK_LOGIN###'] = '';
256 }
257 $markerArray['###BACKLINK_LOGIN###'] = $this->getPageLink($this->pi_getLL('ll_forgot_header_backToLogin', '', TRUE), array());
258 $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('forgot_header', $this->conf['forgotHeader_stdWrap.']);
259 $markerArray['###LEGEND###'] = $this->pi_getLL('legend', $this->pi_getLL('reset_password', '', TRUE), TRUE);
260 $markerArray['###ACTION_URI###'] = $this->getPageLink('', array($this->prefixId . '[forgot]' => 1), TRUE);
261 $markerArray['###EMAIL_LABEL###'] = $this->pi_getLL('your_email', '', TRUE);
262 $markerArray['###FORGOT_PASSWORD_ENTEREMAIL###'] = $this->pi_getLL('forgot_password_enterEmail', '', TRUE);
263 $markerArray['###FORGOT_EMAIL###'] = $this->prefixId . '[forgot_email]';
264 $markerArray['###SEND_PASSWORD###'] = $this->pi_getLL('reset_password', '', TRUE);
265 $markerArray['###DATA_LABEL###'] = $this->pi_getLL('ll_enter_your_data', '', TRUE);
266 $markerArray = array_merge($markerArray, $this->getUserFieldMarkers());
267 // Generate hash
268 $hash = md5($this->generatePassword(3));
269 $markerArray['###FORGOTHASH###'] = $hash;
270 // Set hash in feuser session
271 $GLOBALS['TSFE']->fe_user->setKey('ses', 'forgot_hash', array('forgot_hash' => $hash));
272 return $this->cObj->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray);
273 }
274
275 /**
276 * This function checks the hash from link and checks the validity. If it's valid it shows the form for
277 * changing the password and process the change of password after submit, if not valid it returns the error message
278 *
279 * @return string The content.
280 */
281 protected function changePassword() {
282 $subpartArray = ($linkpartArray = array());
283 $done = FALSE;
284 $minLength = (int)$this->conf['newPasswordMinLength'] ?: 6;
285 $subpart = $this->cObj->getSubpart($this->template, '###TEMPLATE_CHANGEPASSWORD###');
286 $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('change_password_header', $this->conf['changePasswordHeader_stdWrap.']);
287 $markerArray['###STATUS_MESSAGE###'] = sprintf($this->getDisplayText(
288 'change_password_message',
289 $this->conf['changePasswordMessage_stdWrap.']
290 ), $minLength);
291
292 $markerArray['###BACKLINK_LOGIN###'] = '';
293 $uid = $this->piVars['user'];
294 $piHash = $this->piVars['forgothash'];
295 $hash = explode('|', $piHash);
296 if ((int)$uid === 0) {
297 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText(
298 'change_password_notvalid_message',
299 $this->conf['changePasswordNotValidMessage_stdWrap.']
300 );
301 $subpartArray['###CHANGEPASSWORD_FORM###'] = '';
302 } else {
303 $user = $this->pi_getRecord('fe_users', (int)$uid);
304 $userHash = $user['felogin_forgotHash'];
305 $compareHash = explode('|', $userHash);
306 if (!$compareHash || !$compareHash[1] || $compareHash[0] < time() || $hash[0] != $compareHash[0] || md5($hash[1]) != $compareHash[1]) {
307 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText(
308 'change_password_notvalid_message',
309 $this->conf['changePasswordNotValidMessage_stdWrap.']
310 );
311 $subpartArray['###CHANGEPASSWORD_FORM###'] = '';
312 } else {
313 // All is fine, continue with new password
314 $postData = GeneralUtility::_POST($this->prefixId);
315 if (isset($postData['changepasswordsubmit'])) {
316 if (strlen($postData['password1']) < $minLength) {
317 $markerArray['###STATUS_MESSAGE###'] = sprintf($this->getDisplayText(
318 'change_password_tooshort_message',
319 $this->conf['changePasswordTooShortMessage_stdWrap.']),
320 $minLength
321 );
322 } elseif ($postData['password1'] != $postData['password2']) {
323 $markerArray['###STATUS_MESSAGE###'] = sprintf($this->getDisplayText(
324 'change_password_notequal_message',
325 $this->conf['changePasswordNotEqualMessage_stdWrap.']),
326 $minLength
327 );
328 } else {
329 $newPass = $postData['password1'];
330 if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['password_changed']) {
331 $_params = array(
332 'user' => $user,
333 'newPassword' => $newPass
334 );
335 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['password_changed'] as $_funcRef) {
336 if ($_funcRef) {
337 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
338 }
339 }
340 $newPass = $_params['newPassword'];
341 }
342 // Save new password and clear DB-hash
343 $res = $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
344 'fe_users',
345 'uid=' . $user['uid'],
346 array('password' => $newPass, 'felogin_forgotHash' => '', 'tstamp' => $GLOBALS['EXEC_TIME'])
347 );
348 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText(
349 'change_password_done_message',
350 $this->conf['changePasswordDoneMessage_stdWrap.']
351 );
352 $done = TRUE;
353 $subpartArray['###CHANGEPASSWORD_FORM###'] = '';
354 $markerArray['###BACKLINK_LOGIN###'] = $this->getPageLink(
355 $this->pi_getLL('ll_forgot_header_backToLogin', '', TRUE),
356 array($this->prefixId . '[redirectReferrer]' => 'off')
357 );
358 }
359 }
360 if (!$done) {
361 // Change password form
362 $markerArray['###ACTION_URI###'] = $this->getPageLink('', array(
363 $this->prefixId . '[user]' => $user['uid'],
364 $this->prefixId . '[forgothash]' => $piHash
365 ), TRUE);
366 $markerArray['###LEGEND###'] = $this->pi_getLL('change_password', '', TRUE);
367 $markerArray['###NEWPASSWORD1_LABEL###'] = $this->pi_getLL('newpassword_label1', '', TRUE);
368 $markerArray['###NEWPASSWORD2_LABEL###'] = $this->pi_getLL('newpassword_label2', '', TRUE);
369 $markerArray['###NEWPASSWORD1###'] = $this->prefixId . '[password1]';
370 $markerArray['###NEWPASSWORD2###'] = $this->prefixId . '[password2]';
371 $markerArray['###STORAGE_PID###'] = $this->spid;
372 $markerArray['###SEND_PASSWORD###'] = $this->pi_getLL('change_password', '', TRUE);
373 $markerArray['###FORGOTHASH###'] = $piHash;
374 }
375 }
376 }
377 return $this->cObj->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray);
378 }
379
380 /**
381 * Generates a hashed link and send it with email
382 *
383 * @param array $user Contains user data
384 * @return string Empty string with success, error message with no success
385 */
386 protected function generateAndSendHash($user) {
387 $hours = (int)$this->conf['forgotLinkHashValidTime'] > 0 ? (int)$this->conf['forgotLinkHashValidTime'] : 24;
388 $validEnd = time() + 3600 * $hours;
389 $validEndString = date($this->conf['dateFormat'], $validEnd);
390 $hash = md5(GeneralUtility::generateRandomBytes(64));
391 $randHash = $validEnd . '|' . $hash;
392 $randHashDB = $validEnd . '|' . md5($hash);
393 // Write hash to DB
394 $res = $GLOBALS['TYPO3_DB']->exec_UPDATEquery('fe_users', 'uid=' . $user['uid'], array('felogin_forgotHash' => $randHashDB));
395 // Send hashlink to user
396 $this->conf['linkPrefix'] = -1;
397 $isAbsRelPrefix = !empty($GLOBALS['TSFE']->absRefPrefix);
398 $isBaseURL = !empty($GLOBALS['TSFE']->baseUrl);
399 $isFeloginBaseURL = !empty($this->conf['feloginBaseURL']);
400 $link = $this->pi_getPageLink($GLOBALS['TSFE']->id, '', array(
401 rawurlencode($this->prefixId . '[user]') => $user['uid'],
402 rawurlencode($this->prefixId . '[forgothash]') => $randHash
403 ));
404 // Prefix link if necessary
405 if ($isFeloginBaseURL) {
406 // First priority, use specific base URL
407 // "absRefPrefix" must be removed first, otherwise URL will be prepended twice
408 if (!empty($GLOBALS['TSFE']->absRefPrefix)) {
409 $link = substr($link, strlen($GLOBALS['TSFE']->absRefPrefix));
410 }
411 $link = $this->conf['feloginBaseURL'] . $link;
412 } elseif ($isAbsRelPrefix) {
413 // Second priority
414 // absRefPrefix must not necessarily contain a hostname and URL scheme, so add it if needed
415 $link = GeneralUtility::locationHeaderUrl($link);
416 } elseif ($isBaseURL) {
417 // Third priority
418 // Add the global base URL to the link
419 $link = $GLOBALS['TSFE']->baseUrlWrap($link);
420 } else {
421 // No prefix is set, return the error
422 return $this->pi_getLL('ll_change_password_nolinkprefix_message');
423 }
424 $msg = sprintf($this->pi_getLL('ll_forgot_validate_reset_password'), $user['username'], $link, $validEndString);
425 // Add hook for extra processing of mail message
426 if (
427 isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['forgotPasswordMail'])
428 && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['forgotPasswordMail'])
429 ) {
430 $params = array(
431 'message' => &$msg,
432 'user' => &$user
433 );
434 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['forgotPasswordMail'] as $reference) {
435 if ($reference) {
436 GeneralUtility::callUserFunction($reference, $params, $this);
437 }
438 }
439 }
440 // no RDCT - Links for security reasons
441 $oldSetting = $GLOBALS['TSFE']->config['config']['notification_email_urlmode'];
442 $GLOBALS['TSFE']->config['config']['notification_email_urlmode'] = 0;
443 // Send the email
444 $this->cObj->sendNotifyEmail($msg, $user['email'], '', $this->conf['email_from'], $this->conf['email_fromName'], $this->conf['replyTo']);
445 // Restore settings
446 $GLOBALS['TSFE']->config['config']['notification_email_urlmode'] = $oldSetting;
447 return '';
448 }
449
450 /**
451 * Shows logout form
452 *
453 * @return string The content.
454 */
455 protected function showLogout() {
456 $subpart = $this->cObj->getSubpart($this->template, '###TEMPLATE_LOGOUT###');
457 $subpartArray = ($linkpartArray = array());
458 $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('status_header', $this->conf['logoutHeader_stdWrap.']);
459 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('status_message', $this->conf['logoutMessage_stdWrap.']);
460 $this->cObj->stdWrap($this->flexFormValue('message', 's_status'), $this->conf['logoutMessage_stdWrap.']);
461 $markerArray['###LEGEND###'] = $this->pi_getLL('logout', '', TRUE);
462 $markerArray['###ACTION_URI###'] = $this->getPageLink('', array(), TRUE);
463 $markerArray['###LOGOUT_LABEL###'] = $this->pi_getLL('logout', '', TRUE);
464 $markerArray['###NAME###'] = htmlspecialchars($GLOBALS['TSFE']->fe_user->user['name']);
465 $markerArray['###STORAGE_PID###'] = $this->spid;
466 $markerArray['###USERNAME###'] = htmlspecialchars($GLOBALS['TSFE']->fe_user->user['username']);
467 $markerArray['###USERNAME_LABEL###'] = $this->pi_getLL('username', '', TRUE);
468 $markerArray['###NOREDIRECT###'] = $this->noRedirect ? '1' : '0';
469 $markerArray['###PREFIXID###'] = $this->prefixId;
470 $markerArray = array_merge($markerArray, $this->getUserFieldMarkers());
471 if ($this->redirectUrl) {
472 // Use redirectUrl for action tag because of possible access restricted pages
473 $markerArray['###ACTION_URI###'] = htmlspecialchars($this->redirectUrl);
474 $this->redirectUrl = '';
475 }
476 return $this->cObj->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray);
477 }
478
479 /**
480 * Shows login form
481 *
482 * @return string Content
483 */
484 protected function showLogin() {
485 $subpart = $this->cObj->getSubpart($this->template, '###TEMPLATE_LOGIN###');
486 $subpartArray = ($linkpartArray = ($markerArray = array()));
487 $gpRedirectUrl = '';
488 $markerArray['###LEGEND###'] = $this->pi_getLL('oLabel_header_welcome', '', TRUE);
489 if ($this->logintype === 'login') {
490 if ($this->userIsLoggedIn) {
491 // login success
492 $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('success_header', $this->conf['successHeader_stdWrap.']);
493 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('success_message', $this->conf['successMessage_stdWrap.']);
494 $markerArray = array_merge($markerArray, $this->getUserFieldMarkers());
495 $subpartArray['###LOGIN_FORM###'] = '';
496 // Hook for general actions after after login has been confirmed (by Thomas Danzl <thomas@danzl.org>)
497 if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['login_confirmed']) {
498 $_params = array();
499 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['login_confirmed'] as $_funcRef) {
500 if ($_funcRef) {
501 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
502 }
503 }
504 }
505 // show logout form directly
506 if ($this->conf['showLogoutFormAfterLogin']) {
507 $this->redirectUrl = '';
508 return $this->showLogout();
509 }
510 } else {
511 // Hook for general actions on login error
512 if (
513 isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['login_error'])
514 && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['login_error'])
515 ) {
516 $params = array();
517 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['login_error'] as $funcRef) {
518 if ($funcRef) {
519 GeneralUtility::callUserFunction($funcRef, $params, $this);
520 }
521 }
522 }
523 // login error
524 $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('error_header', $this->conf['errorHeader_stdWrap.']);
525 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('error_message', $this->conf['errorMessage_stdWrap.']);
526 $gpRedirectUrl = GeneralUtility::_GP('redirect_url');
527 }
528 } else {
529 if ($this->logintype === 'logout') {
530 // login form after logout
531 $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('logout_header', $this->conf['logoutHeader_stdWrap.']);
532 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('logout_message', $this->conf['logoutMessage_stdWrap.']);
533 } else {
534 // login form
535 $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('welcome_header', $this->conf['welcomeHeader_stdWrap.']);
536 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('welcome_message', $this->conf['welcomeMessage_stdWrap.']);
537 }
538 }
539
540 // This hook allows to call User JS functions.
541 // The methods should also set the required JS functions to get included
542 $onSubmit = '';
543 $extraHidden = '';
544 $onSubmitAr = array();
545 $extraHiddenAr = array();
546 // Check for referer redirect method. if present, save referer in form field
547 if (GeneralUtility::inList($this->conf['redirectMode'], 'referer') || GeneralUtility::inList($this->conf['redirectMode'], 'refererDomains')) {
548 $referer = $this->referer ? $this->referer : GeneralUtility::getIndpEnv('HTTP_REFERER');
549 if ($referer) {
550 $extraHiddenAr[] = '<input type="hidden" name="referer" value="' . htmlspecialchars($referer) . '" />';
551 if ($this->piVars['redirectReferrer'] === 'off') {
552 $extraHiddenAr[] = '<input type="hidden" name="' . $this->prefixId . '[redirectReferrer]" value="off" />';
553 }
554 }
555 }
556 if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['loginFormOnSubmitFuncs'])) {
557 $_params = array();
558 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['loginFormOnSubmitFuncs'] as $funcRef) {
559 list($onSub, $hid) = GeneralUtility::callUserFunction($funcRef, $_params, $this);
560 $onSubmitAr[] = $onSub;
561 $extraHiddenAr[] = $hid;
562 }
563 }
564 if (count($onSubmitAr)) {
565 $onSubmit = implode('; ', $onSubmitAr) . '; return true;';
566 }
567 if (count($extraHiddenAr)) {
568 $extraHidden = implode(LF, $extraHiddenAr);
569 }
570 if (!$gpRedirectUrl && $this->redirectUrl) {
571 $gpRedirectUrl = $this->redirectUrl;
572 }
573 // Login form
574 $markerArray['###ACTION_URI###'] = $this->getPageLink('', array(), TRUE);
575 // Used by kb_md5fepw extension...
576 $markerArray['###EXTRA_HIDDEN###'] = $extraHidden;
577 $markerArray['###LEGEND###'] = $this->pi_getLL('login', '', TRUE);
578 $markerArray['###LOGIN_LABEL###'] = $this->pi_getLL('login', '', TRUE);
579 // Used by kb_md5fepw extension...
580 $markerArray['###ON_SUBMIT###'] = $onSubmit;
581 $markerArray['###PASSWORD_LABEL###'] = $this->pi_getLL('password', '', TRUE);
582 $markerArray['###STORAGE_PID###'] = $this->spid;
583 $markerArray['###USERNAME_LABEL###'] = $this->pi_getLL('username', '', TRUE);
584 $markerArray['###REDIRECT_URL###'] = htmlspecialchars($gpRedirectUrl);
585 $markerArray['###NOREDIRECT###'] = $this->noRedirect ? '1' : '0';
586 $markerArray['###PREFIXID###'] = $this->prefixId;
587 $markerArray = array_merge($markerArray, $this->getUserFieldMarkers());
588 if ($this->conf['showForgotPasswordLink']) {
589 $linkpartArray['###FORGOT_PASSWORD_LINK###'] = explode('|', $this->getPageLink('|', array($this->prefixId . '[forgot]' => 1)));
590 $markerArray['###FORGOT_PASSWORD###'] = $this->pi_getLL('ll_forgot_header', '', TRUE);
591 } else {
592 $subpartArray['###FORGOTP_VALID###'] = '';
593 }
594 // The permanent login checkbox should only be shown if permalogin is not deactivated (-1),
595 // not forced to be always active (2) and lifetime is greater than 0
596 if (
597 $this->conf['showPermaLogin']
598 && GeneralUtility::inList('0,1', $GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'])
599 && $GLOBALS['TYPO3_CONF_VARS']['FE']['lifetime'] > 0
600 ) {
601 $markerArray['###PERMALOGIN###'] = $this->pi_getLL('permalogin', '', TRUE);
602 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 1) {
603 $markerArray['###PERMALOGIN_HIDDENFIELD_ATTRIBUTES###'] = 'disabled="disabled"';
604 $markerArray['###PERMALOGIN_CHECKBOX_ATTRIBUTES###'] = 'checked="checked"';
605 } else {
606 $markerArray['###PERMALOGIN_HIDDENFIELD_ATTRIBUTES###'] = '';
607 $markerArray['###PERMALOGIN_CHECKBOX_ATTRIBUTES###'] = '';
608 }
609 } else {
610 $subpartArray['###PERMALOGIN_VALID###'] = '';
611 }
612 return $this->cObj->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray);
613 }
614
615 /**
616 * Process redirect methods. The function searches for a redirect url using all configured methods.
617 *
618 * @return array Redirect URLs
619 */
620 protected function processRedirect() {
621 $redirect_url = array();
622 if ($this->conf['redirectMode']) {
623 $redirectMethods = GeneralUtility::trimExplode(',', $this->conf['redirectMode'], TRUE);
624 foreach ($redirectMethods as $redirMethod) {
625 if ($GLOBALS['TSFE']->loginUser && $this->logintype === 'login') {
626 // Logintype is needed because the login-page wouldn't be accessible anymore after a login (would always redirect)
627 switch ($redirMethod) {
628 case 'groupLogin':
629 // taken from dkd_redirect_at_login written by Ingmar Schlecht; database-field changed
630 $groupData = $GLOBALS['TSFE']->fe_user->groupData;
631 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
632 'felogin_redirectPid',
633 $GLOBALS['TSFE']->fe_user->usergroup_table,
634 'felogin_redirectPid<>\'\' AND uid IN (' . implode(',', $groupData['uid']) . ')'
635 );
636 if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res)) {
637 // take the first group with a redirect page
638 $redirect_url[] = $this->pi_getPageLink($row[0]);
639 }
640 break;
641 case 'userLogin':
642 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
643 'felogin_redirectPid',
644 $GLOBALS['TSFE']->fe_user->user_table,
645 $GLOBALS['TSFE']->fe_user->userid_column . '=' . $GLOBALS['TSFE']->fe_user->user['uid'] . ' AND felogin_redirectPid<>\'\''
646 );
647 if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res)) {
648 $redirect_url[] = $this->pi_getPageLink($row[0]);
649 }
650 break;
651 case 'login':
652 if ($this->conf['redirectPageLogin']) {
653 $redirect_url[] = $this->pi_getPageLink((int)$this->conf['redirectPageLogin']);
654 }
655 break;
656 case 'getpost':
657 $redirect_url[] = $this->redirectUrl;
658 break;
659 case 'referer':
660 // Avoid redirect when logging in after changing password
661 if ($this->piVars['redirectReferrer'] !== 'off') {
662 // Avoid forced logout, when trying to login immediately after a logout
663 $redirect_url[] = preg_replace('/[&?]logintype=[a-z]+/', '', $this->referer);
664 }
665 break;
666 case 'refererDomains':
667 // Auto redirect.
668 // Feature to redirect to the page where the user came from (HTTP_REFERER).
669 // Allowed domains to redirect to, can be configured with plugin.tx_felogin_pi1.domains
670 // Thanks to plan2.net / Martin Kutschker for implementing this feature.
671 // also avoid redirect when logging in after changing password
672 if ($this->conf['domains'] && $this->piVars['redirectReferrer'] !== 'off') {
673 $url = $this->referer;
674 // Is referring url allowed to redirect?
675 $match = array();
676 if (preg_match('#^http://([[:alnum:]._-]+)/#', $url, $match)) {
677 $redirect_domain = $match[1];
678 $found = FALSE;
679 foreach (GeneralUtility::trimExplode(',', $this->conf['domains'], TRUE) as $d) {
680 if (preg_match('/(?:^|\\.)' . $d . '$/', $redirect_domain)) {
681 $found = TRUE;
682 break;
683 }
684 }
685 if (!$found) {
686 $url = '';
687 }
688 }
689 // Avoid forced logout, when trying to login immediately after a logout
690 if ($url) {
691 $redirect_url[] = preg_replace('/[&?]logintype=[a-z]+/', '', $url);
692 }
693 }
694 break;
695 }
696 } elseif ($this->logintype === 'login') {
697 // after login-error
698 switch ($redirMethod) {
699 case 'loginError':
700 if ($this->conf['redirectPageLoginError']) {
701 $redirect_url[] = $this->pi_getPageLink((int)$this->conf['redirectPageLoginError']);
702 }
703 break;
704 }
705 } elseif ($this->logintype == '' && $redirMethod == 'login' && $this->conf['redirectPageLogin']) {
706 // If login and page not accessible
707 $this->cObj->typolink('', array(
708 'parameter' => $this->conf['redirectPageLogin'],
709 'linkAccessRestrictedPages' => TRUE
710 ));
711 $redirect_url[] = $this->cObj->lastTypoLinkUrl;
712 } elseif ($this->logintype == '' && $redirMethod == 'logout' && $this->conf['redirectPageLogout'] && $GLOBALS['TSFE']->loginUser) {
713 // If logout and page not accessible
714 $redirect_url[] = $this->pi_getPageLink((int)$this->conf['redirectPageLogout']);
715 } elseif ($this->logintype === 'logout') {
716 // after logout
717 // Hook for general actions after after logout has been confirmed
718 if ($this->logintype === 'logout' && $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['logout_confirmed']) {
719 $_params = array();
720 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['logout_confirmed'] as $_funcRef) {
721 if ($_funcRef) {
722 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
723 }
724 }
725 }
726 switch ($redirMethod) {
727 case 'logout':
728 if ($this->conf['redirectPageLogout']) {
729 $redirect_url[] = $this->pi_getPageLink((int)$this->conf['redirectPageLogout']);
730 }
731 break;
732 }
733 } else {
734 // not logged in
735 // Placeholder for maybe future options
736 switch ($redirMethod) {
737 case 'getpost':
738 // Preserve the get/post value
739 $redirect_url[] = $this->redirectUrl;
740 break;
741 }
742 }
743 }
744 }
745 // Remove empty values
746 if (count($redirect_url)) {
747 return GeneralUtility::trimExplode(',', implode(',', $redirect_url), TRUE);
748 } else {
749 return array();
750 }
751 }
752
753 /**
754 * Reads flexform configuration and merge it with $this->conf
755 *
756 * @return void
757 */
758 protected function mergeflexFormValuesIntoConf() {
759 $flex = array();
760 if ($this->flexFormValue('showForgotPassword', 'sDEF')) {
761 $flex['showForgotPasswordLink'] = $this->flexFormValue('showForgotPassword', 'sDEF');
762 }
763 if ($this->flexFormValue('showPermaLogin', 'sDEF')) {
764 $flex['showPermaLogin'] = $this->flexFormValue('showPermaLogin', 'sDEF');
765 }
766 if ($this->flexFormValue('showLogoutFormAfterLogin', 'sDEF')) {
767 $flex['showLogoutFormAfterLogin'] = $this->flexFormValue('showLogoutFormAfterLogin', 'sDEF');
768 }
769 if ($this->flexFormValue('pages', 'sDEF')) {
770 $flex['pages'] = $this->flexFormValue('pages', 'sDEF');
771 }
772 if ($this->flexFormValue('recursive', 'sDEF')) {
773 $flex['recursive'] = $this->flexFormValue('recursive', 'sDEF');
774 }
775 if ($this->flexFormValue('templateFile', 'sDEF')) {
776 $flex['templateFile'] = $this->uploadDir . $this->flexFormValue('templateFile', 'sDEF');
777 }
778 if ($this->flexFormValue('redirectMode', 's_redirect')) {
779 $flex['redirectMode'] = $this->flexFormValue('redirectMode', 's_redirect');
780 }
781 if ($this->flexFormValue('redirectFirstMethod', 's_redirect')) {
782 $flex['redirectFirstMethod'] = $this->flexFormValue('redirectFirstMethod', 's_redirect');
783 }
784 if ($this->flexFormValue('redirectDisable', 's_redirect')) {
785 $flex['redirectDisable'] = $this->flexFormValue('redirectDisable', 's_redirect');
786 }
787 if ($this->flexFormValue('redirectPageLogin', 's_redirect')) {
788 $flex['redirectPageLogin'] = $this->flexFormValue('redirectPageLogin', 's_redirect');
789 }
790 if ($this->flexFormValue('redirectPageLoginError', 's_redirect')) {
791 $flex['redirectPageLoginError'] = $this->flexFormValue('redirectPageLoginError', 's_redirect');
792 }
793 if ($this->flexFormValue('redirectPageLogout', 's_redirect')) {
794 $flex['redirectPageLogout'] = $this->flexFormValue('redirectPageLogout', 's_redirect');
795 }
796 $pid = $flex['pages'] ? $this->pi_getPidList($flex['pages'], $flex['recursive']) : 0;
797 if ($pid > 0) {
798 $flex['storagePid'] = $pid;
799 }
800 $this->conf = array_merge($this->conf, $flex);
801 }
802
803 /**
804 * Loads a variable from the flexform
805 *
806 * @param string $var Name of variable
807 * @param string $sheet Name of sheet
808 * @return string Value of var
809 */
810 protected function flexFormValue($var, $sheet) {
811 return $this->pi_getFFvalue($this->cObj->data['pi_flexform'], $var, $sheet);
812 }
813
814 /**
815 * Generate link with typolink function
816 *
817 * @param string $label Linktext
818 * @param array $piVars Link vars
819 * @param boolean $returnUrl TRUE: returns only url FALSE (default) returns the link)
820 * @return string Link or url
821 */
822 protected function getPageLink($label, $piVars, $returnUrl = FALSE) {
823 $additionalParams = '';
824 if (count($piVars)) {
825 foreach ($piVars as $key => $val) {
826 $additionalParams .= '&' . $key . '=' . $val;
827 }
828 }
829 // Should GETvars be preserved?
830 if ($this->conf['preserveGETvars']) {
831 $additionalParams .= $this->getPreserveGetVars();
832 }
833 $this->conf['linkConfig.']['parameter'] = $GLOBALS['TSFE']->id;
834 if ($additionalParams) {
835 $this->conf['linkConfig.']['additionalParams'] = $additionalParams;
836 }
837 if ($returnUrl) {
838 return htmlspecialchars($this->cObj->typolink_url($this->conf['linkConfig.']));
839 } else {
840 return $this->cObj->typolink($label, $this->conf['linkConfig.']);
841 }
842 }
843
844 /**
845 * Add additional parameters for links according to TS setting preserveGETvars.
846 * Possible values are "all" or a comma separated list of allowed GET-vars.
847 * Supports multi-dimensional GET-vars.
848 * Some hardcoded values are dropped.
849 *
850 * @return string additionalParams-string
851 */
852 protected function getPreserveGetVars() {
853 $getVars = GeneralUtility::_GET();
854 unset(
855 $getVars['id'],
856 $getVars['no_cache'],
857 $getVars['logintype'],
858 $getVars['redirect_url'],
859 $getVars['cHash'],
860 $getVars[$this->prefixId]
861 );
862 if ($this->conf['preserveGETvars'] === 'all') {
863 $preserveQueryParts = $getVars;
864 } else {
865 $preserveQueryParts = GeneralUtility::trimExplode(',', $this->conf['preserveGETvars']);
866 $preserveQueryParts = GeneralUtility::explodeUrl2Array(implode('=1&', $preserveQueryParts) . '=1', TRUE);
867 $preserveQueryParts = \TYPO3\CMS\Core\Utility\ArrayUtility::intersectRecursive($getVars, $preserveQueryParts);
868 }
869 $parameters = GeneralUtility::implodeArrayForUrl('', $preserveQueryParts);
870 return $parameters;
871 }
872
873 /**
874 * Is used by forgot password - function with md5 option.
875 *
876 * @author Bernhard Kraft
877 * @param integer $len Length of new password
878 * @return string New password
879 */
880 protected function generatePassword($len) {
881 $pass = '';
882 while ($len--) {
883 $char = rand(0, 35);
884 if ($char < 10) {
885 $pass .= '' . $char;
886 } else {
887 $pass .= chr($char - 10 + 97);
888 }
889 }
890 return $pass;
891 }
892
893 /**
894 * Returns the header / message value from flexform if present, else from locallang.xlf
895 *
896 * @param string $label label name
897 * @param array $stdWrapArray TS stdWrap array
898 * @return string label text
899 */
900 protected function getDisplayText($label, $stdWrapArray = array()) {
901 $text = $this->flexFormValue($label, 's_messages') ? $this->cObj->stdWrap($this->flexFormValue($label, 's_messages'), $stdWrapArray) : $this->cObj->stdWrap($this->pi_getLL('ll_' . $label, '', TRUE), $stdWrapArray);
902 $replace = $this->getUserFieldMarkers();
903 return strtr($text, $replace);
904 }
905
906 /**
907 * Returns Array of markers filled with user fields
908 *
909 * @return array Marker array
910 */
911 protected function getUserFieldMarkers() {
912 $marker = array();
913 // replace markers with fe_user data
914 if ($GLOBALS['TSFE']->fe_user->user) {
915 // All fields of fe_user will be replaced, scheme is ###FEUSER_FIELDNAME###
916 foreach ($GLOBALS['TSFE']->fe_user->user as $field => $value) {
917 $marker['###FEUSER_' . GeneralUtility::strtoupper($field) . '###'] = $this->cObj->stdWrap($value, $this->conf['userfields.'][$field . '.']);
918 }
919 // Add ###USER### for compatibility
920 $marker['###USER###'] = $marker['###FEUSER_USERNAME###'];
921 }
922 return $marker;
923 }
924
925 /**
926 * Returns a valid and XSS cleaned url for redirect, checked against configuration "allowedRedirectHosts"
927 *
928 * @param string $url
929 * @return string cleaned referer or empty string if not valid
930 */
931 protected function validateRedirectUrl($url) {
932 $url = strval($url);
933 if ($url === '') {
934 return '';
935 }
936 $decodedUrl = rawurldecode($url);
937 $sanitizedUrl = GeneralUtility::removeXSS($decodedUrl);
938 if ($decodedUrl !== $sanitizedUrl || preg_match('#["<>\\\\]+#', $url)) {
939 GeneralUtility::sysLog(sprintf($this->pi_getLL('xssAttackDetected'), $url), 'felogin', GeneralUtility::SYSLOG_SEVERITY_WARNING);
940 return '';
941 }
942 // Validate the URL:
943 if ($this->isRelativeUrl($url) || $this->isInCurrentDomain($url) || $this->isInLocalDomain($url)) {
944 return $url;
945 }
946 // URL is not allowed
947 GeneralUtility::sysLog(sprintf($this->pi_getLL('noValidRedirectUrl'), $url), 'felogin', GeneralUtility::SYSLOG_SEVERITY_WARNING);
948 return '';
949 }
950
951 /**
952 * Determines whether the URL is on the current host and belongs to the
953 * current TYPO3 installation. The scheme part is ignored in the comparison.
954 *
955 * @param string $url URL to be checked
956 * @return boolean Whether the URL belongs to the current TYPO3 installation
957 */
958 protected function isInCurrentDomain($url) {
959 $urlWithoutSchema = preg_replace('#^https?://#', '', $url);
960 $siteUrlWithoutSchema = preg_replace('#^https?://#', '', GeneralUtility::getIndpEnv('TYPO3_SITE_URL'));
961 return GeneralUtility::isFirstPartOfStr($urlWithoutSchema . '/', GeneralUtility::getIndpEnv('HTTP_HOST') . '/')
962 && GeneralUtility::isFirstPartOfStr($urlWithoutSchema, $siteUrlWithoutSchema);
963 }
964
965 /**
966 * Determines whether the URL matches a domain
967 * in the sys_domain database table.
968 *
969 * @param string $url Absolute URL which needs to be checked
970 * @return boolean Whether the URL is considered to be local
971 */
972 protected function isInLocalDomain($url) {
973 $result = FALSE;
974 if (GeneralUtility::isValidUrl($url)) {
975 $parsedUrl = parse_url($url);
976 if ($parsedUrl['scheme'] === 'http' || $parsedUrl['scheme'] === 'https') {
977 $host = $parsedUrl['host'];
978 // Removes the last path segment and slash sequences like /// (if given):
979 $path = preg_replace('#/+[^/]*$#', '', $parsedUrl['path']);
980 $localDomains = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('domainName', 'sys_domain', '1=1' . $this->cObj->enableFields('sys_domain'));
981 if (is_array($localDomains)) {
982 foreach ($localDomains as $localDomain) {
983 // strip trailing slashes (if given)
984 $domainName = rtrim($localDomain['domainName'], '/');
985 if (GeneralUtility::isFirstPartOfStr($host . $path . '/', $domainName . '/')) {
986 $result = TRUE;
987 break;
988 }
989 }
990 }
991 }
992 }
993 return $result;
994 }
995
996 /**
997 * Determines whether the URL is relative to the
998 * current TYPO3 installation.
999 *
1000 * @param string $url URL which needs to be checked
1001 * @return boolean Whether the URL is considered to be relative
1002 */
1003 protected function isRelativeUrl($url) {
1004 $parsedUrl = @parse_url($url);
1005 if ($parsedUrl !== FALSE && !isset($parsedUrl['scheme']) && !isset($parsedUrl['host'])) {
1006 // If the relative URL starts with a slash, we need to check if it's within the current site path
1007 return $parsedUrl['path'][0] !== '/' || GeneralUtility::isFirstPartOfStr($parsedUrl['path'], GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'));
1008 }
1009 return FALSE;
1010 }
1011
1012 }