You need to sign in or sign up before continuing.
Select Git revision
AbstractDb.php
Forked from
lib / unicaen / authentification
Source project has a limited visibility.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
AuthController.php 9.05 KiB
<?php
namespace UnicaenAuth\Controller;
use DomainException;
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
{
use ShibServiceAwareTrait;
use UserServiceAwareTrait;
/**
* Cette action peut être appelée lorsque l'authentification Shibboleth est activée
* (unicaen-auth.shibboleth.enable === true).
*
* > Si la config Apache de Shibboleth est correcte, une requête à l'adresse correspondant à cette action
* (suite au clic sur le bouton "Authentification Shibboleth", typiquement)
* est détournée par Apache pour réaliser l'authentification Shibboleth.
* Ce n'est qu'une fois l'authentification réalisée avec succès que cette action est appelée.
*
* > Si la config Apache de Shibboleth est incorrecte ou absente (localhost par exemple), et que la simulation
* Shibboleth est activée dans la config (unicaen-auth.shibboleth.simulate), cette action est appelée et
* la simulation est enclenchée.
*
* @return Response|array
*/
public function shibbolethAction()
{
$operation = $this->params()->fromRoute('operation');
if ($operation === 'deconnexion') {
return $this->shibbolethLogout();
}
$redirectUrl = $this->params()->fromQuery('redirect', '/');
// enclenchement de la simulation shibboleth éventuellement activée dans la config
if ($simulate = $this->shibService->getShibbolethSimulate()) {
$this->setStoredAuthenticatedUsername($simulate['eppn']); // tout simplement!
return $this->redirect()->toUrl($redirectUrl);
}
$shibUser = $this->shibService->getAuthenticatedUser();
if ($shibUser === null) {
return []; // une page d'aide s'affichera si les données issues de Shibboleth attendues sont absentes
}
// arrivé ici, l'authentification shibboleth a été faite en bonne et due forme et a réussie.
$this->setStoredAuthenticatedUsername($shibUser->getUsername());
$this->userService->userAuthenticated($shibUser);
return $this->redirect()->toUrl($redirectUrl);
}
/**
* Déconnexion Shibboleth.
*
* @return array|Response
*/
private function shibbolethLogout()
{
// déconnexion applicative quoiqu'il arrive
$this->zfcUserAuthentication()->getAuthAdapter()->resetAdapters();
$this->zfcUserAuthentication()->getAuthAdapter()->logoutAdapters();
$this->zfcUserAuthentication()->getAuthService()->clearIdentity();
// déconnexion Shibboleth le cas échéant
if ($this->shibService->isShibbolethEnabled()) {
// désactivation de l'usurpation d'identité éventuelle
$this->shibService->deactivateUsurpation();
// URL par défaut vers laquelle on redirige après déconnexion : accueil
$homeUrl = $this->url()->fromRoute('home', [], ['force_canonical' => true]);
$returnAbsoluteUrl = $this->params()->fromQuery('return', $homeUrl);
return $this->redirect()->toUrl($this->shibService->getLogoutUrl($returnAbsoluteUrl));
} else {
return []; // une page d'aide s'affichera
}
}
/**
* @param string $username
*/
private function setStoredAuthenticatedUsername($username)
{
/** @var AuthenticationService $authService */
$authService = $this->getServiceLocator()->get('zfcuser_auth_service');
try {
$authService->getStorage()->write($username);
} catch (ExceptionInterface $e) {
throw new RuntimeException("Impossible d'écrire dans le storage");
}
}
/**
* @return Response|ViewModel
*/
public function requestPasswordResetAction()
{
$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'];
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)
{
// 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-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;
}
$view->setVariable('form', $form);
$view->setTemplate('unicaen-auth/auth/change-password-form');
return $view;
}
}