1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
14:
15: defined('CON_FRAMEWORK') || die('Illegal call: Missing framework initialization - request aborted.');
16:
17: 18: 19: 20: 21: 22: 23: 24: 25:
26: class cPasswordRequest
27: {
28: 29: 30: 31: 32:
33: protected $_db;
34:
35: 36: 37: 38: 39:
40: protected $_cfg;
41:
42: 43: 44: 45: 46:
47: protected $_tpl;
48:
49: 50: 51: 52: 53:
54: protected $_username;
55:
56: 57: 58: 59: 60:
61: protected $_email;
62:
63: 64: 65: 66: 67:
68: protected $_reloadTime;
69:
70: 71: 72: 73: 74:
75: protected $_tokenLength;
76:
77: 78: 79: 80: 81: 82:
83: protected $_isEnabled;
84:
85: 86: 87: 88: 89:
90: protected $_sendermail;
91:
92: 93: 94: 95: 96:
97: protected $_sendername;
98:
99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109:
110: public function __construct($db, $cfg)
111: {
112:
113: if (!is_object($db)) {
114: $this->_db = cRegistry::getDb();
115: } else {
116: $this->_db = $db;
117: }
118:
119:
120: $this->_cfg = $cfg;
121: $this->_tpl = new cTemplate();
122: $this->_username = '';
123: $this->_email = '';
124:
125:
126: $this->_reloadTime = 240;
127:
128:
129: $this->_tokenLength = 14;
130:
131:
132:
133: $sEnable = getSystemProperty('pw_request', 'enable');
134: if ($sEnable == 'false') {
135: $this->_isEnabled = false;
136: } else {
137: $this->_isEnabled = true;
138: }
139:
140:
141:
142: $sendermail = getSystemProperty('system', 'mail_sender');
143: if (preg_match("/^.+@.+\.([A-Za-z0-9\-_]{1,20})$/", $sendermail)) {
144: $this->_sendermail = $sendermail;
145: } else {
146: $this->_sendermail = 'info@contenido.org';
147: }
148:
149:
150: $sendername = getSystemProperty('system', 'mail_sender_name');
151: if ($sendername != '') {
152: $this->_sendername = $sendername;
153: } else {
154: $this->_sendername = 'CONTENIDO Backend';
155: }
156:
157:
158:
159: if (true === $this->_isEnabled) {
160:
161: if (isset($_GET['pw_reset']) && '' !== $_GET['pw_reset']) {
162:
163: $aRequests = $this->_getCurrentRequests();
164: if (count($aRequests) > 0) {
165:
166: if (false === isset($_POST['user_name'])
167: || false === isset($_POST['user_pw'])
168: || false === isset($_POST['user_pw_repeat'])
169: ) {
170:
171: $this->_renderNewPwForm();
172: } else {
173:
174: $this->_handleResetPw();
175: }
176: }
177: }
178: }
179: }
180:
181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195:
196: public function renderForm($return = false)
197: {
198:
199: if (!$this->_isEnabled) {
200: return '';
201: }
202:
203:
204:
205: if (isset($_POST['action']) && $_POST['action'] == 'request_pw') {
206:
207:
208: $this->_username = $_POST['request_username'];
209:
210: $message = $this->_handleNewPassword();
211:
212:
213: $this->_tpl->set('s', 'JS_CALL', 'showRequestLayer();');
214: } else {
215: $message = '';
216:
217:
218: $this->_tpl->set('s', 'JS_CALL', '');
219: }
220:
221:
222: $form = new cHTMLForm('request_pw', 'index.php', 'post');
223:
224:
225: $safeUsername = stripslashes($this->_username);
226: $safeUsername = conHtmlentities($safeUsername);
227:
228:
229: $inputUsername = new cHTMLTextbox('request_username', $safeUsername, '', '', 'request_username');
230: $inputUsername->setStyle('width:215px;');
231:
232:
233: $form->setVar('action', 'request_pw');
234: $form->setVar('belang', $GLOBALS['belang']);
235:
236:
237: $form->setContent(
238: '<input class="password_request_input" type="image" src="images/submit.gif" alt="' . i18n('Submit')
239: . '" title="' . i18n('Submit') . '">' . $inputUsername->render()
240: );
241: $this->_tpl->set('s', 'FORM', $form->render());
242: $this->_tpl->set('s', 'MESSAGE', $message);
243: $this->_tpl->set('s', 'LABEL', i18n('Please enter your login') . ':');
244:
245:
246: return $this->_tpl->generate(
247: $this->_cfg['path']['contenido'] . $this->_cfg['path']['templates']
248: . $this->_cfg['templates']['request_password'],
249: $return
250: );
251: }
252:
253: 254: 255: 256: 257: 258: 259:
260: protected function _renderNewPwForm()
261: {
262: if (isset($_POST['action']) && $_POST['action'] == 'reset_pw') {
263: $this->_username = $_POST['request_username'];
264:
265: $message = $this->_handleNewPassword();
266:
267:
268: $this->_tpl->set('s', 'JS_CALL', '');
269: } else {
270:
271: $this->_tpl->set('s', 'JS_CALL', 'showResetLayer();');
272: }
273:
274: $msg = i18n('You may now set a new password');
275: $this->_tpl->set('s', 'RESET_LABEL', $msg);
276:
277:
278: $form =
279: new cHTMLForm('reset_form', htmlentities(cRegistry::getBackendUrl()) . '?pw_reset=' . $_GET['pw_reset']);
280:
281: $userNameLbl = new cHTMLDiv(new cHTMLLabel(i18n('User name') . ': ', 'user_name'));
282: $userNameBox = new cHTMLTextbox('user_name');
283: $userNameBox->removeAttribute('size');
284: $userNameBox = new cHTMLDiv($userNameBox);
285:
286: $userPwLbl = new cHTMLLabel(i18n('New password') . ': ', 'user_pw');
287: $userPwBox = new cHTMLTextbox('user_pw');
288: $userPwBox->setAttribute('type', 'password');
289: $userPwBox->removeAttribute('size');
290: $userPwBox = new cHTMLDiv($userPwBox);
291:
292: $userPwRepeatLbl = new cHTMLLabel(i18n('Confirm new password'), 'user_pw_repeat');
293: $userPwRepeatBox = new cHTMLTextbox('user_pw_repeat');
294: $userPwRepeatBox->setAttribute('type', 'password');
295: $userPwRepeatBox->removeAttribute('size');
296:
297: $sendBtn = new cHTMLButton('submit');
298: $sendBtn->setAttribute('type', 'image');
299: $sendBtn->setAttribute('src', 'images/submit.gif');
300: $sendBtn->setAttribute('alt', i18n('Submit'));
301: $sendBtn->setAttribute('title', i18n('Submit'));
302: $sendBtn->setAttribute('class', 'send_btn');
303: $lastFormRow = new cHTMLDiv($userPwRepeatLbl . $userPwRepeatBox . $sendBtn);
304: $lastFormRow->setAttribute('class', 'last_row');
305:
306: $sendBtn->removeAttribute('value');
307: $form->setContent([$userNameLbl, $userNameBox, $userPwLbl, $userPwBox, $lastFormRow]);
308:
309: $this->_tpl->set('s', 'RESET_MESSAGE', '');
310:
311: $this->_tpl->set('s', 'RESET_FORM', $form->render());
312: }
313:
314: 315: 316: 317: 318: 319: 320: 321:
322: protected function _getCurrentRequests()
323: {
324: $oApiUserPasswordRequest = new cApiUserPasswordRequestCollection();
325:
326: return $oApiUserPasswordRequest->fetchCurrentRequests();
327: }
328:
329: 330: 331: 332: 333: 334: 335: 336: 337: 338:
339: protected function _handleNewPassword()
340: {
341:
342: $message = '';
343:
344:
345:
346: $sql = "SELECT
347: username, email
348: FROM
349: " . $this->_cfg['tab']['user'] . "
350: WHERE
351: username = '" . $this->_db->escape($this->_username) . "'
352: AND (valid_from <= NOW() OR valid_from = '0000-00-00 00:00:00' OR valid_from IS NULL)
353: AND (valid_to >= NOW() OR valid_to = '0000-00-00 00:00:00' OR valid_to IS NULL)";
354: $this->_db->query($sql);
355:
356: if ($this->_db->nextRecord() && md5($this->_username) == md5($this->_db->f('username'))) {
357:
358: $isAllowed = true;
359:
360:
361: $lastPwRequest = '0000-00-00 00:00:00';
362:
363:
364: $oApiUser = new cApiUser();
365:
366:
367: if (false === $oApiUser->loadBy('username', $this->_username)) {
368: $isAllowed = false;
369: $message = i18n('New password was submitted to your e-mail address.');
370: } else {
371: $oApiPasswordRequestCol = new cApiUserPasswordRequestCollection();
372: $requests = $oApiPasswordRequestCol->fetchAvailableRequests();
373:
374:
375: foreach ($requests as $oApiUserPasswordRequest) {
376:
377: $reqTime = $oApiUserPasswordRequest->get('request');
378:
379:
380: if (strtotime($lastPwRequest) < strtotime($reqTime)
381: && $this->_db->f($oApiUser->getPrimaryKeyName()) === $oApiUser->get(
382: $oApiUser->getPrimaryKeyName()
383: )
384: ) {
385: $lastPwRequest = $reqTime;
386: }
387:
388:
389:
390: if (false === ($outdatedStr = getEffectiveSetting('pw_request', 'outdated_threshold', false))
391: || '' === $outdatedStr
392: ) {
393: $outdatedStr = '-1 day';
394: }
395:
396:
397: $outdated = new DateTime('now', new DateTimeZone('UTC'));
398: $outdated->modify($outdatedStr);
399: $expiration = new DateTime($oApiUserPasswordRequest->get('expiration'), new DateTimeZone('UTC'));
400: if (false === $oApiUserPasswordRequest->get('expiration')
401: || '' === $oApiUserPasswordRequest->get('expiration')
402: || $expiration < $outdated
403: ) {
404:
405: $oApiPasswordRequestCol->delete(
406: $oApiUserPasswordRequest->get($oApiUserPasswordRequest->getPrimaryKeyName())
407: );
408: }
409: }
410:
411:
412: $uid = $oApiUser->get($oApiUser->getPrimaryKeyName());
413: $requests = $oApiPasswordRequestCol->fetchAvailableRequests($uid);
414:
415:
416: if (false === ($resetThreshold = getEffectiveSetting('pw_request', 'reset_threshold', false))
417: || '' === $resetThreshold
418: ) {
419:
420: $resetThreshold = 4;
421: }
422:
423:
424: if (count($requests) > $resetThreshold) {
425: $isAllowed = false;
426: $message =
427: i18n('Too many password reset requests. You may wait before requesting a new password.');
428: }
429: unset($requests);
430: }
431:
432:
433: $this->_email = $this->_db->f('email');
434:
435:
436: if (preg_match('/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/', $lastPwRequest, $aMatches)) {
437: $lastRequest =
438: mktime($aMatches[4], $aMatches[5], $aMatches[6], $aMatches[2], $aMatches[3], $aMatches[1]);
439:
440:
441: if ((time() - $lastRequest) < (60 * $this->_reloadTime)) {
442:
443: $isAllowed = false;
444: $message = sprintf(i18n('Password requests are allowed every %s minutes.'), $this->_reloadTime);
445: }
446: }
447:
448: $this->_username = stripslashes($this->_username);
449:
450:
451:
452:
453: if ((!preg_match("/^.+@.+\.([A-Za-z0-9\-_]{1,20})$/", $this->_email)
454: || $this->_email == 'sysadmin@IhreSite.de'
455: || $this->_email == 'admin_kunde@IhreSite.de')
456: && $isAllowed
457: ) {
458: $isAllowed = false;
459:
460:
461:
462: $message = i18n('No matching data found. Please contact your system administrator.');
463: }
464:
465:
466:
467: if ($isAllowed) {
468:
469: $token = $this->_generateToken();
470:
471:
472:
473: $expiration = new DateTime('+4 hour', new DateTimeZone('UTC'));
474:
475: if (false == $token || false === $this->_safePwResetRequest($token, $expiration)
476: || false === $this->_submitMail($token)
477: ) {
478: $message = i18n('An unknown problem occurred. Please contact your system administrator.');
479: } else {
480: $message = i18n('New password was submitted to your e-mail address.');
481: }
482: } else {
483: sleep(5);
484: }
485: } else {
486:
487:
488: $message = i18n('No matching data found. Please contact your system administrator.');
489: sleep(5);
490: }
491:
492: return $message;
493: }
494:
495: 496: 497: 498: 499: 500: 501:
502: protected function _handleResetPw()
503: {
504: $this->_tpl->set('s', 'JS_CALL', 'showResetLayer();');
505: $username = (string)$_POST['user_name'];
506: $pw = (string)$_POST['user_pw'];
507: $pwRepeat = (string)$_POST['user_pw_repeat'];
508:
509: if (0 === cString::getStringLength($username)) {
510: $this->_tpl->set('s', 'RESET_MESSAGE', i18n('Username can\'t be empty'));
511: $this->_tpl->set('s', 'RESET_LABEL', '');
512: $this->_renderNewPwForm();
513:
514: return;
515: }
516: if ($pw !== $pwRepeat) {
517: $this->_tpl->set('s', 'RESET_MESSAGE', i18n('Passwords don\'t match'));
518: $this->_tpl->set('s', 'RESET_LABEL', '');
519: $this->_renderNewPwForm();
520:
521: return;
522: }
523: if ((string)$_POST['user_pw'] === (string)$_GET['pw_reset']) {
524: $this->_tpl->set('s', 'RESET_MESSAGE', i18n('You may not use the confirmation token as password'));
525: $this->_tpl->set('s', 'RESET_LABEL', '');
526: $this->_renderNewPwForm();
527:
528: return;
529: }
530:
531:
532: $oApiUser = new cApiUser();
533: $oApiUser->loadUserByUsername($username);
534:
535: if (false === $oApiUser->isLoaded()) {
536:
537:
538: $this->_tpl->set('s', 'RESET_MESSAGE', i18n('New password has been set.'));
539: $this->_tpl->set('s', 'RESET_LABEL', '');
540: $this->_tpl->set('s', 'RESET_FORM', '');
541:
542: return;
543: }
544:
545: $oPasswordRequest = new cApiUserPasswordRequestCollection();
546:
547:
548:
549:
550: if (null === ($requests = $this->_getCurrentRequests())) {
551:
552: $this->_tpl->set('s', 'RESET_MESSAGE', i18n('New password has been set.'));
553: $this->_tpl->set('s', 'RESET_LABEL', '');
554: $this->_tpl->set('s', 'RESET_FORM', '');
555:
556: return;
557: }
558:
559:
560: $validUser = false;
561: foreach ($requests as $request) {
562:
563: if ($request->get('validation_token') === $_GET['pw_reset']) {
564:
565: if ($oApiUser->get($oApiUser->getPrimaryKeyName()) === $request->get($oApiUser->getPrimaryKeyName())) {
566:
567: $validUser = true;
568: }
569: }
570: }
571: if (false === $validUser) {
572:
573:
574: $this->_tpl->set('s', 'RESET_MESSAGE', i18n('New password has been set.'));
575: $this->_tpl->set('s', 'RESET_LABEL', '');
576: $this->_tpl->set('s', 'RESET_FORM', '');
577:
578: return;
579: }
580:
581:
582: $res = $oApiUser->setPassword($pw);
583:
584:
585: if (cApiUser::PASS_OK !== $res) {
586:
587: $msg = cApiUser::getErrorString($res);
588: $this->_tpl->set('s', 'RESET_MESSAGE', $msg);
589: $this->_tpl->set('s', 'RESET_LABEL', '');
590: $this->_renderNewPwForm();
591:
592: return;
593: }
594:
595:
596: if (false !== $oApiUser->store()) {
597: $this->_tpl->set('s', 'RESET_LABEL', '');
598: $this->_tpl->set('s', 'RESET_FORM', '');
599:
600: $oPasswordRequest->deleteByUserId($oApiUser->get($oApiUser->getPrimaryKeyName()));
601: $msg = i18n('New password has been set.');
602: } else {
603:
604: $msg = i18n('An unknown problem occurred. Please contact your system administrator.');
605: }
606:
607:
608: $this->_tpl->set('s', 'RESET_MESSAGE', $msg);
609: }
610:
611: 612: 613: 614: 615: 616: 617: 618: 619: 620: 621: 622: 623: 624:
625: protected function _safePwResetRequest($token, DateTime $expiration)
626: {
627: $oUserPwRequestCol = new cApiUserPasswordRequestCollection();
628: $oUserPwRequest = $oUserPwRequestCol->createNewItem();
629:
630:
631: $requestTime = new DateTime('now', new DateTimeZone('UTC'));
632: $oApiUser = new cApiUser();
633: $oApiUser->loadBy('username', $this->_username);
634:
635: $oUserPwRequest->set($oApiUser->getPrimaryKeyName(), $oApiUser->get($oApiUser->getPrimaryKeyName()));
636: $oUserPwRequest->set('request', $requestTime->format('Y-m-d H:i:s'));
637: $oUserPwRequest->set('expiration', $expiration->format('Y-m-d H:i:s'));
638: $oUserPwRequest->set('validation_token', $token);
639:
640:
641: return $oUserPwRequest->store();
642: }
643:
644: 645: 646: 647: 648: 649: 650: 651: 652:
653: protected function _submitMail($token)
654: {
655: $cfg = cRegistry::getConfig();
656:
657: $token = (string)$token;
658:
659:
660: $msg = i18n(
661: "Dear CONTENIDO-User %s,\n\nA request to change your password for Content Management System CONTENIDO was made. "
662: );
663: $msg .= i18n("Use the following URL to confirm the password change:\n\n");
664: $msg .= cRegistry::getBackendUrl() . '?pw_reset=';
665: $msg .= i18n("%s\n\nBest regards\n\nYour CONTENIDO sysadmin");
666: $mailBody = sprintf($msg, $this->_username, $token);
667:
668: $from = [$this->_sendermail => $this->_sendername];
669:
670:
671: if ($cfg['php_settings']['default_charset'] != 'UTF-8') {
672: $subject = utf8_encode(
673: conHtmlEntityDecode(
674: stripslashes(i18n('Your new password for CONTENIDO Backend')),
675: '',
676: $cfg['php_settings']['default_charset']
677: )
678: );
679: $body = utf8_encode(conHtmlEntityDecode($mailBody, '', $cfg['php_settings']['default_charset']));
680: } else {
681: $subject = conHtmlEntityDecode(stripslashes(i18n('Your new password for CONTENIDO Backend')));
682: $body = conHtmlEntityDecode($mailBody);
683: }
684:
685: try {
686: $mailer = new cMailer();
687: $mailer->sendMail($from, $this->_email, $subject, $body);
688:
689: return true;
690: } catch (cDbException $e) {
691: return false;
692: } catch (cInvalidArgumentException $e) {
693: return false;
694: } catch (cException $e) {
695: return false;
696: }
697: }
698:
699: 700: 701: 702: 703: 704:
705: protected function _generateToken()
706: {
707:
708: $chars = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghjkmnopqrstuvwxyz123456789";
709:
710: $password = "";
711:
712:
713: for ($i = 0; $i < $this->_tokenLength; $i++) {
714: $password .= $chars[rand(0, cString::getStringLength($chars))];
715: }
716:
717: return $password;
718: }
719: }
720: