Commit 5a77b202 authored by David Surville's avatar David Surville
Browse files

Déplacement de la gestion des comptes locaux et de la création/oubli du mot de...

Déplacement de la gestion des comptes locaux et de la création/oubli du mot de passe dans la librairie unicaen-utilisateur
parent e69b425f
Loading
Loading
Loading
Loading
+0 −20
Original line number Diff line number Diff line
@@ -366,8 +366,6 @@ return [
                ['controller' => 'UnicaenAuthentification\Controller\Auth', 'action' => 'authenticate', 'roles' => 'guest'],
                ['controller' => 'UnicaenAuthentification\Controller\Auth', 'action' => 'logout', 'roles' => 'guest'],
                ['controller' => 'UnicaenAuthentification\Controller\Auth', 'action' => 'shibboleth', 'roles' => 'guest'],
                ['controller' => 'UnicaenAuthentification\Controller\Auth', 'action' => 'requestPasswordReset', 'roles' => 'guest'],
                ['controller' => 'UnicaenAuthentification\Controller\Auth', 'action' => 'changePassword', 'roles' => 'guest'],
            ],
        ],
    ],
@@ -435,24 +433,6 @@ return [
                            ],
                        ],
                    ],
                    'requestPasswordReset' => [
                        'type' => 'Segment',
                        'options' => [
                            'route' => '/request-password-reset',
                            'defaults' => [
                                'action' => 'requestPasswordReset',
                            ],
                        ],
                    ],
                    'changePassword' => [
                        'type' => 'Segment',
                        'options' => [
                            'route' => '/change-password/:token',
                            'defaults' => [
                                'action' => 'changePassword',
                            ],
                        ],
                    ],
                ],
            ],
            'zfcuser' => [
+0 −128
Original line number Diff line number Diff line
@@ -347,132 +347,4 @@ class AuthController extends AbstractActionController

        return $this->redirect()->toUrl($redirectUrl);
    }

    /**
     * @return Response|ViewModel
     */
    public function requestPasswordResetAction()
    {
        $form = $this->userService->createResetPasswordEmailForm();

        $view = new ViewModel();
        $view->setVariable('form', $form);
        $view->setTemplate('unicaen-authentification/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'];
                try {
                    $this->processPasswordResetRequest($email);

                    $view->setVariable('email', $email);
                    $view->setTemplate('unicaen-authentification/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(string $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();
        $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()
    {
        $token = $this->params()->fromRoute('token');
        $view = new ViewModel();

        // 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-authentification/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-authentification/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-authentification/auth/change-password-result');

            return $view;
        }

        $view->setVariable('form', $form);
        $view->setTemplate('unicaen-authentification/auth/change-password-form');

        return $view;
    }
}
 No newline at end of file
+0 −134
Original line number Diff line number Diff line
@@ -232,138 +232,4 @@ class User implements 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"));

        $emailInput = new Input('email');
        $emailInput->setRequired(true);
        $emailInput->getValidatorChain()->attach(new EmailAddress());
        $form->getInputFilter()->add($emailInput);

        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;
    }

    /**
     * Génère puis enregistre le token permettant d'autoriser un utilisateur à changer son mot de passe.
     *
     * @param AbstractUser $user Utilisateur concerné
     * @return string|null Token généré
     */
    public function updateUserPasswordResetToken(AbstractUser $user)
    {
        // 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()
    {
        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->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
+1 −13
Original line number Diff line number Diff line
@@ -3,8 +3,8 @@
namespace UnicaenAuthentification\Service;

use Doctrine\ORM\EntityManagerInterface;
use UnicaenUtilisateur\Entity\Db\AbstractUser;
use UnicaenAuthentification\Options\ModuleOptions;
use UnicaenUtilisateur\Entity\Db\AbstractUser;
use ZfcUser\Entity\UserInterface as UserEntityInterface;
use ZfcUser\Mapper\UserInterface;

@@ -107,16 +107,4 @@ class UserMapper implements UserInterface

        return $user;
    }

    /**
     * @param string $token
     * @return AbstractUser
     */
    public function findOneByPasswordResetToken($token)
    {
        /** @var AbstractUser $user */
        $user = $this->em->getRepository($this->options->getUserEntityClass())->findOneBy(['passwordResetToken' => $token]);

        return $user;
    }
}
 No newline at end of file
+8 −4
Original line number Diff line number Diff line
@@ -28,10 +28,14 @@ use Laminas\Form\Form;

<?php if ($messages = $this->flashMessenger('zfcuser-login-form')): ?>
<?php foreach ($messages as $message): ?>
<div class="messenger alert alert-danger ">
    <button type="button" class="close" title="Fermer cette alerte" data-dismiss="alert">×</button>
    <span class="glyphicon glyphicon-warning-sign"></span>
        <?php echo $message ?> <br>
<div class="messenger alert alert-danger d-flex align-items-center alert-dismissible">
    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="flex-shrink-0 me-2" viewBox="0 0 512 512">
        <path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/>
    </svg>
    <div>
        <?php echo $message ?>
    </div>
    <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endforeach ?>
<?php endif ?>
Loading