Loading CHANGELOG.md +9 −2 Original line number Diff line number Diff line Loading @@ -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. src/UnicaenAuth/Controller/AuthController.php +27 −9 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -135,24 +135,42 @@ class AuthController extends AbstractActionController $form->setData($data); if ($form->isValid()) { $email = $data['email']; 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(); Loading src/UnicaenAuth/Entity/Db/AbstractUser.php +19 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading src/UnicaenAuth/Entity/Shibboleth/ShibUser.php +2 −1 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ namespace UnicaenAuth\Entity\Shibboleth; use UnicaenAuth\Entity\Db\AbstractUser; use ZfcUser\Entity\UserInterface; class ShibUser implements UserInterface Loading Loading @@ -200,7 +201,7 @@ class ShibUser implements UserInterface */ public function getPassword() { return 'shib'; return AbstractUser::PASSWORD_SHIB; } /** Loading src/UnicaenAuth/Service/User.php +19 −18 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading Loading @@ -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: Loading Loading @@ -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; } Loading @@ -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(); Loading Loading @@ -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; Loading Loading
CHANGELOG.md +9 −2 Original line number Diff line number Diff line Loading @@ -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.
src/UnicaenAuth/Controller/AuthController.php +27 −9 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -135,24 +135,42 @@ class AuthController extends AbstractActionController $form->setData($data); if ($form->isValid()) { $email = $data['email']; 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(); Loading
src/UnicaenAuth/Entity/Db/AbstractUser.php +19 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading
src/UnicaenAuth/Entity/Shibboleth/ShibUser.php +2 −1 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ namespace UnicaenAuth\Entity\Shibboleth; use UnicaenAuth\Entity\Db\AbstractUser; use ZfcUser\Entity\UserInterface; class ShibUser implements UserInterface Loading Loading @@ -200,7 +201,7 @@ class ShibUser implements UserInterface */ public function getPassword() { return 'shib'; return AbstractUser::PASSWORD_SHIB; } /** Loading
src/UnicaenAuth/Service/User.php +19 −18 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading Loading @@ -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: Loading Loading @@ -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; } Loading @@ -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(); Loading Loading @@ -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; Loading