1<?php 2/* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */ 3 4/** 5 * Password assistance facility for users who have forgotten their password 6 * or for users for whom no password has been assigned yet. 7 * @author Werner Randelshofer <wrandels@hsw.fhz.ch> 8 * @author Michael Jansen <mjansen@databay.de> 9 * @version $Id$ 10 * @ingroup ServicesInit 11 */ 12class ilPasswordAssistanceGUI 13{ 14 const PERMANENT_LINK_TARGET_PW = 'pwassist'; 15 const PERMANENT_LINK_TARGET_NAME = 'nameassist'; 16 17 /** 18 * @var ilCtrl 19 */ 20 protected $ctrl; 21 22 /** 23 * @var ilLanguage 24 */ 25 protected $lng; 26 27 /** 28 * @var ilRbacReview 29 */ 30 protected $rbacreview; 31 32 /** 33 * @var ilTemplate 34 */ 35 protected $tpl; 36 37 /** 38 * @var ilSetting 39 */ 40 protected $settings; 41 42 /** 43 * @var ILIAS 44 */ 45 protected $ilias; 46 47 /** 48 * @var \ilErrorHandling 49 */ 50 private $ilErr; 51 52 53 public function __construct() 54 { 55 global $DIC; 56 57 $this->ctrl = $DIC->ctrl(); 58 $this->lng = $DIC->language(); 59 $this->rbacreview = $DIC->rbac()->review(); 60 $this->tpl = $DIC->ui()->mainTemplate(); 61 $this->settings = $DIC->settings(); 62 $this->ilias = $DIC['ilias']; 63 $this->ilErr = $DIC['ilErr']; 64 } 65 66 /** 67 * @return mixed 68 */ 69 public function executeCommand() 70 { 71 // check hack attempts 72 if (!$this->settings->get('password_assistance')) { 73 $this->ilErr->raiseError($this->lng->txt('permission_denied'), $this->ilErr->FATAL); 74 } 75 76 // check correct setup 77 if (!$this->settings->get('setup_ok')) { 78 $this->ilErr->raiseError('Setup is not completed. Please run setup routine again.', $this->ilErr->FATAL); 79 } 80 81 // Change the language, if necessary. 82 // And load the 'pwassist' language module 83 $lang = $_GET['lang']; 84 if ($lang != null && $lang != '' && $this->lng->getLangKey() != $lang) { 85 $lng = new ilLanguage($lang); 86 } 87 $this->lng->loadLanguageModule('pwassist'); 88 89 $cmd = $this->ctrl->getCmd(); 90 $next_class = $this->ctrl->getNextClass($this); 91 92 switch ($next_class) { 93 default: 94 if ($cmd != '' && method_exists($this, $cmd)) { 95 return $this->$cmd(); 96 } else { 97 if (!empty($_GET['key'])) { 98 $this->showAssignPasswordForm(); 99 } else { 100 $this->showAssistanceForm(); 101 } 102 } 103 break; 104 } 105 } 106 107 /** 108 * Returns the ILIAS http path without a trailing / 109 * @return string 110 */ 111 protected function getBaseUrl() : string 112 { 113 return rtrim(ILIAS_HTTP_PATH, '/'); 114 } 115 116 /** 117 * @param string $script 118 * @param array $queryParameters 119 * @return string 120 */ 121 protected function buildUrl(string $script, array $queryParameters) : string 122 { 123 $url = implode('/', [ 124 $this->getBaseUrl(), 125 ltrim($script, '/') 126 ]); 127 128 $url = \ilUtil::appendUrlParameterString( 129 $url, 130 http_build_query($queryParameters, null, '&') 131 ); 132 133 return $url; 134 } 135 136 /** 137 * @return ilPropertyFormGUI 138 */ 139 protected function getAssistanceForm() 140 { 141 require_once 'Services/Form/classes/class.ilPropertyFormGUI.php'; 142 $form = new ilPropertyFormGUI(); 143 144 $form->setTitle($this->lng->txt('password_assistance')); 145 $form->setFormAction($this->ctrl->getFormAction($this, 'submitAssistanceForm')); 146 $form->setTarget('_parent'); 147 148 $username = new ilTextInputGUI($this->lng->txt('username'), 'username'); 149 $username->setRequired(true); 150 $form->addItem($username); 151 152 $email = new ilEMailInputGUI($this->lng->txt('email'), 'email'); 153 $email->setRequired(true); 154 $form->addItem($email); 155 156 $form->addCommandButton('submitAssistanceForm', $this->lng->txt('submit')); 157 158 return $form; 159 } 160 161 /** 162 * @param ilPropertyFormGUI $form 163 */ 164 public function showAssistanceForm(ilPropertyFormGUI $form = null) 165 { 166 ilStartUpGUI::initStartUpTemplate('tpl.pwassist_assistance.html', true); 167 $this->tpl->setVariable('IMG_PAGEHEADLINE', ilUtil::getImagePath('icon_auth.svg')); 168 $this->tpl->setVariable('TXT_PAGEHEADLINE', $this->lng->txt('password_assistance')); 169 170 $this->tpl->setVariable( 171 'TXT_ENTER_USERNAME_AND_EMAIL', 172 str_replace( 173 "\\n", 174 '<br />', 175 sprintf( 176 $this->lng->txt('pwassist_enter_username_and_email'), 177 '<a href="mailto:' . ilUtil::prepareFormOutput($this->settings->get('admin_email')) . '">' . ilUtil::prepareFormOutput($this->settings->get('admin_email')) . '</a>' 178 ) 179 ) 180 ); 181 182 if (!$form) { 183 $form = $this->getAssistanceForm(); 184 } 185 $this->tpl->setVariable('FORM', $form->getHTML()); 186 $this->fillPermanentLink(self::PERMANENT_LINK_TARGET_PW); 187 $this->tpl->show(); 188 } 189 190 /** 191 * Reads the submitted data from the password assistance form. 192 * The following form fields are read as HTTP POST parameters: 193 * username 194 * email 195 * If the submitted username and email address matches an entry in the user data 196 * table, then ILIAS creates a password assistance session for the user, and 197 * sends a password assistance mail to the email address. 198 * For details about the creation of the session and the e-mail see function 199 * sendPasswordAssistanceMail(). 200 */ 201 public function submitAssistanceForm() 202 { 203 $form = $this->getAssistanceForm(); 204 if (!$form->checkInput()) { 205 $form->setValuesByPost(); 206 $this->showAssistanceForm($form); 207 return; 208 } 209 210 $username = $form->getInput('username'); 211 $email = $form->getInput('email'); 212 213 $usrId = \ilObjUser::getUserIdByLogin($username); 214 if (!is_numeric($usrId) || !($usrId > 0)) { 215 \ilLoggerFactory::getLogger('usr')->info(sprintf( 216 'Could not process password assistance form (reason: no user found) %s / %s', 217 $username, 218 $email 219 )); 220 221 $this->showMessageForm(sprintf($this->lng->txt('pwassist_mail_sent'), $email)); 222 return; 223 } 224 225 $defaultAuth = AUTH_LOCAL; 226 if ($GLOBALS['DIC']['ilSetting']->get('auth_mode')) { 227 $defaultAuth = $GLOBALS['DIC']['ilSetting']->get('auth_mode'); 228 } 229 230 $user = new \ilObjUser($usrId); 231 $emailAddresses = array_map('strtolower', [$user->getEmail(), $user->getSecondEmail()]); 232 233 if (!in_array(strtolower($email), $emailAddresses)) { 234 if (0 === strlen(implode('', $emailAddresses))) { 235 \ilLoggerFactory::getLogger('usr')->info(sprintf( 236 'Could not process password assistance form (reason: account without email addresses): %s / %s', 237 $username, 238 $email 239 )); 240 } else { 241 \ilLoggerFactory::getLogger('usr')->info(sprintf( 242 'Could not process password assistance form (reason: account email addresses differ from input): %s / %s', 243 $username, 244 $email 245 )); 246 } 247 } elseif ( 248 ( 249 $user->getAuthMode(true) != AUTH_LOCAL || 250 ($user->getAuthMode(true) == $defaultAuth && $defaultAuth != AUTH_LOCAL) 251 ) && !( 252 $user->getAuthMode(true) == AUTH_SAML 253 ) 254 ) { 255 \ilLoggerFactory::getLogger('usr')->info(sprintf( 256 'Could not process password assistance form (reason: not permitted for accounts using external authentication sources): %s / %s', 257 $username, 258 $email 259 )); 260 } elseif ( 261 $this->rbacreview->isAssigned($user->getId(), ANONYMOUS_ROLE_ID) || 262 $this->rbacreview->isAssigned($user->getId(), SYSTEM_ROLE_ID) 263 ) { 264 \ilLoggerFactory::getLogger('usr')->info(sprintf( 265 'Could not process password assistance form (reason: not permitted for system user or anonymous): %s / %s', 266 $username, 267 $email 268 )); 269 } else { 270 $this->sendPasswordAssistanceMail($user); 271 } 272 273 $this->showMessageForm(sprintf($this->lng->txt('pwassist_mail_sent'), $email)); 274 } 275 276 /** 277 * Creates (or reuses) a password assistance session, and sends a password 278 * assistance mail to the specified user. 279 * Note: To prevent DOS attacks, a new session is created only, if no session 280 * exists, or if the existing session has been expired. 281 * The password assistance mail contains an URL, which points to this script 282 * and contains the following URL parameters: 283 * client_id 284 * key 285 * @param $userObj ilObjUser 286 */ 287 public function sendPasswordAssistanceMail(ilObjUser $userObj) 288 { 289 global $DIC; 290 291 require_once 'include/inc.pwassist_session_handler.php'; 292 293 // Check if we need to create a new session 294 $pwassist_session = db_pwassist_session_find($userObj->getId()); 295 if ( 296 !is_array($pwassist_session) || 297 count($pwassist_session) == 0 || 298 $pwassist_session['expires'] < time() || 299 true // comment by mjansen: wtf? :-) 300 ) { 301 // Create a new session id 302 // #9700 - this didn't do anything before?! 303 // db_set_save_handler(); 304 session_start(); 305 $pwassist_session['pwassist_id'] = db_pwassist_create_id(); 306 session_destroy(); 307 db_pwassist_session_write( 308 $pwassist_session['pwassist_id'], 309 3600, 310 $userObj->getId() 311 ); 312 } 313 314 $pwassist_url = $this->buildUrl( 315 'pwassist.php', 316 [ 317 'client_id' => $this->ilias->getClientId(), 318 'lang' => $this->lng->getLangKey(), 319 'key' => $pwassist_session['pwassist_id'] 320 ] 321 ); 322 323 $alternative_pwassist_url = $this->buildUrl( 324 'pwassist.php', 325 [ 326 'client_id' => $this->ilias->getClientId(), 327 'lang' => $this->lng->getLangKey(), 328 'key' => $pwassist_session['pwassist_id'] 329 ] 330 ); 331 332 /** @var ilMailMimeSenderFactory $senderFactory */ 333 $senderFactory = $DIC["mail.mime.sender.factory"]; 334 $sender = $senderFactory->system(); 335 336 $mm = new ilMimeMail(); 337 $mm->Subject($this->lng->txt('pwassist_mail_subject'), true); 338 $mm->From($sender); 339 $mm->To($userObj->getEmail()); 340 $mm->Body( 341 str_replace( 342 array("\\n", "\\t"), 343 array("\n", "\t"), 344 sprintf( 345 $this->lng->txt('pwassist_mail_body'), 346 $pwassist_url, 347 $this->getBaseUrl() . '/', 348 $_SERVER['REMOTE_ADDR'], 349 $userObj->getLogin(), 350 'mailto:' . $DIC->settings()->get("admin_email"), 351 $alternative_pwassist_url 352 ) 353 ) 354 ); 355 $mm->Send(); 356 } 357 358 /** 359 * @param string $pwassist_id 360 * @return ilPropertyFormGUI 361 */ 362 protected function getAssignPasswordForm($pwassist_id) 363 { 364 require_once 'Services/Form/classes/class.ilPropertyFormGUI.php'; 365 $form = new ilPropertyFormGUI(); 366 367 $form->setFormAction($this->ctrl->getFormAction($this, 'submitAssignPasswordForm')); 368 $form->setTarget('_parent'); 369 370 $username = new ilTextInputGUI($this->lng->txt('username'), 'username'); 371 $username->setRequired(true); 372 $form->addItem($username); 373 374 $password = new ilPasswordInputGUI($this->lng->txt('password'), 'password'); 375 $password->setInfo(\ilUtil::getPasswordRequirementsInfo()); 376 $password->setRequired(true); 377 $form->addItem($password); 378 379 $key = new ilHiddenInputGUI('key'); 380 $key->setValue($pwassist_id); 381 $form->addItem($key); 382 383 $form->addCommandButton('submitAssignPasswordForm', $this->lng->txt('submit')); 384 385 return $form; 386 } 387 388 /** 389 * Assign password form. 390 * This form is used to assign a password to a username. 391 * To use this form, the following data must be provided as HTTP GET parameter, 392 * or in argument pwassist_id: 393 * key 394 * The key is used to retrieve the password assistance session. 395 * If the key is missing, or if the password assistance session has expired, the 396 * password assistance form will be shown instead of this form. 397 * @param ilPropertyFormGUI $form 398 * @param string $pwassist_id 399 */ 400 public function showAssignPasswordForm(ilPropertyFormGUI $form = null, $pwassist_id = '') 401 { 402 require_once 'include/inc.pwassist_session_handler.php'; 403 require_once 'Services/Language/classes/class.ilLanguage.php'; 404 405 // Retrieve form data 406 if (!$pwassist_id) { 407 $pwassist_id = $_GET['key']; 408 } 409 410 // Retrieve the session, and check if it is valid 411 $pwassist_session = db_pwassist_session_read($pwassist_id); 412 if ( 413 !is_array($pwassist_session) || 414 count($pwassist_session) == 0 || 415 $pwassist_session['expires'] < time() 416 ) { 417 ilUtil::sendFailure($this->lng->txt('pwassist_session_expired')); 418 $this->showAssistanceForm(null); 419 } else { 420 ilStartUpGUI::initStartUpTemplate('tpl.pwassist_assignpassword.html', true); 421 $this->tpl->setVariable('IMG_PAGEHEADLINE', ilUtil::getImagePath('icon_auth.svg')); 422 $this->tpl->setVariable('TXT_PAGEHEADLINE', $this->lng->txt('password_assistance')); 423 424 $this->tpl->setVariable('TXT_ENTER_USERNAME_AND_NEW_PASSWORD', $this->lng->txt('pwassist_enter_username_and_new_password')); 425 426 if (!$form) { 427 $form = $this->getAssignPasswordForm($pwassist_id); 428 } 429 $this->tpl->setVariable('FORM', $form->getHTML()); 430 $this->fillPermanentLink(self::PERMANENT_LINK_TARGET_PW); 431 $this->tpl->show(); 432 } 433 } 434 435 /** 436 * Reads the submitted data from the password assistance form. 437 * The following form fields are read as HTTP POST parameters: 438 * key 439 * username 440 * password1 441 * password2 442 * The key is used to retrieve the password assistance session. 443 * If the key is missing, or if the password assistance session has expired, the 444 * password assistance form will be shown instead of this form. 445 * If the password assistance session is valid, and if the username matches the 446 * username, for which the password assistance has been requested, and if the 447 * new password is valid, ILIAS assigns the password to the user. 448 * Note: To prevent replay attacks, the session is deleted when the 449 * password has been assigned successfully. 450 */ 451 public function submitAssignPasswordForm() 452 { 453 require_once 'include/inc.pwassist_session_handler.php'; 454 455 // We need to fetch this before form instantiation 456 $pwassist_id = ilUtil::stripSlashes($_POST['key']); 457 458 $form = $this->getAssignPasswordForm($pwassist_id); 459 if (!$form->checkInput()) { 460 $form->setValuesByPost(); 461 $this->showAssistanceForm($form); 462 return; 463 } 464 465 $username = $form->getInput('username'); 466 $password = $form->getInput('password'); 467 $pwassist_id = $form->getInput('key'); 468 469 // Retrieve the session 470 $pwassist_session = db_pwassist_session_read($pwassist_id); 471 472 if ( 473 !is_array($pwassist_session) || 474 count($pwassist_session) == 0 || 475 $pwassist_session['expires'] < time() 476 ) { 477 ilUtil::sendFailure(str_replace("\\n", '', $this->lng->txt('pwassist_session_expired'))); 478 $form->setValuesByPost(); 479 $this->showAssistanceForm($form); 480 return; 481 } else { 482 $is_successful = true; 483 $message = ''; 484 485 $userObj = \ilObjectFactory::getInstanceByObjId($pwassist_session['user_id'], false); 486 if (!$userObj || !($userObj instanceof \ilObjUser)) { 487 $message = $this->lng->txt('user_does_not_exist'); 488 $is_successful = false; 489 } 490 491 // check if the username entered by the user matches the 492 // one of the user object. 493 if ($is_successful && strcasecmp($userObj->getLogin(), $username) != 0) { 494 $message = $this->lng->txt('pwassist_login_not_match'); 495 $is_successful = false; 496 } 497 498 $error_lng_var = ''; 499 if (!ilUtil::isPasswordValidForUserContext($password, $userObj, $error_lng_var)) { 500 $message = $this->lng->txt($error_lng_var); 501 $is_successful = false; 502 } 503 504 // End of validation 505 // If the validation was successful, we change the password of the 506 // user. 507 // ------------------ 508 if ($is_successful) { 509 $is_successful = $userObj->resetPassword($password, $password); 510 if (!$is_successful) { 511 $message = $this->lng->txt('passwd_invalid'); 512 } 513 } 514 515 // If we are successful so far, we update the user object. 516 // ------------------ 517 if ($is_successful) { 518 $userObj->update(); 519 } 520 521 // If we are successful, we destroy the password assistance 522 // session and redirect to the login page. 523 // Else we display the form again along with an error message. 524 // ------------------ 525 if ($is_successful) { 526 db_pwassist_session_destroy($pwassist_id); 527 $this->showMessageForm(sprintf($this->lng->txt('pwassist_password_assigned'), $username)); 528 } else { 529 ilUtil::sendFailure(str_replace("\\n", '', $message)); 530 $form->setValuesByPost(); 531 $this->showAssignPasswordForm($form, $pwassist_id); 532 } 533 } 534 } 535 536 /** 537 * @return ilPropertyFormGUI 538 */ 539 protected function getUsernameAssistanceForm() 540 { 541 require_once 'Services/Form/classes/class.ilPropertyFormGUI.php'; 542 $form = new ilPropertyFormGUI(); 543 544 $form->setFormAction($this->ctrl->getFormAction($this, 'submitUsernameAssistanceForm')); 545 $form->setTarget('_parent'); 546 547 $email = new ilTextInputGUI($this->lng->txt('email'), 'email'); 548 $email->setRequired(true); 549 $form->addItem($email); 550 551 $form->addCommandButton('submitUsernameAssistanceForm', $this->lng->txt('submit')); 552 553 return $form; 554 } 555 556 /** 557 * Shows the password assistance form. 558 * This form is used to request a password assistance mail from ILIAS. 559 * This form contains the following fields: 560 * username 561 * email 562 * When the user submits the form, then this script is invoked with the cmd 563 * 'submitAssistanceForm'. 564 * @param ilPropertyFormGUI $form 565 */ 566 public function showUsernameAssistanceForm(ilPropertyFormGUI $form = null) 567 { 568 ilStartUpGUI::initStartUpTemplate('tpl.pwassist_username_assistance.html', true); 569 $this->tpl->setVariable('IMG_PAGEHEADLINE', ilUtil::getImagePath('icon_auth.svg')); 570 $this->tpl->setVariable('TXT_PAGEHEADLINE', $this->lng->txt('password_assistance')); 571 572 $this->tpl->setVariable( 573 'TXT_ENTER_USERNAME_AND_EMAIL', 574 str_replace( 575 "\\n", 576 '<br />', 577 sprintf( 578 $this->lng->txt('pwassist_enter_email'), 579 '<a href="mailto:' . ilUtil::prepareFormOutput($this->settings->get('admin_email')) . '">' . ilUtil::prepareFormOutput($this->settings->get('admin_email')) . '</a>' 580 ) 581 ) 582 ); 583 584 if (!$form) { 585 $form = $this->getUsernameAssistanceForm(); 586 } 587 $this->tpl->setVariable('FORM', $form->getHTML()); 588 $this->fillPermanentLink(self::PERMANENT_LINK_TARGET_NAME); 589 $this->tpl->show(); 590 } 591 592 /** 593 * Reads the submitted data from the password assistance form. 594 * The following form fields are read as HTTP POST parameters: 595 * username 596 * email 597 * If the submitted username and email address matches an entry in the user data 598 * table, then ILIAS creates a password assistance session for the user, and 599 * sends a password assistance mail to the email address. 600 * For details about the creation of the session and the e-mail see function 601 * sendPasswordAssistanceMail(). 602 */ 603 public function submitUsernameAssistanceForm() 604 { 605 require_once 'Services/User/classes/class.ilObjUser.php'; 606 require_once 'Services/Utilities/classes/class.ilUtil.php'; 607 608 $form = $this->getUsernameAssistanceForm(); 609 if (!$form->checkInput()) { 610 $form->setValuesByPost(); 611 $this->showUsernameAssistanceForm($form); 612 613 return; 614 } 615 616 $email = $form->getInput('email'); 617 $logins = ilObjUser::getUserLoginsByEmail($email); 618 619 if (is_array($logins) && count($logins) > 0) { 620 $this->sendUsernameAssistanceMail($email, $logins); 621 } else { 622 \ilLoggerFactory::getLogger('usr')->info(sprintf( 623 'Could not sent username assistance emails to (reason: no user found): %s', 624 $email 625 )); 626 } 627 628 $this->showMessageForm($this->lng->txt('pwassist_mail_sent_generic')); 629 } 630 631 /** 632 * Creates (or reuses) a password assistance session, and sends a password 633 * assistance mail to the specified user. 634 * Note: To prevent DOS attacks, a new session is created only, if no session 635 * exists, or if the existing session has been expired. 636 * The password assistance mail contains an URL, which points to this script 637 * and contains the following URL parameters: 638 * client_id 639 * key 640 * @param $email 641 * @param $logins 642 */ 643 public function sendUsernameAssistanceMail($email, array $logins) 644 { 645 global $DIC; 646 647 require_once 'Services/Mail/classes/class.ilMailbox.php'; 648 require_once 'Services/Mail/classes/class.ilMail.php'; 649 require_once 'Services/Mail/classes/class.ilMimeMail.php'; 650 require_once 'include/inc.pwassist_session_handler.php'; 651 652 $login_url = $this->buildUrl( 653 'pwassist.php', 654 [ 655 'client_id' => $this->ilias->getClientId(), 656 'lang' => $this->lng->getLangKey() 657 ] 658 ); 659 660 /** @var ilMailMimeSenderFactory $senderFactory */ 661 $senderFactory = $DIC["mail.mime.sender.factory"]; 662 $sender = $senderFactory->system(); 663 664 $mm = new ilMimeMail(); 665 $mm->Subject($this->lng->txt('pwassist_mail_subject'), true); 666 $mm->From($sender); 667 $mm->To($email); 668 $mm->Body( 669 str_replace( 670 array("\\n", "\\t"), 671 array("\n", "\t"), 672 sprintf( 673 $this->lng->txt('pwassist_username_mail_body'), 674 join(",\n", $logins), 675 $this->getBaseUrl() . '/', 676 $_SERVER['REMOTE_ADDR'], 677 $email, 678 'mailto:' . $DIC->settings()->get("admin_email"), 679 $login_url 680 ) 681 ) 682 ); 683 $mm->Send(); 684 } 685 686 /** 687 * This form is used to show a message to the user. 688 * @param string $text 689 */ 690 public function showMessageForm($text) 691 { 692 ilStartUpGUI::initStartUpTemplate('tpl.pwassist_message.html', true); 693 $this->tpl->setVariable('TXT_PAGEHEADLINE', $this->lng->txt('password_assistance')); 694 $this->tpl->setVariable('IMG_PAGEHEADLINE', ilUtil::getImagePath('icon_auth.svg')); 695 696 $this->tpl->setVariable('TXT_TEXT', str_replace("\\n", '<br />', $text)); 697 $this->fillPermanentLink(self::PERMANENT_LINK_TARGET_NAME); 698 $this->tpl->show(); 699 } 700 701 /** 702 * @param string $context 703 */ 704 protected function fillPermanentLink($context) 705 { 706 $this->tpl->setPermanentLink('usr', null, $context); 707 } 708} 709