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