diff --git a/composer.json b/composer.json index 268376b2ae1fe029744bc10416b77211a47d0784..9d35afc132e58a9f71d9c20a9e3a308add2bc10c 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,8 @@ "unicaen/app": "^1.3", "zf-commons/zfc-user-doctrine-orm": ">=0.1", "jasig/phpcas": ">=1.3.3", - "bjyoungblood/bjy-authorize": ">=1.4" + "bjyoungblood/bjy-authorize": ">=1.4", + "ramsey/uuid": "^3.8" }, "require-dev": { "phpunit/PHPUnit": ">=3.7" diff --git a/config/module.config.php b/config/module.config.php index f6ea7d6c8e3b6e3822e1b8b0a737dad220c4b369..52902653c1ad69f1fddd88132421772a4f32f35e 100644 --- a/config/module.config.php +++ b/config/module.config.php @@ -5,6 +5,7 @@ use UnicaenAuth\Controller\AuthControllerFactory; use UnicaenAuth\Service\ShibService; use UnicaenAuth\Service\ShibServiceFactory; use UnicaenAuth\Service\UserContextFactory; +use UnicaenAuth\Service\UserMapperFactory; use UnicaenAuth\View\Helper\LdapConnectViewHelperFactory; use UnicaenAuth\View\Helper\LocalConnectViewHelperFactory; use UnicaenAuth\View\Helper\ShibConnectViewHelperFactory; @@ -154,6 +155,8 @@ return [ ['controller' => 'UnicaenAuth\Controller\Utilisateur', 'action' => 'selectionner-profil', 'roles' => []], ['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'shibboleth', 'roles' => []], + ['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'requestPasswordReset', 'roles' => []], + ['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'changePassword', 'roles' => []], ], ], ], @@ -221,6 +224,24 @@ return [ ], ], ], + 'requestPasswordReset' => [ + 'type' => 'Segment', + 'options' => [ + 'route' => '/request-password-reset', + 'defaults' => [ + 'action' => 'requestPasswordReset', + ], + ], + ], + 'changePassword' => [ + 'type' => 'Segment', + 'options' => [ + 'route' => '/change-password/:token', + 'defaults' => [ + 'action' => 'changePassword', + ], + ], + ], ], ], 'zfcuser' => [ @@ -428,6 +449,7 @@ return [ 'zfcuser_redirect_callback' => 'UnicaenAuth\Authentication\RedirectCallbackFactory', // substituion ShibService::class => ShibServiceFactory::class, 'UnicaenAuth\Service\UserContext' => UserContextFactory::class, + 'zfcuser_user_mapper' => UserMapperFactory::class, 'MouchardCompleterAuth' => 'UnicaenAuth\Mouchard\MouchardCompleterAuthFactory', ], 'shared' => [ diff --git a/data/schema_mysql.sql b/data/schema_mysql.sql index 7d22ce0e15a9f7b24352ed65f230a561ce4826fc..adc2cfcd2ace5d75276777d3b90181c9bab8a8f1 100644 --- a/data/schema_mysql.sql +++ b/data/schema_mysql.sql @@ -9,6 +9,10 @@ CREATE TABLE user ( UNIQUE INDEX `unique_username` (`username` ASC) ) ENGINE=InnoDB DEFAULT CHARACTER SET = utf8 COLLATE = utf8_unicode_ci; +alter table user add PASSWORD_RESET_TOKEN varchar2(256) default null; + +create unique index USER_PASSWORD_RESET_TOKEN_UN on user (PASSWORD_RESET_TOKEN); + CREATE TABLE IF NOT EXISTS `user_role` ( `id` INT(11) NOT NULL AUTO_INCREMENT, diff --git a/data/schema_oracle.sql b/data/schema_oracle.sql index 5410e1ed8bd04608e472bd535ff597f03839f592..4570eb35425a8f23b480c694e7ceac67e8d6d410 100644 --- a/data/schema_oracle.sql +++ b/data/schema_oracle.sql @@ -10,6 +10,10 @@ CREATE TABLE "USER" ); CREATE SEQUENCE "USER_ID_SEQ" ; +alter table "USER" add PASSWORD_RESET_TOKEN varchar2(256) default null; + +create unique index USER_PASSWORD_RESET_TOKEN_UN on "USER" (PASSWORD_RESET_TOKEN); + CREATE TABLE USER_ROLE ( "ID" NUMBER(*,0) NOT NULL ENABLE, diff --git a/data/schema_postgresql.sql b/data/schema_postgresql.sql index 59dd14b76d9f8f0122a638f4d893f45b4cf0e904..64f30c9254e5374ae73d57c6b26c1b543969f5a5 100644 --- a/data/schema_postgresql.sql +++ b/data/schema_postgresql.sql @@ -8,6 +8,10 @@ CREATE TABLE "user" ( ) ; CREATE UNIQUE INDEX user_username_unique ON "user" (username); +alter table "user" add PASSWORD_RESET_TOKEN varchar2(256) default null; + +create unique index USER_PASSWORD_RESET_TOKEN_UN on "user" (PASSWORD_RESET_TOKEN); + CREATE TABLE user_role ( id BIGSERIAL PRIMARY KEY, role_id VARCHAR(64) NOT NULL, diff --git a/src/UnicaenAuth/Controller/AuthController.php b/src/UnicaenAuth/Controller/AuthController.php index 986c50e5c7496601cb1e10338c7f02ae62cb31b9..33747a8659674b6081988871cdb2c7021c819d36 100644 --- a/src/UnicaenAuth/Controller/AuthController.php +++ b/src/UnicaenAuth/Controller/AuthController.php @@ -2,19 +2,27 @@ namespace UnicaenAuth\Controller; +use Doctrine\ORM\NoResultException; +use UnicaenApp\Controller\Plugin\AppInfos; +use UnicaenApp\Controller\Plugin\Mail; use UnicaenApp\Exception\RuntimeException; use UnicaenAuth\Service\Traits\ShibServiceAwareTrait; use UnicaenAuth\Service\Traits\UserServiceAwareTrait; use Zend\Authentication\AuthenticationService; use Zend\Authentication\Exception\ExceptionInterface; +use Zend\Http\Request; use Zend\Http\Response; use Zend\Mvc\Controller\AbstractActionController; +use Zend\View\Model\ViewModel; use ZfcUser\Controller\Plugin\ZfcUserAuthentication; /** * Classe ajoutée lors de l'implémentation de l'auth Shibboleth. * * @method ZfcUserAuthentication zfcUserAuthentication() + * @method AppInfos appInfos() + * @method Mail mail() + * * @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr> */ class AuthController extends AbstractActionController @@ -108,25 +116,113 @@ class AuthController extends AbstractActionController } } - public function sendPasswordRenewalMailAction() + /** + * @return Response|ViewModel + */ + public function requestPasswordResetAction() { - // lecture email fourni + $form = $this->userService->createResetPasswordEmailForm(); + + $view = new ViewModel(); + $view->setVariable('form', $form); + $view->setTemplate('unicaen-auth/auth/request-password-reset-form'); + + /** @var Request $request */ + $request = $this->getRequest(); + + if ($request->isPost()) { + $data = $request->getPost(); + $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'); + } + } - // tester email connu dans table utilisateur + return $view; + } - // générer / enregistrer token dans table utilisateur + private function processPasswordResetRequest($email) + { + try { + $token = $this->userService->updateUserPasswordResetToken($email); + } catch (NoResultException $nre) { + // aucun utilisateur trouvé tel que username = $email + return; + } - // envoyer mail avec lien/token + // envoi du mail contenant le lien de changement de mdp + $app = $this->appInfos()->getNom(); + $subject = "[$app] Demande de changement de mot de passe"; + $changePasswordUrl = $this->url()->fromRoute('auth/changePassword', ['token' => $token], ['force_canonical' => true]); + $body = <<<EOS +<p>Une demande de changement de mot de passe a été faite sur l'application $app.</p> +<p>Si vous n'en êtes pas l'auteur, vous pouvez ignorer ce message.</p> +<p>Cliquez sur le lien suivant pour accéder au formulaire de changement de votre mot de passe :<br><a href='$changePasswordUrl'>$changePasswordUrl</a></p> +EOS; + $message = $this->mail()->createNewMessage($body, $subject); + $message->setTo($email); + $this->mail()->send($message); } + /** + * @return array|ViewModel + */ public function changePasswordAction() { - // lecture token fourni + $token = $this->params()->fromRoute('token'); + $view = new ViewModel(); - // test token fourni existe dans table utilisateur + // recherche du token spécifié dans table utilisateur + $user = $this->userService->getUserMapper()->findOneByPasswordResetToken($token); + if ($user === null) { + // token inexistant + $view->setVariable('result', 'unknown_token'); + $view->setTemplate('unicaen-auth/auth/change-password-result'); + + return $view; + } + + $form = $this->userService->createPasswordChangeForm(); + + /** @var Request $request */ + $request = $this->getRequest(); + + if ($request->isPost()) { + $data = $request->getPost(); + $form->setData($data); + if ($form->isValid()) { + // màj password + $password = $this->params()->fromPost('password'); + $this->userService->updateUserPassword($user, $password); + + $view->setVariable('result', 'success'); + $view->setTemplate('unicaen-auth/auth/change-password-result'); + + // todo: faut-il déconnecter l'utilisateur (attention au logout shib différent) ? + + return $view; + } + } + + // test durée de vie du token + $date = $this->userService->extractDateFromResetPasswordToken($token); + if ($date < date_create()) { + // token expiré, on le raz + $this->userService->clearUserPasswordResetToken($user); + + $view->setVariable('result', 'dead_token'); + $view->setTemplate('unicaen-auth/auth/change-password-result'); + + return $view; + } - // afficher formulaire de màj + $view->setVariable('form', $form); + $view->setTemplate('unicaen-auth/auth/change-password-form'); - // màj password + return $view; } } \ No newline at end of file diff --git a/src/UnicaenAuth/Entity/Db/AbstractUser.php b/src/UnicaenAuth/Entity/Db/AbstractUser.php index e19aba664c5006252e0ad54fd581e13bf2ef83a0..a8adfa17b9254fb4ee540ce1ac35af59a872889d 100644 --- a/src/UnicaenAuth/Entity/Db/AbstractUser.php +++ b/src/UnicaenAuth/Entity/Db/AbstractUser.php @@ -52,6 +52,12 @@ abstract class AbstractUser implements UserInterface, ProviderInterface */ protected $state; + /** + * @var string + * @ORM\Column(type="string", length=256) + */ + protected $passwordResetToken; + /** * @var Collection * @ORM\ManyToMany(targetEntity="UnicaenAuth\Entity\Db\Role") @@ -202,6 +208,22 @@ abstract class AbstractUser implements UserInterface, ProviderInterface $this->state = $state; } + /** + * @return string + */ + public function getPasswordResetToken() + { + return $this->passwordResetToken; + } + + /** + * @param string $passwordResetToken + */ + public function setPasswordResetToken($passwordResetToken = null) + { + $this->passwordResetToken = $passwordResetToken; + } + /** * Get role. * diff --git a/src/UnicaenAuth/Service/User.php b/src/UnicaenAuth/Service/User.php index c1a483d607d4ef4e582df4bbe7c9a14c71768d4f..d566f10ef17a53edce6af21c620630db19e2db66 100644 --- a/src/UnicaenAuth/Service/User.php +++ b/src/UnicaenAuth/Service/User.php @@ -2,24 +2,34 @@ namespace UnicaenAuth\Service; -use UnicaenAuth\Event\UserAuthenticatedEvent; -use PDOException; +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\Shibboleth\ShibUser; +use UnicaenAuth\Event\UserAuthenticatedEvent; use UnicaenAuth\Options\ModuleOptions; +use Zend\Crypt\Password\Bcrypt; use Zend\EventManager\EventManagerAwareInterface; use Zend\EventManager\EventManagerInterface; +use Zend\Form\Element\Csrf; +use Zend\Form\Element\Password; +use Zend\Form\Element\Submit; +use Zend\Form\Element\Text; +use Zend\Form\Form; +use Zend\InputFilter\Input; use Zend\ServiceManager\ServiceLocatorAwareInterface; use Zend\ServiceManager\ServiceLocatorAwareTrait; +use Zend\Validator\Identical; use ZfcUser\Entity\UserInterface; use ZfcUser\Options\AuthenticationOptionsInterface; use ZfcUser\Options\ModuleOptions as ZfcUserModuleOptions; +use UnicaenAuth\Entity\Db\AbstractUser; /** - * Service d'enregistrement dans la table des utilisateurs de l'application - * de l'utilisateur authentifié avec succès. + * Service traitant des utilisateurs locaux de l'application. * * @see \UnicaenAuth\Authentication\Adapter\AbstractFactory * @author Unicaen @@ -45,15 +55,20 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface */ protected $zfcUserOptions; + /** + * @var UserMapper + */ + protected $userMapper; + /** * @var LdapPeopleMapper */ protected $ldapPeopleMapper; /** - * Save authenticated user in database from LDAP data. + * Save authenticated user in database from LDAP or Shibboleth data. * - * @param UserInterface|People $userData + * @param People|ShibUser $userData * @return bool */ public function userAuthenticated($userData) @@ -68,7 +83,6 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface $email = $userData->getMail(); $password = 'ldap'; $state = in_array('deactivated', ldap_explode_dn($userData->getDn(), 1)) ? 0 : 1; - break; case $userData instanceof ShibUser: $username = $userData->getUsername(); @@ -94,46 +108,42 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface throw new RuntimeException("Identité rencontrée inattendue."); } - // update/insert de l'utilisateur dans la table de l'appli - $mapper = $this->getServiceLocator()->get('zfcuser_user_mapper'); /* @var $mapper \ZfcUserDoctrineORM\Mapper\User */ - try { - /** @var UserInterface $entity */ - $entity = $mapper->findByUsername($username); - if (!$entity) { - $entityClass = $this->getZfcUserOptions()->getUserEntityClass(); - $entity = new $entityClass; - $entity->setUsername($username); - $method = 'insert'; - } - else { - $method = 'update'; - } - $entity->setEmail($email); - $entity->setDisplayName($userData->getDisplayName()); - $entity->setPassword($password); - $entity->setState($state); - - // pre-persist - $event = new UserAuthenticatedEvent(UserAuthenticatedEvent::PRE_PERSIST); - $this->triggerEvent($event, $entity, $userData); - - // persist - $mapper->$method($entity); - - // post-persist - $event = new UserAuthenticatedEvent(UserAuthenticatedEvent::POST_PERSIST); - $this->triggerEvent($event, $entity, $userData); + $mapper = $this->getUserMapper(); + + /** @var UserInterface $entity */ + $entity = $mapper->findByUsername($username); + if (!$entity) { + $entityClass = $this->getZfcUserOptions()->getUserEntityClass(); + $entity = new $entityClass; + $entity->setUsername($username); + $method = 'insert'; } - catch (PDOException $pdoe) { - throw new RuntimeException("Impossible d'enregistrer l'utilisateur authentifié dans la base de données.", null, $pdoe); + else { + $method = 'update'; } + $entity->setEmail($email); + $entity->setDisplayName($userData->getDisplayName()); + $entity->setPassword($password); + $entity->setState($state); + + // pre-persist + $event = new UserAuthenticatedEvent(UserAuthenticatedEvent::PRE_PERSIST); + $this->triggerEvent($event, $entity, $userData); + + // persist + $mapper->$method($entity); + + // post-persist + $event = new UserAuthenticatedEvent(UserAuthenticatedEvent::POST_PERSIST); + $this->triggerEvent($event, $entity, $userData); return true; } /** - * @param UserInterface $entity - * @param People|ShibUser $userData + * @param UserAuthenticatedEvent $event + * @param UserInterface $entity + * @param People|ShibUser $userData */ private function triggerEvent(UserAuthenticatedEvent $event, $entity, $userData) { @@ -148,6 +158,18 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface $this->getEventManager()->trigger($event); } + /** + * @return UserMapper + */ + public function getUserMapper() + { + if ($this->userMapper === null) { + $this->userMapper = $this->getServiceLocator()->get('zfcuser_user_mapper'); + } + + return $this->userMapper; + } + /** * Retrieve the event manager * @@ -222,4 +244,137 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface return $this->zfcUserOptions; } + + const PASSWORD_RESET_TOKEN_SEP = '-'; + const PASSWORD_RESET_TOKEN_DATE_FORMAT = 'YmdHis'; + + /** + * Construit le formulaire de saisie de l'adresse électronique à laquelle envoyer le lien de + * changement de mot de passe. + * + * @return Form + */ + public function createResetPasswordEmailForm() + { + $form = new Form(); + $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)); + + return $form; + } + + /** + * Construit le formulaire de saisie d'un nouveau mot de passe. + * + * @return Form + */ + public function createPasswordChangeForm() + { + $form = new Form(); + $form->add((new Password('password'))->setLabel("Nouveau mot de passe :")); + $form->add((new Password('passwordbis'))->setLabel("Confirmation du nouveau mot de passe :")); + $form->add((new Csrf('csrf'))); + $form->add((new Submit('submit'))->setLabel("Enregistrer")); + $form->getInputFilter()->add((new Input('password'))->setRequired(true)); + + $passwordbisInput = (new Input('passwordbis'))->setRequired(true); + $passwordbisInput->getValidatorChain()->attach(new Identical('password')); + $form->getInputFilter()->add($passwordbisInput); + + return $form; + } + + /** + * 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. + * + * @param string $email Email de l'utilisateur qui doit être aussi son username + * @return string|null Token généré + * @throws NoResultException Aucun utilisateur trouvé avec cet email + */ + public function updateUserPasswordResetToken($email) + { + // 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(); + + // Enregistrement du token dans la table des utilisateurs + $user->setPasswordResetToken($token); + $this->getUserMapper()->update($user); + + return $token; + } + + /** + * @param AbstractUser $user + */ + public function clearUserPasswordResetToken(AbstractUser $user) + { + $user->setPasswordResetToken(null); + $this->getUserMapper()->update($user); + } + + /** + * @param AbstractUser $user + * @param string $password + */ + public function updateUserPassword(AbstractUser $user, $password) + { + $bcrypt = new Bcrypt(); + $bcrypt->setCost($this->getZfcUserOptions()->getPasswordCost()); + $password = $bcrypt->create($password); + + $user->setPasswordResetToken(null); + $user->setPassword($password); + $this->getUserMapper()->update($user); + } + + /** + * Génération d'un token pour la demande de renouvellement de mot de passe. + * + * @return string + */ + public function generatePasswordResetToken() + { + // 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); + // durée de vie = 24h + + return $token; + } + + /** + * Génération du motif permettant de rechercher un token dans la table des utilisateurs. + * + * Rappel: la date de génération est concaténée à la fin. + * + * @param string $tokenUnderTest Le token recherché + * @return string + */ + public function generatePasswordResetTokenSearchPattern($tokenUnderTest) + { + return $tokenUnderTest . self::PASSWORD_RESET_TOKEN_SEP . '%'; + } + + /** + * Extrait la date de fin de vie d'un token. + * + * @param string $token + * @return DateTime + */ + public function extractDateFromResetPasswordToken($token) + { + $ts = ltrim(strrchr($token, $sep = self::PASSWORD_RESET_TOKEN_SEP), $sep); + $date = DateTime::createFromFormat(self::PASSWORD_RESET_TOKEN_DATE_FORMAT, $ts); + + return $date; + } } \ No newline at end of file diff --git a/src/UnicaenAuth/Service/UserMapper.php b/src/UnicaenAuth/Service/UserMapper.php new file mode 100644 index 0000000000000000000000000000000000000000..b41c49b66dc40ccdeac7cc4b36fee6767d07db44 --- /dev/null +++ b/src/UnicaenAuth/Service/UserMapper.php @@ -0,0 +1,21 @@ +<?php + +namespace UnicaenAuth\Service; + +use UnicaenAuth\Entity\Db\User; +use ZfcUserDoctrineORM\Mapper\User as ZfcUserDoctrineORMUserMapper; + +class UserMapper extends ZfcUserDoctrineORMUserMapper +{ + /** + * @param string $token + * @return User + */ + public function findOneByPasswordResetToken($token) + { + /** @var User $user */ + $user = $this->em->getRepository($this->options->getUserEntityClass())->findOneBy(['passwordResetToken' => $token]); + + return $user; + } +} \ No newline at end of file diff --git a/src/UnicaenAuth/Service/UserMapperFactory.php b/src/UnicaenAuth/Service/UserMapperFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..a6c83548907240466c147fc1a74228d62fa0d06d --- /dev/null +++ b/src/UnicaenAuth/Service/UserMapperFactory.php @@ -0,0 +1,27 @@ +<?php + +namespace UnicaenAuth\Service; + +use Doctrine\ORM\EntityManagerInterface; +use Zend\ServiceManager\FactoryInterface; +use Zend\ServiceManager\ServiceLocatorInterface; + +class UserMapperFactory implements FactoryInterface +{ + /** + * Create service + * + * @param ServiceLocatorInterface $serviceLocator + * @return UserMapper + */ + public function createService(ServiceLocatorInterface $serviceLocator) + { + /** @var EntityManagerInterface $em */ + $em = $serviceLocator->get('zfcuser_doctrine_em'); + + /** @var \ZfcUserDoctrineORM\Options\ModuleOptions $options */ + $options = $serviceLocator->get('zfcuser_module_options'); + + return new UserMapper($em, $options); + } +} diff --git a/src/UnicaenAuth/View/Helper/LocalConnectViewHelper.php b/src/UnicaenAuth/View/Helper/LocalConnectViewHelper.php index e6dd8047cccd3307831b2432458ddb0375e1369d..b2475ed8867a5b22a1ebeec08e66631f330fb5a9 100644 --- a/src/UnicaenAuth/View/Helper/LocalConnectViewHelper.php +++ b/src/UnicaenAuth/View/Helper/LocalConnectViewHelper.php @@ -67,7 +67,7 @@ class LocalConnectViewHelper extends AbstractHelper 'enabled' => $this->enabled, 'form' => $this->form, 'redirect' => null, - 'password_reset' => true, + 'passwordReset' => true, ]); } catch (\Exception $e) { return '<p>' . $e->getMessage() . '</p><p>' . $e->getTraceAsString() . '</p>'; diff --git a/src/UnicaenAuth/View/Helper/partial/connect.phtml b/src/UnicaenAuth/View/Helper/partial/connect.phtml index f216e21f7f2ade485af54a49b694623bf000d6ff..7d758b6ec3ffe9e5ca9c8119599b8742c1b3c0b2 100644 --- a/src/UnicaenAuth/View/Helper/partial/connect.phtml +++ b/src/UnicaenAuth/View/Helper/partial/connect.phtml @@ -7,6 +7,7 @@ use Zend\Form\Form; * @var Form $form * @var string $title * @var string $redirect + * @var bool $passwordReset */ ?> @@ -34,6 +35,9 @@ use Zend\Form\Form; echo $this->formLabel($identity); echo $this->formInput($identity); ?> + <?php if ($passwordReset): ?> + <a class="connect-credentials-lost" href="<?php echo $this->url('auth/requestPasswordReset') ?>">Mot de passe oublié</a> + <?php endif ?> </p> <?php if ($redirect): ?> <input type="hidden" name="redirect" value="<?php echo $redirect ?>"/> diff --git a/src/UnicaenAuth/View/Helper/partial/reset-password.phtml b/src/UnicaenAuth/View/Helper/partial/reset-password.phtml deleted file mode 100644 index ef61977c15f5810981ad18ef90e2595513dc3687..0000000000000000000000000000000000000000 --- a/src/UnicaenAuth/View/Helper/partial/reset-password.phtml +++ /dev/null @@ -1,39 +0,0 @@ -<?php - -use Zend\Form\Form; - -/** - * @var bool $enabled - * @var Form $form - * @var string $title - * @var string $redirect - */ -?> - -<?php if ($title): ?> - <h3 class="password-reset-title"> - <?php echo $title ?> - </h3> -<?php endif ?> - -<?php echo $this->form()->openTag($form) ?> - -<?php if (($errors = $this->formErrors($form))): ?> - <p><?php echo $errors ?></p> -<?php endif ?> -<p class="password-reset-identity"> - <?php - $identity = $form->get($name = 'identity')->setAttributes(['id' => $name, 'class' => 'form-control']); - echo $this->formLabel($identity); - echo $this->formInput($identity); - ?> -</p> -<?php if ($redirect): ?> - <input type="hidden" name="redirect" value="<?php echo $redirect ?>"/> -<?php endif ?> - -<p class="password-reset-submit"> - <?php echo $this->formButton($form->get('submit')->setAttribute('class', 'btn btn-primary')) ?> -</p> - -<?php echo $this->form()->closeTag() ?> diff --git a/view/unicaen-auth/auth/change-password-form.phtml b/view/unicaen-auth/auth/change-password-form.phtml new file mode 100644 index 0000000000000000000000000000000000000000..5054ec457d51c2519e12534d1345d91d26c78f7c --- /dev/null +++ b/view/unicaen-auth/auth/change-password-form.phtml @@ -0,0 +1,45 @@ +<?php + +/** + * @var string $status + */ +?> + +<h2 class="page-header password-change-title"> + Formulaire de changement de mot de passe +</h2> + +<p class="lead"> + Veuillez choisir puis confirmer votre nouveau mot de passe.<br> +</p> + +<div class="col-sm-4"> + + <?php echo $this->form()->openTag($form) ?> + + <p class="password-change-password"> + <?php + $password = $form->get($name = 'password')->setAttributes(['id' => $name, 'class' => 'form-control']); + echo $this->formLabel($password); + echo $this->formInput($password); + echo $this->formElementErrors($password, ['class' => 'text-danger']); + ?> + </p> + <p class="password-change-passwordbis"> + <?php + $passwordbis = $form->get($name = 'passwordbis')->setAttributes(['id' => $name, 'class' => 'form-control']); + echo $this->formLabel($passwordbis); + echo $this->formInput($passwordbis); + echo $this->formElementErrors($passwordbis, ['class' => 'text-danger']); + ?> + </p> + + <?php echo $this->formInput($form->get('csrf')); ?> + + <p class="password-change-submit"> + <?php echo $this->formButton($form->get('submit')->setAttribute('class', 'btn btn-primary')) ?> + </p> + + <?php echo $this->form()->closeTag() ?> + +</div> \ No newline at end of file diff --git a/view/unicaen-auth/auth/change-password-result.phtml b/view/unicaen-auth/auth/change-password-result.phtml new file mode 100644 index 0000000000000000000000000000000000000000..4de2e9fe3a3f67fefef2a877c1fd61850eb49bc2 --- /dev/null +++ b/view/unicaen-auth/auth/change-password-result.phtml @@ -0,0 +1,25 @@ +<?php + +/** + * @var string $result + */ +?> + +<h2 class="page-header password-reset-title"> + Changement de mot de passe +</h2> + +<?php if (in_array($result, ['unknown_token', 'dead_token'])): ?> + <p class="lead text-danger"> + <strong>Impossible!</strong> Le lien que vous avez utilisé n'est plus valide. <br> + </p> + <p> + Vous pouvez refaire une demande de changement de mot de passe <a href="<?php echo $this->url('auth/requestPasswordReset') ?>">ici</a>. + </p> +<?php else: ?> + <p class="lead text-success"> + <strong>C'est fait!</strong> Votre nouveau mot de passe a bien été enregistré. + </p> +<?php endif ?> + +<a class="btn btn-primary" href="<?php echo $this->url('home') ?>">Revenir à l'accueil</a> diff --git a/view/unicaen-auth/auth/request-password-reset-form.phtml b/view/unicaen-auth/auth/request-password-reset-form.phtml new file mode 100644 index 0000000000000000000000000000000000000000..c297a4de736e83b809ac79f31fca8ff00cd44747 --- /dev/null +++ b/view/unicaen-auth/auth/request-password-reset-form.phtml @@ -0,0 +1,43 @@ +<?php + +use Zend\Form\Form; + +/** + * @var bool $enabled + * @var Form $form + * @var string $title + * @var string $redirect + */ +?> + +<h2 class="page-header password-reset-title"> + Demande de changement de mot de passe +</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. +</p> + +<div class="col-sm-4"> + + <?php echo $this->form()->openTag($form) ?> + + <p class="password-reset-email"> + <?php + $email = $form->get($name = 'email')->setAttributes(['id' => $name, 'class' => 'form-control']); + echo $this->formLabel($email); + echo $this->formInput($email); + echo $this->formElementErrors($email, ['class' => 'text-danger']); + ?> + </p> + + <?php echo $this->formInput($form->get('csrf')); ?> + + <p class="password-reset-submit"> + <?php echo $this->formButton($form->get('submit')->setAttribute('class', 'btn btn-primary')) ?> + </p> + + <?php echo $this->form()->closeTag() ?> + +</div> \ No newline at end of file diff --git a/view/unicaen-auth/auth/request-password-reset-success.phtml b/view/unicaen-auth/auth/request-password-reset-success.phtml new file mode 100644 index 0000000000000000000000000000000000000000..34c79c06bb2696cf974affb6adf7cca1e05dd3c3 --- /dev/null +++ b/view/unicaen-auth/auth/request-password-reset-success.phtml @@ -0,0 +1,25 @@ +<?php + +use Zend\Form\Form; + +/** + * @var Form $form + * @var string $email + */ +?> + +<h2 class="page-header password-reset-title"> + Demande de changement de mot de passe +</h2> + +<p class="lead text-success"> + <strong>Relevez votre courier!</strong><br> + Un lien permettant de changer votre mot de passe a été envoyé à l'adresse <strong><?php echo $email ?></strong>... +</p> + +<p> + Si besoin, vous pouvez refaire une demande de changement en cliquant + <a href="<?php echo $this->url('auth/requestPasswordReset') ?>">ici</a>. +</p> + +<a class="btn btn-primary" href="<?php echo $this->url('home') ?>">Revenir à l'accueil</a>