• Côme Chilliet's avatar
    :sparkles: feat(core) Big refactor of dialog system · 94eaa6ba
    Côme Chilliet authored
    This replaces save_object and execute methods by 3 methods:
    readPost - Reads POST data
    update - Update object state
    render - Render HTML UI
    
    The point is to avoid reading POST and rendering HTML when this is not
     needed (when doing stuff through the webservice for instance).
    
    It’s also more consisent across FD with all classes handling some kind
     of dialog implementing the new interface FusionDirectoryDialog which
     makes sure these 3 methods are implemented.
    
    issue #6072
    Unverified
    94eaa6ba
class_passwordRecovery.inc 14.03 KiB
<?php
/*
  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
  Copyright (C) 2003-2010  Cajus Pollmeier
  Copyright (C) 2011-2018  FusionDirectory
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
class passwordRecovery extends standAlonePage
  protected $loginAttribute;
  protected $login;
  protected $email_address;
  protected $message;
  protected $step;
  /* Salt needed to mask the uniq id in the ldap */
  protected $salt;
  /* Uniq ID recovered from email */
  protected $uniq;
  /* Delay allowed for the user to change his password (minutes) */
  protected $delay_allowed;
  /* Sender */
  protected $from_mail;
  protected $mail_body;
  protected $mail_subject;
  protected $mail2_body;
  protected $mail2_subject;
  protected $usealternates;
  function init ()
    parent::init();
    $this->step     = 1;
    $this->message  = [];
    if (isset($_GET['email_address']) && ($_GET['email_address'] != '')) {
      $this->email_address = validate($_GET['email_address']);
    } elseif (isset($_POST['email_address'])) {
      $this->email_address = validate($_POST['email_address']);
    /* Check for selected user... */
    if (isset($_GET['login']) && $_GET['login'] != '') {
      $this->login = validate($_GET['login']);
    } elseif (isset($_POST['login'])) {
      $this->login = validate($_POST['login']);
    } else {
      $this->login = '';
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
} } function readPost () { if (!$this->activated) { return; } /* Got a formular answer, validate and try to log in */ if ($_SERVER['REQUEST_METHOD'] == 'POST') { if (session::is_set('_LAST_PAGE_REQUEST')) { session::set('_LAST_PAGE_REQUEST', time()); } if (isset($_POST['change'])) { $this->step4(); } elseif (isset($_POST['apply'])) { if ($_POST['email_address'] == '') { $this->message[] = new FusionDirectoryError(msgPool::required(_('Email address'))); return; } $this->email_address = $_POST['email_address']; $this->step2(); if ($this->step == 2) { /* No errors */ $this->step3(); } } } elseif ($_SERVER['REQUEST_METHOD'] == 'GET') { if (isset($_GET['uniq'])) { $this->step4(); } } } function execute () { $this->readPost(); /* Do we need to show error messages? */ if (count($this->message) != 0) { /* Show error message and continue editing */ msg_dialog::displayChecks($this->message); } logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $this->step, "Step"); $smarty = get_smarty(); $this->assignSmartyVars(); $smarty->append('js_files', 'include/pwdStrength.js'); $smarty->append('css_files', get_template_path('login.css')); $smarty->assign('title', _('Password recovery')); $smarty->display(get_template_path('headers.tpl')); $smarty->assign('step', $this->step); $smarty->assign('delay_allowed', $this->delay_allowed); $smarty->assign('activated', $this->activated); $smarty->assign('email_address', $this->email_address); $smarty->display(get_template_path('recovery.tpl')); exit(); } /* Check that password recovery is activated, read config in ldap * Returns a boolean saying if password recovery is activated */ protected function readLdapConfig (): bool { global $config;
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
$this->salt = $config->get_cfg_value('passwordRecoverySalt'); $this->delay_allowed = $config->get_cfg_value('passwordRecoveryValidity'); $this->mail_subject = $config->get_cfg_value('passwordRecoveryMailSubject'); $this->mail_body = $config->get_cfg_value('passwordRecoveryMailBody'); $this->mail2_subject = $config->get_cfg_value('passwordRecoveryMail2Subject'); $this->mail2_body = $config->get_cfg_value('passwordRecoveryMail2Body'); $this->from_mail = $config->get_cfg_value('passwordRecoveryEmail'); $this->usealternates = $config->get_cfg_value('passwordRecoveryUseAlternate'); $this->loginAttribute = $config->get_cfg_value('passwordRecoveryLoginAttribute', 'uid'); logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $config->get_cfg_value('passwordRecoveryActivated'), "passwordRecoveryActivated"); return ($config->get_cfg_value('passwordRecoveryActivated') == "TRUE"); } function storeToken ($temp_password) { global $config; /* Store it in ldap with the salt */ $salt_temp_password = $this->salt.$temp_password.$this->salt; $sha1_temp_password = "{SHA}".base64_encode(pack("H*", sha1($salt_temp_password))); $ldap = $config->get_ldap_link(); // Check if token branch is here $token = get_ou('recoveryTokenRDN').get_ou('fusiondirectoryRDN').$config->current['BASE']; $ldap->cat($token, ['dn']); if (!$ldap->count()) { /* It's not, let's create it */ $ldap->cd($config->current['BASE']); try { $ldap->create_missing_trees($token); } catch (FusionDirectoryError $error) { return $error; } fusiondirectory_log("Created token branch ".$token); } $dn = 'ou='.$this->login.','.$token; $ldap->cat($dn, ['dn']); $add = ($ldap->count() == 0); /* We store the token and its validity due date */ $attrs = [ 'objectClass' => ['organizationalUnit'], 'ou' => $this->login, 'userPassword' => $sha1_temp_password, 'description' => time() + $this->delay_allowed * 60, ]; $ldap->cd($dn); if ($add) { $ldap->add($attrs); } else { $ldap->modify($attrs); } if (!$ldap->success()) { return new SimplePluginLdapError( NULL, $dn, ($add ? LDAP_ADD : LDAP_MOD), $ldap->get_error(), $ldap->get_errno() ); } /* Everything went well */ return NULL;
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
} function checkToken ($token) { global $config; $salt_token = $this->salt.$token.$this->salt; $sha1_token = "{SHA}".base64_encode(pack("H*", sha1($salt_token))); /* Retrieve hash from the ldap */ $ldap = $config->get_ldap_link(); $token = get_ou('recoveryTokenRDN').get_ou('fusiondirectoryRDN').$config->current['BASE']; $dn = 'ou='.$this->login.','.$token; $ldap->cat($dn); $attrs = $ldap->fetch(); $ldap_token = $attrs['userPassword'][0]; $last_time_recovery = $attrs['description'][0]; /* Return TRUE if the token match and is still valid */ return ($last_time_recovery >= time()) && ($ldap_token == $sha1_token); } function getUserDn () { global $config; /* Retrieve dn from the ldap */ $ldap = $config->get_ldap_link(); $objectClasses = ['gosaMailAccount']; if (class_available('personalInfo') && ($config->get_cfg_value('privateEmailPasswordRecovery', 'FALSE') == 'TRUE')) { $objectClasses[] = 'fdPersonalInfo'; } if (class_available('supannAccount') && ($config->get_cfg_value('supannPasswordRecovery', 'TRUE') == 'TRUE')) { $objectClasses[] = 'supannPerson'; } $filter = '(&(|(objectClass='.join(')(objectClass=', $objectClasses).'))('.$this->loginAttribute.'='.ldap_escape_f($this->login).'))'; $ldap->cd($config->current['BASE']); $ldap->search($filter, ['dn']); if ($ldap->count() < 1) { $this->message[] = new FusionDirectoryError(htmlescape(sprintf(_('Did not find an account with login "%s"'), $this->login))); return; } elseif ($ldap->count() > 1) { $this->message[] = new FusionDirectoryError(htmlescape(sprintf(_('Found multiple accounts with login "%s"'), $this->login))); return; } $attrs = $ldap->fetch(); return $attrs['dn']; } /* Find the login of for the given email address */ function step2 ($email = NULL) { global $config; if ($email !== NULL) { /* Special case when recovery is called from webservice */ $this->email_address = $email; } /* Search login corresponding to the mail */ $address_escaped = ldap_escape_f($this->email_address); if ($this->usealternates) { $filter = '(&(objectClass=gosaMailAccount)(|(mail='.$address_escaped.')(gosaMailAlternateAddress='.$address_escaped.')))'; } else { $filter = '(&(objectClass=gosaMailAccount)(mail='.$address_escaped.'))';
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
} if (class_available('personalInfo') && ($config->get_cfg_value('privateEmailPasswordRecovery', 'FALSE') == 'TRUE')) { $filter = '(|'.$filter.'(&(objectClass=fdPersonalInfo)(fdPrivateMail='.$address_escaped.')))'; } if (class_available('supannAccount') && ($config->get_cfg_value('supannPasswordRecovery', 'TRUE') == 'TRUE')) { $filter = '(|'.$filter.'(&(objectClass=supannPerson)(supannMailPerso='.$address_escaped.')))'; } $ldap = $config->get_ldap_link(); $ldap->cd($config->current['BASE']); $ldap->search($filter, ['dn', 'userPassword', $this->loginAttribute]); /* Only one ldap node should be found */ if ($ldap->count() < 1) { $this->message[] = new FusionDirectoryError(htmlescape(sprintf(_('There is no account using email "%s"'), $this->email_address))); return FALSE; } elseif ($ldap->count() > 1) { $this->message[] = new FusionDirectoryError(htmlescape(sprintf(_('There are several accounts using email "%s"'), $this->email_address))); return FALSE; } $attrs = $ldap->fetch(); $method = passwordMethod::get_method($attrs['userPassword'][0], $attrs['dn']); if (is_object($method) && $method->is_locked($attrs['dn'])) { $this->message[] = new FusionDirectoryError(htmlescape(sprintf(_('The user using email "%s" is locked. Please contact your administrator.'), $this->email_address))); return FALSE; } $this->login = $attrs[$this->loginAttribute][0]; $this->step = 2; if ($this->interactive) { $smarty = get_smarty(); $smarty->assign('login', $this->login); $smarty->assign('email_address', $this->email_address); $params = $this->encodeParams(['login', 'directory', 'email_address']); $smarty->assign('params', $params); } return $attrs['dn']; } function generateAndStoreToken () { $activatecode = static::generateRandomHash(); $error = $this->storeToken($activatecode); if (!empty($error)) { $this->message[] = $error; return FALSE; } return $activatecode; } /* generate a token and send it by email */ function step3 () { /* Send a mail, save information in session and create a very random unique id */ $token = $this->generateAndStoreToken(); if ($token === FALSE) { return; } $reinit_link = URL::getPageURL(); $reinit_link .= '?uniq='.urlencode($token); $reinit_link .= '&login='.urlencode($this->login); $reinit_link .= '&email_address='.urlencode($this->email_address);
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
logging::debug(DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $reinit_link, 'Setting link to'); /* Send the mail */ $body = sprintf($this->mail_body, $this->login, $reinit_link); if (mail_utf8($this->email_address, FALSE, $this->from_mail, $this->mail_subject, $body)) { $this->step = 3; } else { $this->message[] = new FusionDirectoryError(htmlescape(_('Contact your administrator, there was a problem with mail server'))); } $smarty = get_smarty(); $smarty->assign('login', $this->login); } /* check if the given token is the good one */ function step4 () { $uniq_id_from_mail = validate($_GET['uniq']); if (!$this->checkToken($uniq_id_from_mail)) { $this->message[] = new FusionDirectoryError(htmlescape(_('This token is invalid'))); return; } $smarty = get_smarty(); $smarty->assign('uniq', $uniq_id_from_mail); $this->uniq = $uniq_id_from_mail; $this->step = 4; $smarty->assign('login', $this->login); $params = $this->encodeParams(['login', 'directory', 'email_address', 'uniq']); $smarty->assign('params', $params); if (isset($_POST['change'])) { $this->step5(); } } function changeUserPassword ($new_password, $new_password_repeated) { $dn = $this->getUserDn(); if (!$dn) { return FALSE; } $userTabs = objects::open($dn, 'user'); $userTab = $userTabs->getBaseObject(); $userTab->userPassword = [ '', $new_password, $new_password_repeated, $userTab->userPassword, $userTab->attributesAccess['userPassword']->isLocked() ]; /* Is there any problem with entered passwords? */ $userTabs->update(); $errors = $userTabs->save(); if (!empty($errors)) { $this->message = $errors; return; } fusiondirectory_log('User '.$this->login.' password has been changed'); return TRUE; }
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
/* change the password and send confirmation email */ function step5 () { $success = $this->changeUserPassword($_POST['new_password'], $_POST['new_password_repeated']); if (!$success) { return; } /* Send the mail */ $body = sprintf($this->mail2_body, $this->login); if (mail_utf8($this->email_address, FALSE, $this->from_mail, $this->mail2_subject, $body)) { $smarty = get_smarty(); $this->step = 5; $smarty->assign('changed', TRUE); } else { $this->message[] = new FusionDirectoryError(htmlescape(_('There was a problem with mail server, confirmation email not sent'))); } } function getErrorMessages (): array { return $this->message; } function setLogin (string $login) { $this->login = $login; } function getLogin () { return $this->login; } }