diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d4a2e5fc0ff5ff011883dd9f3f2f9ac032e6ca3..18532dfff7f828c300072de3b4db691cf1ba5dfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,15 @@ - Authentification locale activable/désactivable dans la config. - Clé de config `unicaen-auth` > `local` > `enabled`. -- Fonctionnalité "Mot de passe oublié" pour l'authentification locale. +- Ajout de la fonctionnalité "Mot de passe oublié" pour l'authentification locale. - Principe: un lien contenant un token est envoyé par mail à l'utilisateur. - NB: Le username de l'utilisateur doit être une adresse électronique. - - NB: nécessitéd de créer une nouvelle colonne dans la table des utilisateurs, + - NB: nécessité de créer une nouvelle colonne dans la table des utilisateurs, cf. [répertoire data](./data). + +## 1.3.1 - 25/01/2019 + +- Fonctionnalité "Mot de passe oublié" : + - Correction: l'utilisateur n'était pas recherché par son username! + - Ajout d'un validateur sur le formulaire de saisie de l'adresse électronique. + - Vérification que le compte utilisateur est bien local. diff --git a/src/UnicaenAuth/Controller/AuthController.php b/src/UnicaenAuth/Controller/AuthController.php index 33747a8659674b6081988871cdb2c7021c819d36..643d6d1bb9ab909715bbb39e86684c779f5ef331 100644 --- a/src/UnicaenAuth/Controller/AuthController.php +++ b/src/UnicaenAuth/Controller/AuthController.php @@ -2,7 +2,7 @@ namespace UnicaenAuth\Controller; -use Doctrine\ORM\NoResultException; +use DomainException; use UnicaenApp\Controller\Plugin\AppInfos; use UnicaenApp\Controller\Plugin\Mail; use UnicaenApp\Exception\RuntimeException; @@ -135,24 +135,42 @@ class AuthController extends AbstractActionController $form->setData($data); if ($form->isValid()) { $email = $data['email']; - $this->processPasswordResetRequest($email); - - $view->setVariable('email', $email); - $view->setTemplate('unicaen-auth/auth/request-password-reset-success'); + try { + $this->processPasswordResetRequest($email); + + $view->setVariable('email', $email); + $view->setTemplate('unicaen-auth/auth/request-password-reset-success'); + } catch (DomainException $de) { + // affichage de l'erreur comme une erreur de validation + $form->get('email')->setMessages([$de->getMessage()]); + } } } return $view; } + /** + * @param string $email + */ private function processPasswordResetRequest($email) { - try { - $token = $this->userService->updateUserPasswordResetToken($email); - } catch (NoResultException $nre) { - // aucun utilisateur trouvé tel que username = $email + // Recherche de l'utilisateur ayant pour *username* (login) l'email spécifié + $user = $this->userService->getUserMapper()->findOneByUsername($email); + + if ($user === null) { + // Aucun utilisateur trouvé ayant l'email spécifié : + // on ne fait rien mais on ne le signale pas sinon le formulaire permettrait + // de tester si des emails potentiellement valides existent dans la base. return; } + if (! $user->isLocal()) { + // L'email spécifié appartient à un utilisateur non local : on signale l'impossibilité de changer le mdp. + throw new DomainException("Le changement de mot de passe n'est pas possible pour cet utilisateur."); + } + + // génération/enregistrement d'un token + $token = $this->userService->updateUserPasswordResetToken($user); // envoi du mail contenant le lien de changement de mdp $app = $this->appInfos()->getNom(); diff --git a/src/UnicaenAuth/Entity/Db/AbstractUser.php b/src/UnicaenAuth/Entity/Db/AbstractUser.php index a8adfa17b9254fb4ee540ce1ac35af59a872889d..9b0b006e82f09f43f49df431720b88f9b85a4a38 100644 --- a/src/UnicaenAuth/Entity/Db/AbstractUser.php +++ b/src/UnicaenAuth/Entity/Db/AbstractUser.php @@ -14,6 +14,9 @@ use Doctrine\ORM\Mapping as ORM; */ abstract class AbstractUser implements UserInterface, ProviderInterface { + const PASSWORD_LDAP = 'ldap'; + const PASSWORD_SHIB = 'shib'; + /** * @var int * @ORM\Id @@ -246,6 +249,22 @@ abstract class AbstractUser implements UserInterface, ProviderInterface $this->roles->add($role); } + /** + * Retourne true si cet utilisateur est local. + * + * Un utilisateur est local s'il ne résulte pas d'une authentification LDAP ou Shibboleth. + * Son mot de passe est chiffré dans la table des utilisateurs. + * + * @return bool + */ + public function isLocal() + { + return ! in_array($this->getPassword(), [ + AbstractUser::PASSWORD_LDAP, + AbstractUser::PASSWORD_SHIB, + ]); + } + /** * * @return string diff --git a/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php b/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php index 3312082344781febd0e56333e80c4a2aaf829613..d444b1c4c27e91895e3cecddda93d849660ee73c 100644 --- a/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php +++ b/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php @@ -2,6 +2,7 @@ namespace UnicaenAuth\Entity\Shibboleth; +use UnicaenAuth\Entity\Db\AbstractUser; use ZfcUser\Entity\UserInterface; class ShibUser implements UserInterface @@ -200,7 +201,7 @@ class ShibUser implements UserInterface */ public function getPassword() { - return 'shib'; + return AbstractUser::PASSWORD_SHIB; } /** diff --git a/src/UnicaenAuth/Service/User.php b/src/UnicaenAuth/Service/User.php index d566f10ef17a53edce6af21c620630db19e2db66..1d72db1f6794eb544d817870fe0ffa4b30e6e067 100644 --- a/src/UnicaenAuth/Service/User.php +++ b/src/UnicaenAuth/Service/User.php @@ -3,11 +3,11 @@ namespace UnicaenAuth\Service; use DateTime; -use Doctrine\ORM\NoResultException; use Ramsey\Uuid\Uuid; use UnicaenApp\Entity\Ldap\People; use UnicaenApp\Exception\RuntimeException; use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper; +use UnicaenAuth\Entity\Db\AbstractUser; use UnicaenAuth\Entity\Shibboleth\ShibUser; use UnicaenAuth\Event\UserAuthenticatedEvent; use UnicaenAuth\Options\ModuleOptions; @@ -22,11 +22,11 @@ use Zend\Form\Form; use Zend\InputFilter\Input; use Zend\ServiceManager\ServiceLocatorAwareInterface; use Zend\ServiceManager\ServiceLocatorAwareTrait; +use Zend\Validator\EmailAddress; use Zend\Validator\Identical; use ZfcUser\Entity\UserInterface; use ZfcUser\Options\AuthenticationOptionsInterface; use ZfcUser\Options\ModuleOptions as ZfcUserModuleOptions; -use UnicaenAuth\Entity\Db\AbstractUser; /** * Service traitant des utilisateurs locaux de l'application. @@ -81,13 +81,13 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface case $userData instanceof People: $username = $userData->getData($this->getOptions()->getLdapUsername()); $email = $userData->getMail(); - $password = 'ldap'; + $password = AbstractUser::PASSWORD_LDAP; $state = in_array('deactivated', ldap_explode_dn($userData->getDn(), 1)) ? 0 : 1; break; case $userData instanceof ShibUser: $username = $userData->getUsername(); $email = $userData->getEmail(); - $password = 'shib'; + $password = AbstractUser::PASSWORD_SHIB; $state = 1; break; default: @@ -260,7 +260,11 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface $form->add((new Text('email'))->setLabel("Adresse électronique :")); $form->add((new Csrf('csrf'))); $form->add((new Submit('submit'))->setLabel("Envoyer le lien")); - $form->getInputFilter()->add((new Input('email'))->setRequired(true)); + + $emailInput = new Input('email'); + $emailInput->setRequired(true); + $emailInput->getValidatorChain()->attach(new EmailAddress()); + $form->getInputFilter()->add($emailInput); return $form; } @@ -287,22 +291,13 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface } /** - * Si l'utilisateur dont le username égale l'email spécifié est trouvé, - * génère puis enregistre le token permettant d'autoriser cet utilisateur à changer son mot de passe. + * Génère puis enregistre le token permettant d'autoriser un utilisateur à changer son mot de passe. * - * @param string $email Email de l'utilisateur qui doit être aussi son username + * @param AbstractUser $user Utilisateur concerné * @return string|null Token généré - * @throws NoResultException Aucun utilisateur trouvé avec cet email */ - public function updateUserPasswordResetToken($email) + public function updateUserPasswordResetToken(AbstractUser $user) { - // Si l'email est inconnu, on ne fera rien mais on ne le signale pas sinon le formulaire permettrait - // de tester si des emails potentiellement valides existent dans la base. - $user = $this->getUserMapper()->findByEmail($email); /** @var User $user */ - if ($user === null) { - throw new NoResultException(); - } - // Génération du token. $token = $this->generatePasswordResetToken(); @@ -344,8 +339,14 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface */ public function generatePasswordResetToken() { + try { + $uuid = Uuid::uuid4(); + } catch (\Exception $e) { + throw new RuntimeException("Erreur rencontrée lors de la génération du UUID.", null, $e); + } + // NB: la date de fin de vie du token est concaténée à la fin. - $token = Uuid::uuid4()->toString() . self::PASSWORD_RESET_TOKEN_SEP . date('YmdHis', time() + 3600*24); + $token = $uuid->toString() . self::PASSWORD_RESET_TOKEN_SEP . date('YmdHis', time() + 3600 * 24); // durée de vie = 24h return $token; diff --git a/src/UnicaenAuth/Service/UserMapper.php b/src/UnicaenAuth/Service/UserMapper.php index b41c49b66dc40ccdeac7cc4b36fee6767d07db44..88c6b6ade37b18e82e2bd921ef7ec4b81602f2b2 100644 --- a/src/UnicaenAuth/Service/UserMapper.php +++ b/src/UnicaenAuth/Service/UserMapper.php @@ -2,18 +2,32 @@ namespace UnicaenAuth\Service; -use UnicaenAuth\Entity\Db\User; +use UnicaenAuth\Entity\Db\AbstractUser; use ZfcUserDoctrineORM\Mapper\User as ZfcUserDoctrineORMUserMapper; class UserMapper extends ZfcUserDoctrineORMUserMapper { + /** + * Recherche un utilisateur par son username (identifiant de connexion). + * + * @param string $username + * @return AbstractUser|null + */ + public function findOneByUsername($username) + { + /** @var AbstractUser $user */ + $user = $this->em->getRepository($this->options->getUserEntityClass())->findOneBy(['username' => $username]); + + return $user; + } + /** * @param string $token - * @return User + * @return AbstractUser */ public function findOneByPasswordResetToken($token) { - /** @var User $user */ + /** @var AbstractUser $user */ $user = $this->em->getRepository($this->options->getUserEntityClass())->findOneBy(['passwordResetToken' => $token]); return $user; diff --git a/view/unicaen-auth/auth/request-password-reset-form.phtml b/view/unicaen-auth/auth/request-password-reset-form.phtml index c297a4de736e83b809ac79f31fca8ff00cd44747..61e8802100efd7029f08724fa3a3bc9a95c811d9 100644 --- a/view/unicaen-auth/auth/request-password-reset-form.phtml +++ b/view/unicaen-auth/auth/request-password-reset-form.phtml @@ -15,8 +15,8 @@ use Zend\Form\Form; </h2> <p class="lead"> - Un lien permettant de changer votre mot de passe vous sera envoyé à l'adresse que vous renseignerez ci-dessous.<br> - <strong>NB:</strong> Cette adresse doit correspondre au compte que vous utilisez pour vous connecter à l'application. + Cette page permet d'initier le changement du mot de passe associé à votre compte local de connexion à l'application.<br> + <strong>NB:</strong> Cela n'est possible que pour les utilisateurs se connectant avec leur adresse électronique.<br> </p> <div class="col-sm-4"> @@ -30,6 +30,7 @@ use Zend\Form\Form; echo $this->formInput($email); echo $this->formElementErrors($email, ['class' => 'text-danger']); ?> + <span class="help-block">Un lien à cliquer vous sera envoyé à cette adresse électronique.</span> </p> <?php echo $this->formInput($form->get('csrf')); ?>