Commit 89455a49 authored by Patrick Barroca's avatar Patrick Barroca 😠
Browse files

Merge branch 'sandbox-user-password-hash' into 'stable'

Sandbox user password hash

See merge request afi/opacce!2424
parents b66adccf 368f9027
- Administration : hashage des mots de passe administrateurs
\ No newline at end of file
...@@ -161,23 +161,35 @@ class Admin_IndexController extends ZendAfi_Controller_Action { ...@@ -161,23 +161,35 @@ class Admin_IndexController extends ZendAfi_Controller_Action {
public function becomeAction() { public function becomeAction() {
if(!Class_Users::getIdentity()->isSuperAdmin()) { if (!Class_Users::getIdentity()->isSuperAdmin()) {
$this->_helper->notify($this->_('Vous n\'avez pas les droits suffisants pour utiliser cette fonctionnalité.')); $this->_helper->notify($this->_('Vous n\'avez pas les droits suffisants pour utiliser cette fonctionnalité.'));
return $this->_redirectToIndex(); return $this->_redirectToIndex();
} }
if(!$user = Class_Users::find($this->_getParam('id', null))) { if (!$user = Class_Users::find($this->_getParam('id', null))) {
$this->_helper->notify($this->_('Utilisateur invalide')); $this->_helper->notify($this->_('Utilisateur invalide'));
return $this->_redirectToIndex(); return $this->_redirectToIndex();
} }
if(!ZendAfi_Auth::getInstance()->authenticateLoginPassword($user->getLogin(), $user->getPassword())) { if ($user->isAbonne())
return $this->_becomePatron($user);
ZendAfi_Auth::getInstance()->logUser($user);
$this->_helper->notify($this->_('Vous êtes maintenant connecté avec l\'utilisateur "%s"',
$user->getNomComplet()));
$this->_redirectToIndex();
}
protected function _becomePatron($user) {
if (!ZendAfi_Auth::getInstance()->authenticateLoginPassword($user->getLogin(), $user->getPassword())) {
$this->_helper->notify($this->_('Nom d\'utilisateur ou mot de passe invalide')); $this->_helper->notify($this->_('Nom d\'utilisateur ou mot de passe invalide'));
return $this->_redirectToIndex(); return $this->_redirectToIndex();
} }
$this->_helper->notify($this->_('Vous êtes maintenant connecté avec l\'utilisateur "%s"', $this->_helper->notify($this->_('Vous êtes maintenant connecté avec l\'utilisateur "%s"',
$user->getNomComplet())); $user->getNomComplet()));
$this->_redirectToIndex();
return $this->_redirectToIndex();
} }
} }
\ No newline at end of file
...@@ -20,6 +20,10 @@ ...@@ -20,6 +20,10 @@
*/ */
class AuthController extends ZendAfi_Controller_Action { class AuthController extends ZendAfi_Controller_Action {
const
LOST_PASS_NOLOGIN = 1,
LOST_PASS_NOUSER = 2,
LOST_PASS_NOMAIL = 4;
public function init() { public function init() {
$this->view->locale = Zend_Registry::get('locale'); $this->view->locale = Zend_Registry::get('locale');
...@@ -54,12 +58,13 @@ class AuthController extends ZendAfi_Controller_Action { ...@@ -54,12 +58,13 @@ class AuthController extends ZendAfi_Controller_Action {
public function getErrorMessages($error_code) { public function getErrorMessages($error_code) {
$messages= [1 => $this->_('Veuillez saisir votre identifiant.'), $messages = [static::LOST_PASS_NOLOGIN => $this->_('Veuillez saisir votre identifiant.'),
2 => $this->_('Identifiant inconnu.'), static::LOST_PASS_NOUSER => $this->_('Identifiant inconnu.'),
4 => $this->_("Votre mail n'est pas renseigné dans votre compte lecteur. Merci de vous adresser à la bibliothèque pour récupérer votre mot de passe ou bien le remplacer par un nouveau.")]; static::LOST_PASS_NOMAIL => $this->_("Votre mail n'est pas renseigné dans votre compte lecteur. Merci de vous adresser à la bibliothèque pour récupérer votre mot de passe ou bien le remplacer par un nouveau.")];
if (isset($messages[$error_code]))
return $messages[$error_code]; return isset($messages[$error_code])
return ''; ? $messages[$error_code]
: '';
} }
...@@ -84,13 +89,13 @@ class AuthController extends ZendAfi_Controller_Action { ...@@ -84,13 +89,13 @@ class AuthController extends ZendAfi_Controller_Action {
$password = $f->filter($this->_request->getPost('password')); $password = $f->filter($this->_request->getPost('password'));
if (empty($username)) if (empty($username))
return $this->view->_('Entrez votre identifiant S.V.P.'); return $this->_('Entrez votre identifiant S.V.P.');
if (empty($password)) if (empty($password))
return $this->view->_('Entrez votre mot de passe S.V.P.'); return $this->_('Entrez votre mot de passe S.V.P.');
if (!ZendAfi_Auth::getInstance()->authenticateLoginPassword($username, $password)) if (!ZendAfi_Auth::getInstance()->authenticateLoginPassword($username, $password))
return $this->view->_('Identifiant ou mot de passe incorrect.'); return $this->_('Identifiant ou mot de passe incorrect.');
$user = Class_Users::getIdentity(); $user = Class_Users::getIdentity();
$this->_helper->trackEvent('authentification', 'connexion', 'utilisateur', $user->getId()); $this->_helper->trackEvent('authentification', 'connexion', 'utilisateur', $user->getId());
...@@ -296,25 +301,54 @@ class AuthController extends ZendAfi_Controller_Action { ...@@ -296,25 +301,54 @@ class AuthController extends ZendAfi_Controller_Action {
public function lostpassAction() { public function lostpassAction() {
$preferences = $this->_loginPrefFromWidgetOrModule(); $preferences = $this->_loginPrefFromWidgetOrModule();
$options = ['username_label' => $preferences["identifiant"],
'username_placeholder' => $preferences["identifiant_exemple"]];
$this->view->form = ZendAfi_Form_LostPassword::newWithOptions(['username_label' => $preferences["identifiant"], $form = ZendAfi_Form_LostPassword::newWithOptions($options)
'username_placeholder' => $preferences["identifiant_exemple"]])
->setAction($this->view->url(['module' => 'opac', ->setAction($this->view->url(['module' => 'opac',
'controller' => 'auth', 'controller' => 'auth',
'action' => 'lostpass'], null , true)); 'action' => 'lostpass'], null , true));
$this->view->form = $form;
if(!$this->_request->isPost()) if(!$this->_request->isPost()
|| !$form->isValid($this->_request->getPost()))
return; return;
$user = ZendAfi_Filters_Post::filterStatic($this->_request->getPost('lost_username')); $login = ZendAfi_Filters_Post::filterStatic($this->_request->getPost('lost_username'));
$classe_user = new Class_Users(); $this->view->username = $login;
$ret = $classe_user->lostpass($user);
$this->view->message = $this->getErrorMessages($ret["error"]); $user = Class_Users::findFirstValidOrNotBy(['login' => $login]);
$this->view->message_mail = isset($ret["message_mail"]) $this->view->message_mail = (new Class_User_LostPass($user))->send();
? $ret["message_mail"] }
: '';
$this->view->username=$user;
public function resetPasswordAction() {
$this->view->title = $this->_('Réinitialisation du mot de passe');
if (!$user = Class_Users::find((int)$this->_getParam('id')))
return $this->view->message = $this->_('Utilisateur inconnu');
$lostpass = new Class_User_LostPass($user);
$created = $this->_getParam('created');
if ($this->_getParam('token', '') != $lostpass->tokenAt($created))
return $this->view->message = $this->_('Jeton de réinitialisation invalide');
if ($lostpass->tokenHasExpiredFrom($created))
return $this->view->message = $this->_('Jeton de réinitialisation expiré');
$this->view->form = $form = new ZendAfi_Form_ResetPassword();
$form->setAction($this->view->url());
if (!$this->_request->isPost()
|| !$form->isValid($this->_request->getPost()))
return;
$user->setPassword($form->getValue('new_pass'))
->save();
$this->_helper->notify($this->_('Votre mot de passe a été réinitialisé, vous pouvez vous connecter.'));
$this->_redirect('/auth/login');
} }
......
<?php
$this->openBoite($this->title);
if ($this->message)
echo $this->tag('div', $this->message);
if ($this->form)
echo $this->renderForm($this->form);
$this->closeBoite();
...@@ -37,7 +37,8 @@ function cosmo_auth($user, $passe, $cfg) { ...@@ -37,7 +37,8 @@ function cosmo_auth($user, $passe, $cfg) {
if ($user == getVariable('catalog_login') && $passe == getVariable('catalog_pwd')) if ($user == getVariable('catalog_login') && $passe == getVariable('catalog_pwd'))
return 'catalogueur'; return 'catalogueur';
if (($bokeh_user = Class_Users::findFirstBy(['login' => $user, 'password' => $passe])) if (($bokeh_user = Class_Users::findFirstBy(['login' => $user]))
&& $bokeh_user->verifyPassword($passe)
&& $bokeh_user->isSuperAdmin()) && $bokeh_user->isSuperAdmin())
return 'admin_systeme'; return 'admin_systeme';
} }
......
<?php
(new Class_Migration_HashAdminPasswords)->run();
\ No newline at end of file
<?php
/**
* Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
*
* BOKEH is free software; you can redistribute it and/or modify
* it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
* the Free Software Foundation.
*
* There are special exceptions to the terms and conditions of the AGPL as it
* is applied to this software (see README file).
*
* BOKEH 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
* along with BOKEH; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
class Class_Crypt {
/** @see http://php.net/manual/en/function.crypt.php CRYPT_BLOWFISH section */
const BLOWFISH_PATTERN = '/^\$2[axy]\$[0-9]{2}\$/';
public function isBlowFish($value) {
return 1 === preg_match(static::BLOWFISH_PATTERN, $value);
}
public function blowFishHashOf($value) {
return password_hash($value, PASSWORD_BCRYPT);
}
}
...@@ -16,29 +16,22 @@ ...@@ -16,29 +16,22 @@
* *
* You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
* along with BOKEH; if not, write to the Free Software * along with BOKEH; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
////////////////////////////////////////////////////////////////////////////////////////
// OPAC3 : ENVOI DE MAILS
///////////////////////////////////////////////////////////////////////////////////////
class Class_Mail class Class_Mail {
{ use Trait_Translator;
private $params_ok=false; // Flag de controle si un mail peut etre envoyé private $params_ok=false; // Flag de controle si un mail peut etre envoyé
private $mail_from; // Header from private $mail_from; // Header from
private $_translate;
//---------------------------------------------------------------------------------
// constructeur : init des parametres
//---------------------------------------------------------------------------------
public function __construct() { public function __construct() {
$this->_translate = Zend_Registry::get('translate');
$this->params_ok = false; $this->params_ok = false;
$this->mail_from = Class_Profil::getCurrentProfil()->getMailSiteOrPortail(); $this->mail_from = Class_Profil::getCurrentProfil()->getMailSiteOrPortail();
if (!$this->mail_from) if (!$this->mail_from)
$this->mail_from = Class_CosmoVar::get('mail_admin'); $this->mail_from = Class_CosmoVar::get('mail_admin');
if ($this->isMailValid($this->mail_from)) { if ($this->isMailValid($this->mail_from)) {
ini_set('sendmail_from', $this->mail_from); ini_set('sendmail_from', $this->mail_from);
$this->params_ok=true; $this->params_ok=true;
...@@ -46,12 +39,7 @@ class Class_Mail ...@@ -46,12 +39,7 @@ class Class_Mail
} }
public function _translate($message) { public function mail($destinataire, $sujet, $body) {
return $this->_translate->_($message);
}
public function mail($destinataire, $sujet, $body, $headers) {
$mail = new ZendAfi_Mail('utf8'); $mail = new ZendAfi_Mail('utf8');
$mail $mail
->setSubject($sujet) ->setSubject($sujet)
...@@ -67,21 +55,21 @@ class Class_Mail ...@@ -67,21 +55,21 @@ class Class_Mail
} }
} }
//---------------------------------------------------------------------------------
// Envoi de mail public function sendMail($sujet, $body, $destinataire, $data=false) {
//---------------------------------------------------------------------------------
public function sendMail($sujet,$body,$destinataire,$data=false)
{
$error_message = sprintf('%s <br/> %s', $error_message = sprintf('%s <br/> %s',
$this->_translate("Les paramètres d'envoi de mails du portail sont incomplets."), $this->_("Les paramètres d'envoi de mails du portail sont incomplets."),
$this->_translate("Merci de le signaler aux responsables de la bibliothèque.")); $this->_("Merci de le signaler aux responsables de la bibliothèque."));
if(!$this->params_ok or !trim($body))
return $error_message;
// Controle des parametres
if(!trim($destinataire)) if(!trim($destinataire))
return $this->_translate->_("Adresse du destinataire absente."); return $this->_("L'Adresse du destinataire est absente.");
if (!$this->isMailValid($destinataire))
return $this->_("L'adresse e-mail du destinataire est incorrecte.");
if($this->params_ok==false or !trim($body))
return $error_message;
// Fusion // Fusion
if($data) if($data)
...@@ -93,20 +81,12 @@ class Class_Mail ...@@ -93,20 +81,12 @@ class Class_Mail
} }
} }
$body = wordwrap($body, 60); $body = wordwrap($body, 60);
// Envoi du mail
$ret=$this->getHeaders($destinataire);
if (array_isset("erreur", $ret))
return $ret["erreur"];
else
$headers=$ret["headers"];
$statut = $this->mail($destinataire, $sujet, $body, $headers);
if($statut == false) $statut = $this->mail($destinataire, $sujet, $body);
return $error_message;
return ""; return (true === $statut)
? ''
: $error_message;
} }
...@@ -114,27 +94,4 @@ class Class_Mail ...@@ -114,27 +94,4 @@ class Class_Mail
$validator = new Zend_Validate_EmailAddress(); $validator = new Zend_Validate_EmailAddress();
return $validator->isValid($mail); return $validator->isValid($mail);
} }
}
\ No newline at end of file
//---------------------------------------------------------------------------------
// Constitution des headers
//---------------------------------------------------------------------------------
protected function getHeaders($destinataire)
{
$ret = array('headers' => '');
if (!$this->isMailValid($destinataire)) {
$ret["erreur"]= "L'adresse e-mail du destinataire est incorrecte.";
return $ret;
}
// Headers
$headers = 'MIME-Version: 1.0' . "\r\n";
$headers .= 'Content-type: text/html; charset=UTF-8' . "\r\n";
$headers .= 'To: '.$destinataire. "\r\n";
$headers .= 'From: '.$this->mail_from . "\r\n";
$ret["headers"]=$headers;
return $ret;
}
}
?>
\ No newline at end of file
<?php
/**
* Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
*
* BOKEH is free software; you can redistribute it and/or modify
* it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
* the Free Software Foundation.
*
* There are special exceptions to the terms and conditions of the AGPL as it
* is applied to this software (see README file).
*
* BOKEH 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
* along with BOKEH; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
class Class_Migration_HashAdminPasswords {
public function run() {
foreach(Class_Users::findAllBy(['where' => 'role_level > 2']) as $user)
$user->save();
}
}
<?php
/**
* Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
*
* BOKEH is free software; you can redistribute it and/or modify
* it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
* the Free Software Foundation.
*
* There are special exceptions to the terms and conditions of the AGPL as it
* is applied to this software (see README file).
*
* BOKEH 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
* along with BOKEH; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
class Class_User_LostPass {
use Trait_TimeSource;
const MAX_MINUTES = 30;
const TOKEN_SEPARATOR = '@';
const TOKEN_DATE_FORMAT = 'YmdHis';
protected $_user;
public function __construct($user) {
$this->_user = $user;
}
public function send() {
$strategy = $this->_user->isAbonne()
? new Class_User_LostPassPatron($this)
: new Class_User_LostPassLocal($this);
return $strategy->sendTo($this->_user);
}
public function tokenAt($date) {
$parts = [$this->_user->getId(), $this->_user->getLogin(), $date];
return sha1(implode(static::TOKEN_SEPARATOR, $parts));
}
public function tokenHasExpiredFrom($date) {
$from = DateTime::createFromFormat(static::TOKEN_DATE_FORMAT, $date);
$now = new DateTime($this->getCurrentDateTime());
$from->add(new DateInterval('PT' . static::MAX_MINUTES . 'M'));
return $from < $now;
}
}
abstract class Class_User_LostPassSender {
use Trait_Translator;
protected $_lostpass;
public function __construct($lostpass) {
$this->_lostpass = $lostpass;
}
public function sendTo($user) {
$error = (new Class_Mail())
->sendMail(Class_Profil::getCurrentProfil()->getTitreSite(),
$this->_contentFor($user),
$user->getMail());
return $error
? '<p class="error">' . $error . '</p>'
: $this->_successMessage();
}
protected function _contentFor($user) {
return '';
}
protected function _successMessage() {
return '';
}
}
class Class_User_LostPassPatron extends Class_User_LostPassSender {
protected function _contentFor($user) {
return
sprintf("%s\n\n",
$this->_('Vous avez fait une demande de mot de passe sur le portail.'))
. $this->_("Votre identifiant : %s\n", $user->getLogin())
. $this->_("Votre mot de passe : %s\n", $user->getPassword())
. sprintf("%s\n\n", $this->_('Bonne navigation sur le portail'))
;
}
protected function _successMessage() {
return $this->_('Un mail vient de vous être envoyé avec vos paramètres de connexion.');
}
}
class Class_User_LostPassLocal extends Class_User_LostPassSender{
use Trait_TimeSource;
protected function _contentFor($user) {
$created_at = $this->getCurrentTime();
$created_at_part = date(Class_User_LostPass::TOKEN_DATE_FORMAT, $created_at);
$token = $this->_lostpass->tokenAt($created_at_part);
return
sprintf("%s\n\n",
$this->_('Vous avez fait une demande de réinitialisation de votre mot de passe sur le portail.'))
. $this->_("Votre lien de réinitialisation : %s\n",
Class_Url::absolute(['module' => 'opac',
'controller' => 'auth',
'action' => 'reset-password',
'id' => $user->getId(),
'token' => $token,
'created' => $created_at_part], null, true))
. $this->_("ATTENTION : ce lien créé à %s est valide pendant %s minutes\n",
date('H:i', $created_at),
Class_User_LostPass::MAX_MINUTES)
. sprintf("%s\n\n", $this->_('Bonne navigation sur le portail'));
}
protected function _successMessage() {