diff --git a/config/module.config.php b/config/module.config.php
index b015ae6ff8c3e1e9babb0b2f034142249d61281e..da392d13a35ae4bd9abf2a4120ed8d0e3c598213 100644
--- a/config/module.config.php
+++ b/config/module.config.php
@@ -1,10 +1,13 @@
 <?php
 
+use UnicaenAuth\Authentication\Adapter\ShibSimulatorAdapter;
+use UnicaenAuth\Authentication\Storage\ShibSimulatorStorage;
 use UnicaenAuth\Controller\AuthControllerFactory;
 use UnicaenAuth\Service\ShibService;
 use UnicaenAuth\Service\ShibServiceFactory;
 use UnicaenAuth\Service\UserContextFactory;
 use UnicaenAuth\View\Helper\ShibConnectViewHelperFactory;
+use UnicaenAuth\View\Helper\UserUsurpationHelperFactory;
 
 $settings = [
     /**
@@ -373,6 +376,7 @@ return [
             'UnicaenAuth\Authentication\Storage\Db'   => 'UnicaenAuth\Authentication\Storage\Db',
             'UnicaenAuth\Authentication\Storage\Ldap' => 'UnicaenAuth\Authentication\Storage\Ldap',
             'UnicaenAuth\Authentication\Storage\Shib' => 'UnicaenAuth\Authentication\Storage\Shib',
+            'UnicaenAuth\Authentication\Storage\ShibSimulatorStorage' => ShibSimulatorStorage::class,
             'UnicaenAuth\View\RedirectionStrategy'    => 'UnicaenAuth\View\RedirectionStrategy',
             'UnicaenAuth\Service\User'                => 'UnicaenAuth\Service\User',
             'UnicaenAuth\Service\CategoriePrivilege'  => 'UnicaenAuth\Service\CategoriePrivilegeService',
@@ -432,6 +436,7 @@ return [
             'userInfo'                   => 'UnicaenAuth\View\Helper\UserInfoFactory',
             'userProfileSelect'          => 'UnicaenAuth\View\Helper\UserProfileSelectFactory',
             'userProfileSelectRadioItem' => 'UnicaenAuth\View\Helper\UserProfileSelectRadioItemFactory',
+            'userUsurpation'             => UserUsurpationHelperFactory::class,
             'shibConnect'                => ShibConnectViewHelperFactory::class,
         ],
         'invokables' => [
diff --git a/src/UnicaenAuth/Authentication/Adapter/Cas.php b/src/UnicaenAuth/Authentication/Adapter/Cas.php
index a495217f8b0e954c05ac07536ab4c46ad6a8ab31..562446f4a4c32f5030cc144c24d18f1a9e7f9f05 100644
--- a/src/UnicaenAuth/Authentication/Adapter/Cas.php
+++ b/src/UnicaenAuth/Authentication/Adapter/Cas.php
@@ -1,9 +1,11 @@
 <?php
+
 namespace UnicaenAuth\Authentication\Adapter;
 
 use phpCAS;
-use UnicaenApp\Exception;
+use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
 use UnicaenAuth\Options\ModuleOptions;
+use UnicaenAuth\Service\User;
 use Zend\Authentication\Exception\UnexpectedValueException;
 use Zend\Authentication\Result as AuthenticationResult;
 use Zend\EventManager\EventManager;
@@ -48,6 +50,11 @@ class Cas extends AbstractAdapter implements ServiceManagerAwareInterface, Event
      */
     protected $casClient;
 
+    /**
+     * @var LdapPeopleMapper
+     */
+    protected $ldapPeopleMapper;
+
     /**
      * Réalise l'authentification.
      *
@@ -94,7 +101,12 @@ class Cas extends AbstractAdapter implements ServiceManagerAwareInterface, Event
         $e->setCode(AuthenticationResult::SUCCESS)
           ->setMessages(['Authentication successful.']);
 
-        $this->getEventManager()->trigger('userAuthenticated', $e);
+        // recherche de l'individu dans l'annuaire LDAP (il existe forcément puisque l'auth CAS a réussi)
+        $ldapPeople = $this->getLdapPeopleMapper()->findOneByUsername($identity);
+
+        /* @var $userService User */
+        $userService = $this->getServiceManager()->get('unicaen-auth_user_service');
+        $userService->userAuthenticated($ldapPeople);
     }
 
     /**
@@ -186,6 +198,31 @@ class Cas extends AbstractAdapter implements ServiceManagerAwareInterface, Event
         return $this->options;
     }
 
+    /**
+     * get ldap people mapper
+     *
+     * @return LdapPeopleMapper
+     */
+    public function getLdapPeopleMapper()
+    {
+        if (null === $this->ldapPeopleMapper) {
+            $this->ldapPeopleMapper = $this->getServiceManager()->get('ldap_people_mapper');
+        }
+        return $this->ldapPeopleMapper;
+    }
+
+    /**
+     * set ldap people mapper
+     *
+     * @param LdapPeopleMapper $mapper
+     * @return self
+     */
+    public function setLdapPeopleMapper(LdapPeopleMapper $mapper)
+    {
+        $this->ldapPeopleMapper = $mapper;
+        return $this;
+    }
+
     /**
      * Get service manager
      *
diff --git a/src/UnicaenAuth/Authentication/Storage/ChainServiceFactory.php b/src/UnicaenAuth/Authentication/Storage/ChainServiceFactory.php
index 97a74366de1e3a118789247142351aa122f06a4f..c15c3fc83a3a013e8c5e871b94f4c7c877f160c7 100644
--- a/src/UnicaenAuth/Authentication/Storage/ChainServiceFactory.php
+++ b/src/UnicaenAuth/Authentication/Storage/ChainServiceFactory.php
@@ -12,11 +12,15 @@ use Zend\ServiceManager\ServiceLocatorInterface;
  */
 class ChainServiceFactory implements FactoryInterface
 {
-    protected $storages = [
+    private $mandatoryStorages = [
         200 => 'UnicaenAuth\Authentication\Storage\Ldap',
         100 => 'UnicaenAuth\Authentication\Storage\Db',
+        76  => 'UnicaenAuth\Authentication\Storage\ShibSimulatorStorage',
+        75  => 'UnicaenAuth\Authentication\Storage\Shib',
     ];
 
+    protected $storages = [];
+
     /**
      * Create service
      *
@@ -27,7 +31,10 @@ class ChainServiceFactory implements FactoryInterface
     {
         $chain = new Chain();
 
-        foreach ($this->storages as $priority => $name) {
+        $storages = $this->mandatoryStorages + $this->storages;
+        krsort($storages);
+
+        foreach ($storages as $priority => $name) {
             $storage = $serviceLocator->get($name);
             $chain->getEventManager()->attach('read', [$storage, 'read'], $priority);
             $chain->getEventManager()->attach('write', [$storage, 'write'], $priority);
diff --git a/src/UnicaenAuth/Authentication/Storage/Shib.php b/src/UnicaenAuth/Authentication/Storage/Shib.php
index 1bc25efa09bfdb760746b6a8e63aa2ec2fad23d1..8d8a81c9178bf4d255581983de3db12fd6adb290 100644
--- a/src/UnicaenAuth/Authentication/Storage/Shib.php
+++ b/src/UnicaenAuth/Authentication/Storage/Shib.php
@@ -3,7 +3,6 @@
 namespace UnicaenAuth\Authentication\Storage;
 
 use UnicaenAuth\Entity\Shibboleth\ShibUser;
-use UnicaenAuth\Options\ModuleOptions;
 use UnicaenAuth\Service\ShibService;
 use Zend\Authentication\Storage\Session;
 use Zend\Authentication\Storage\StorageInterface;
@@ -24,11 +23,6 @@ class Shib implements ChainableStorage, ServiceLocatorAwareInterface
      * @var StorageInterface
      */
     protected $storage;
-    
-    /**
-     * @var ModuleOptions
-     */
-    protected $options;
 
     /**
      * @var ShibUser
@@ -51,15 +45,30 @@ class Shib implements ChainableStorage, ServiceLocatorAwareInterface
      */
     public function read(ChainEvent $e)
     {
-        /** @var ShibService $shib */
-        $shib = $this->getServiceLocator()->get(ShibService::class);
-        $shibUser = $shib->getAuthenticatedUser();
+        $shibUser = $this->getAuthenticatedUser();
 
         $e->addContents('shib', $shibUser);
         
         return $shibUser;
     }
 
+    /**
+     * @return null|ShibUser
+     */
+    private function getAuthenticatedUser()
+    {
+        if (null !== $this->resolvedIdentity) {
+            return $this->resolvedIdentity;
+        }
+
+        /** @var ShibService $shib */
+        $shib = $this->getServiceLocator()->get(ShibService::class);
+
+        $this->resolvedIdentity = $shib->getAuthenticatedUser();
+
+        return $this->resolvedIdentity;
+    }
+
     /**
      * Writes $contents to storage
      *
diff --git a/src/UnicaenAuth/Authentication/Storage/ShibSimulatorStorage.php b/src/UnicaenAuth/Authentication/Storage/ShibSimulatorStorage.php
new file mode 100644
index 0000000000000000000000000000000000000000..ffb1eabc5d211b1f44c1d7b0d45c9fae3dfaee30
--- /dev/null
+++ b/src/UnicaenAuth/Authentication/Storage/ShibSimulatorStorage.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace UnicaenAuth\Authentication\Storage;
+
+use UnicaenAuth\Service\ShibService;
+use Zend\ServiceManager\ServiceLocatorAwareInterface;
+use Zend\ServiceManager\ServiceLocatorAwareTrait;
+
+/**
+ * Storage permettant de simuler un utilisateur authentifié via Shibboleth.
+ *
+ * @author Unicaen
+ */
+class ShibSimulatorStorage implements ChainableStorage, ServiceLocatorAwareInterface
+{
+    use ServiceLocatorAwareTrait;
+
+    /**
+     * @var ShibService
+     */
+    protected $shibService;
+
+    /**
+     * @var array
+     */
+    protected $shibbolethOptions;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function read(ChainEvent $e)
+    {
+        if (! $this->getShibService()->isShibbolethEnabled()) {
+            return;
+        }
+        if (! $this->getShibService()->isSimulationActive()) {
+            return;
+        }
+
+        $this->getShibService()->handleSimulation();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function write(ChainEvent $e)
+    {
+        // nop
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function clear(ChainEvent $e)
+    {
+        // nop
+    }
+
+    /**
+     * @return ShibService
+     */
+    private function getShibService()
+    {
+        if ($this->shibService === null) {
+            $this->shibService = $this->serviceLocator->get(ShibService::class);
+        }
+
+        return $this->shibService;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Controller/AuthController.php b/src/UnicaenAuth/Controller/AuthController.php
index d3b468c39e9fc06686bdbd0a30023854a27e1124..045136805e05183b05717d49000a2284a1e95182 100644
--- a/src/UnicaenAuth/Controller/AuthController.php
+++ b/src/UnicaenAuth/Controller/AuthController.php
@@ -23,6 +23,18 @@ class AuthController extends AbstractActionController
     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()
@@ -30,44 +42,69 @@ class AuthController extends AbstractActionController
         $operation = $this->params()->fromRoute('operation');
 
         if ($operation === 'deconnexion') {
-            // 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->isShibbolethEnable()) {
-                $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
-            }
+            return $this->shibbolethLogout();
         }
 
-        $shibUser = $this->shibService->getAuthenticatedUser();
+        $redirectUrl = $this->params()->fromQuery('redirect', '/');
 
-        if ($shibUser === null) {
-            return []; // une page d'aide s'affichera
+        // 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);
         }
 
-        /** @var AuthenticationService $authService */
-        $authService = $this->getServiceLocator()->get('zfcuser_auth_service');
-        try {
-            $authService->getStorage()->write($shibUser->getId());
-        } catch (ExceptionInterface $e) {
-            throw new RuntimeException("Impossible d'écrire dans le storage");
+        $shibUser = $this->shibService->getAuthenticatedUser();
+        if ($shibUser === null) {
+            return []; // une page d'aide s'affichera si les données issues de Shibboleth attendues sont absentes
         }
 
-        $this->userService->userAuthenticated($shibUser);
+        // arrivé ici, l'authentification shibboleth a été faite en bonne et due forme et a réussie.
 
-        $redirectUrl = $this->params()->fromQuery('redirect', '/');
+        $this->setStoredAuthenticatedUsername($shibUser->getUsername());
+        $this->userService->userAuthenticated($shibUser);
 
         return $this->redirect()->toUrl($redirectUrl);
     }
 
-    public function shibboleth()
+    /**
+     * 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");
+        }
     }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Controller/UtilisateurController.php b/src/UnicaenAuth/Controller/UtilisateurController.php
index e8b1b01e70f8b5933d7ddd9f35f3e6b39b98b13f..18bccc48d3dc9eae2113c5cd38dae6767dd741d6 100644
--- a/src/UnicaenAuth/Controller/UtilisateurController.php
+++ b/src/UnicaenAuth/Controller/UtilisateurController.php
@@ -2,16 +2,30 @@
 
 namespace UnicaenAuth\Controller;
 
+use UnicaenApp\Exception\RuntimeException;
+use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
+use UnicaenAuth\Entity\Db\AbstractUser;
+use UnicaenAuth\Entity\Ldap\People;
+use UnicaenAuth\Entity\Shibboleth\ShibUser;
+use UnicaenAuth\Options\ModuleOptions;
+use UnicaenAuth\Service\ShibService;
+use UnicaenAuth\Service\UserContext;
+use Zend\Authentication\AuthenticationService;
 use Zend\Http\Request;
+use Zend\Http\Response;
 use Zend\Mvc\Controller\AbstractActionController;
+use ZfcUser\Mapper\User as UserMapper;
 
 /**
- * 
- *
  * @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr>
  */
 class UtilisateurController extends AbstractActionController
 {
+    /**
+     * @var LdapPeopleMapper
+     */
+    protected $ldapPeopleMapper;
+
     /**
      * Traite les requêtes AJAX POST de sélection d'un profil utilisateur.
      * La sélection est mémorisé en session par le service AuthUserContext.
@@ -45,12 +59,127 @@ class UtilisateurController extends AbstractActionController
 
         return false;
     }
-    
+
     /**
-     * @return \UnicaenAuth\Service\UserContext
+     * Usurpe l'identité d'un autre utilisateur.
+     *
+     * @return Response
+     */
+    public function usurperIdentiteAction()
+    {
+        $request = $this->getRequest();
+        if (! $request instanceof Request) {
+            exit(1);
+        }
+
+        $newIdentity = $request->getQuery('identity', $request->getPost('identity'));
+        if (! $newIdentity) {
+            return $this->redirect()->toRoute('home');
+        }
+
+        /** @var AuthenticationService $authenticationService */
+        $authenticationService = $this->getServiceLocator()->get(AuthenticationService::class);
+
+        $currentIdentity = $authenticationService->getIdentity();
+        if (! $currentIdentity || ! is_array($currentIdentity)) {
+            return $this->redirect()->toRoute('home');
+        }
+
+        if (isset($currentIdentity['ldap'])) {
+            // si l'identifiant demandé contient un @, on estime qu'il s'agit d'un eppn shibboleth : on autorise pas le mélange des genres!
+            // todo: faire mieux
+            if (strpos($newIdentity, '@') !== false) {
+                throw new RuntimeException("Usurpation Shibboleth interdite depuis une authentification LDAP");
+            }
+            /** @var People $currentIdentity */
+            $currentIdentity = $currentIdentity['ldap'];
+
+            // vérif existence de l'individu dans l'annuaire LDAP
+            $ldapPeople = $this->getLdapPeopleMapper()->findOneByUsername($newIdentity);
+            if (!$ldapPeople) {
+                throw new RuntimeException("Identifiant LDAP inconnu");
+            }
+        } elseif (isset($currentIdentity['shib'])) {
+            // si l'identifiant demandé ne contient pas @, on estime qu'il s'agit d'un identifiant LDAP : on autorise pas le mélange des genres!
+            // todo: faire mieux
+            if (strpos($newIdentity, '@') === false) {
+                throw new RuntimeException("Usurpation LDAP interdite depuis une authentification Shibboleth");
+            }
+            /** @var ShibUser $currentIdentity */
+            $currentIdentity = $currentIdentity['shib'];
+        }
+        else {
+            return $this->redirect()->toRoute('home');
+        }
+
+        // seuls les logins spécifiés dans la config sont habilités à usurper des identités
+        /** @var ModuleOptions $options */
+        $options = $this->getServiceLocator()->get('unicaen-auth_module_options');
+        if (! in_array($currentIdentity->getUsername(), $options->getUsurpationAllowedUsernames())) {
+            throw new RuntimeException("Usurpation non explicitement autorisée");
+        }
+
+        // cuisine spéciale pour Shibboleth
+        if ($currentIdentity instanceof ShibUser) {
+            $fromShibUser = $currentIdentity;
+            $toShibUser = $this->createShibUserFromUtilisateurUsername($newIdentity);
+            /** @var ShibService $shibService */
+            $shibService = $this->getServiceLocator()->get(ShibService::class);
+            $shibService->activateUsurpation($fromShibUser, $toShibUser);
+        }
+
+        $authenticationService->getStorage()->write($newIdentity);
+
+        return $this->redirect()->toRoute('home');
+    }
+
+    /**
+     * Recherche l'utilisateur dont le login est spécifié puis instancie un ShibUser à partir
+     * des attributs de cet utilisateur.
+     *
+     * @param string $username Ex tartempion@unicaen.fr
+     * @return ShibUser
+     */
+    protected function createShibUserFromUtilisateurUsername($username)
+    {
+        /** @var AbstractUser $utilisateur */
+        $utilisateur = $this->getUserMapper()->findByUsername($username);
+        if ($utilisateur === null) {
+            throw new RuntimeException("L'utilisateur '$username' n'existe pas dans la table des utilisateurs");
+        }
+
+        $shibUser = new ShibUser();
+        $shibUser->setEppn($utilisateur->getUsername());
+        $shibUser->setId(uniqid()); // peut pas mieux faire pour l'instant
+        $shibUser->setDisplayName($utilisateur->getDisplayName());
+        $shibUser->setEmail($utilisateur->getEmail());
+        $shibUser->setNom('?');     // peut pas mieux faire pour l'instant
+        $shibUser->setPrenom('?');  // peut pas mieux faire pour l'instant
+
+        return $shibUser;
+    }
+
+    /**
+     * @return UserMapper
+     */
+    public function getUserMapper()
+    {
+        return $this->getServiceLocator()->get('zfcuser_user_mapper');
+    }
+
+    /**
+     * @return UserContext
      */
     protected function getAuthUserContextService()
     {
         return $this->getServiceLocator()->get('AuthUserContext');
     }
+
+    /**
+     * @return LdapPeopleMapper
+     */
+    public function getLdapPeopleMapper()
+    {
+        return $this->serviceLocator->get('ldap_people_mapper');
+    }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php b/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php
index 82c4114695736d22317fa3a2ca7c94449869e324..3312082344781febd0e56333e80c4a2aaf829613 100644
--- a/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php
+++ b/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php
@@ -55,6 +55,16 @@ class ShibUser implements UserInterface
         return $this->getUsername();
     }
 
+    /**
+     * Set eduPersoPrincipalName (EPPN).
+     *
+     * @param string $eppn eduPersoPrincipalName (EPPN), ex: 'gauthierb@unicaen.fr'
+     */
+    public function setEppn($eppn)
+    {
+        $this->setUsername($eppn);
+    }
+
     /**
      * Get id.
      *
@@ -156,7 +166,7 @@ class ShibUser implements UserInterface
      */
     public function getPrenom()
     {
-        return $this->givenName;
+        return $this->prenom;
     }
 
     /**
@@ -168,7 +178,7 @@ class ShibUser implements UserInterface
     }
 
     /**
-     * @return string
+     * @return string|null
      */
     public function getCivilite()
     {
@@ -176,9 +186,9 @@ class ShibUser implements UserInterface
     }
 
     /**
-     * @param string $civilite
+     * @param string|null $civilite
      */
-    public function setCivilite($civilite)
+    public function setCivilite($civilite = null)
     {
         $this->civilite = $civilite;
     }
diff --git a/src/UnicaenAuth/Service/ShibService.php b/src/UnicaenAuth/Service/ShibService.php
index 902e3f55c0eb97eb5fc52843e8040f68b164762f..c21da8671ba60f24fa38d84a5209365e576442e8 100644
--- a/src/UnicaenAuth/Service/ShibService.php
+++ b/src/UnicaenAuth/Service/ShibService.php
@@ -2,10 +2,14 @@
 
 namespace UnicaenAuth\Service;
 
+use Assert\Assertion;
+use Assert\AssertionFailedException;
+use UnicaenApp\Exception\LogicException;
 use UnicaenApp\Exception\RuntimeException;
 use UnicaenAuth\Entity\Shibboleth\ShibUser;
 use UnicaenAuth\Options\ModuleOptions;
 use Zend\Mvc\Router\Http\TreeRouteStack;
+use Zend\Session\Container;
 
 /**
  * Shibboleth service
@@ -44,35 +48,205 @@ EOS;
         return $text;
     }
 
+    /**
+     * @return ShibUser|null
+     */
+    public function getAuthenticatedUser()
+    {
+        if (! $this->isShibbolethEnabled()) {
+            return null;
+        }
+
+        if ($this->authenticatedUser === null) {
+            // gestion de l'usurpation éventuelle
+            $this->handleUsurpation();
+
+            if (empty($_SERVER['REMOTE_USER'])) {
+                return null;
+            }
+
+            $this->authenticatedUser = $this->createShibUserFromServerArrayData();
+        }
+
+        return $this->authenticatedUser;
+    }
+
     /**
      * @return boolean
      */
-    public function isShibbolethEnable()
+    public function isShibbolethEnabled()
     {
         $options = $this->options->getShibboleth();
 
         return array_key_exists('enable', $options) && (bool) $options['enable'];
     }
 
+    /**
+     * @return array
+     */
+    public function getShibbolethSimulate()
+    {
+        $options = $this->options->getShibboleth();
+
+        if (! array_key_exists('simulate', $options) || ! is_array($options['simulate'])) {
+            return [];
+        }
+
+        return $options['simulate'];
+    }
+
+    /**
+     * Retourne true si la simulation d'un utilisateur authentifié via Shibboleth est en cours.
+     *
+     * @return bool
+     */
+    public function isSimulationActive()
+    {
+        $options = $this->options->getShibboleth();
+
+        if (array_key_exists('simulate', $options) && is_array($options['simulate']) && ! empty($options['simulate'])) {
+            return true;
+        }
+
+        return false;
+    }
+
     /**
      * @return ShibUser|null
      */
-    public function getAuthenticatedUser()
+    public function handleSimulation()
     {
-        if ($this->authenticatedUser === null) {
-            if (empty($_SERVER['REMOTE_USER'])) {
-                return null;
-            }
-            $this->authenticatedUser = $this->createShibUser();
+        if (! $this->isSimulationActive()) {
+            return null;
         }
 
-        return $this->authenticatedUser;
+        // si nécessaire
+        $this->handleUsurpation();
+
+        $simulate = $this->getShibbolethSimulate();
+
+        try {
+            Assertion::keyIsset($simulate, 'eppn',
+                "Clé 'eppn' introuvable ou sans valeur dans l'option 'unicaen-auth.shibboleth.simulate'");
+            Assertion::eq(count(array_intersect($keys = ['supannEmpId', 'supannEtuId'], array_keys($simulate))), 1,
+                "L'une ou l'autre de ces clés doit être présente dans l'option 'unicaen-auth.shibboleth.simulate': " .
+                implode(', ', $keys));
+        } catch (AssertionFailedException $e) {
+            throw new LogicException("Configuration erronée", null, $e);
+        }
+
+        $eppn = $simulate['eppn'];
+        $supannId = $simulate['supannEmpId'] ?: $simulate['supannEtuId'];
+        $email = isset($simulate['email']) ? $simulate['email'] : null;
+
+        $shibUser = new ShibUser();
+        $shibUser->setEppn($eppn);
+        $shibUser->setId($supannId);
+        $shibUser->setDisplayName("$eppn ($supannId)");
+        $shibUser->setEmail($email);
+        $shibUser->setNom('Shibboleth');
+        $shibUser->setPrenom('Simulation');
+
+        $this->simulateAuthenticatedUser($shibUser);
+
+        return $shibUser;
+    }
+
+    /**
+     * Retourne true si les données stockées en session indiquent qu'une usurpation d'identité Shibboleth est en cours.
+     *
+     * @return bool
+     */
+    private function isUsurpationActive()
+    {
+        return $this->getSessionContainer()->offsetExists('fromShibUser');
+    }
+
+    /**
+     * @param ShibUser $fromShibUser
+     * @param ShibUser $toShibUser
+     * @return self
+     */
+    public function activateUsurpation(ShibUser $fromShibUser, ShibUser $toShibUser)
+    {
+        // le login doit faire partie des usurpateurs autorisés
+        if (! in_array($fromShibUser->getUsername(), $this->options->getUsurpationAllowedUsernames())) {
+            throw new RuntimeException("Usurpation non autorisée");
+        }
+
+        $session = $this->getSessionContainer();
+        $session->offsetSet('fromShibUser', $fromShibUser);
+        $session->offsetSet('toShibUser', $toShibUser);
+
+        return $this;
+    }
+
+    /**
+     * Suppression des données stockées en session concernant l'usurpation d'identité Shibboleth.
+     *
+     * @return self
+     */
+    public function deactivateUsurpation()
+    {
+        $session = $this->getSessionContainer();
+        $session->offsetUnset('fromShibUser');
+        $session->offsetUnset('toShibUser');
+
+        return $this;
+    }
+
+    /**
+     * @return $this
+     */
+    public function handleUsurpation()
+    {
+        if (! $this->isUsurpationActive()) {
+            return $this;
+        }
+
+        $session = $this->getSessionContainer();
+
+        /** @var ShibUser|null $toShibUser */
+        $toShibUser = $session->offsetGet('toShibUser');
+        if ($toShibUser === null) {
+            throw new RuntimeException("Anomalie: 'toShibUser' introuvable");
+        }
+
+        $this->simulateAuthenticatedUser($toShibUser, 'supannEmpId');
+
+        return $this;
+    }
+
+    /**
+     * @return Container
+     */
+    private function getSessionContainer()
+    {
+        return new Container(ShibService::class);
+    }
+
+    /**
+     * Inscrit dans le tableau $_SERVER le nécessaire pour usurper l'identité d'un utilisateur
+     * qui ce serait authentifié via Shibboleth.
+     *
+     * @param ShibUser $shibUser Utilisateur dont on veut usurper l'identité.
+     * @param string $keyForId Clé du tableau $_SERVER dans laquelle mettre l'id de l'utilsateur spécifié.
+     *                         Ex: 'supannEmpId', 'supannEtuId'.
+     */
+    public function simulateAuthenticatedUser(ShibUser $shibUser, $keyForId = 'supannEmpId')
+    {
+        $_SERVER['REMOTE_USER'] = $shibUser->getEppn();
+        $_SERVER[$keyForId] = $shibUser->getId();
+        $_SERVER['displayName'] = $shibUser->getDisplayName();
+        $_SERVER['mail'] = $shibUser->getEmail();
+        $_SERVER['sn'] = $shibUser->getNom();
+        $_SERVER['givenName'] = $shibUser->getPrenom();
     }
 
     /**
      * @return ShibUser
      */
-    private function createShibUser()
+    private function createShibUserFromServerArrayData()
     {
         $eppn = $_SERVER['REMOTE_USER'];
 
@@ -81,7 +255,7 @@ EOS;
         } elseif (isset($_SERVER['supannEmpId'])) {
             $id = $_SERVER['supannEmpId'];
         } else {
-            throw new RuntimeException('Un au moins des attributs suivants doivent exister dans $_SERVER : supannEtuId, supannEmpId.');
+            throw new RuntimeException('Un au moins des attributs Shibboleth suivants doit exister dans $_SERVER : supannEtuId, supannEmpId.');
         }
 
         $mail = null;
@@ -94,11 +268,34 @@ EOS;
             $displayName = $_SERVER['displayName'];
         }
 
+        $surname = null;
+        if (isset($_SERVER['sn'])) {
+            $surname = $_SERVER['sn'];
+        } elseif (isset($_SERVER['surname'])) {
+            $surname = $_SERVER['surname'];
+        }
+
+        $givenName = null;
+        if (isset($_SERVER['givenName'])) {
+            $givenName = $_SERVER['givenName'];
+        }
+
+        $civilite = null;
+        if (isset($_SERVER['supannCivilite'])) {
+            $civilite = $_SERVER['supannCivilite'];
+        }
+
         $shibUser = new ShibUser();
+        // propriétés de UserInterface
         $shibUser->setId($id);
         $shibUser->setUsername($eppn);
         $shibUser->setDisplayName($displayName);
         $shibUser->setEmail($mail);
+        $shibUser->setPassword(null);
+        // autres propriétés
+        $shibUser->setNom($surname);
+        $shibUser->setPrenom($givenName);
+        $shibUser->setCivilite($civilite);
 
         return $shibUser;
     }
@@ -111,6 +308,10 @@ EOS;
      */
     public function getLogoutUrl($returnAbsoluteUrl = null)
     {
+        if ($this->getShibbolethSimulate()) {
+            return '/';
+        }
+
         $logoutRelativeUrl = '/Shibboleth.sso/Logout?return='; // NB: '?return=' semble obligatoire!
 
         if ($returnAbsoluteUrl) {
diff --git a/src/UnicaenAuth/Service/UserContext.php b/src/UnicaenAuth/Service/UserContext.php
index 65aab76944b4043dc4f522d14d2acb782f6a35ca..0fc768d899a4205cd2e15582f902ff1985e40a60 100644
--- a/src/UnicaenAuth/Service/UserContext.php
+++ b/src/UnicaenAuth/Service/UserContext.php
@@ -5,6 +5,7 @@ namespace UnicaenAuth\Service;
 use BjyAuthorize\Acl\Role;
 use UnicaenApp\Exception\RuntimeException;
 use UnicaenApp\Traits\SessionContainerTrait;
+use UnicaenAuth\Entity\Shibboleth\ShibUser;
 use UnicaenAuth\Event\UserRoleSelectedEvent;
 use UnicaenAuth\Provider\Identity\Chain;
 use Zend\EventManager\EventManagerAwareInterface;
@@ -73,6 +74,24 @@ class UserContext extends AbstractService implements EventManagerAwareInterface
 
 
 
+    /**
+     * Retourne l'éventuel utilisateur Shibboleth courant.
+     *
+     * @return ShibUser|null
+     */
+    public function getShibUser()
+    {
+        if (($identity = $this->getIdentity())) {
+            if (isset($identity['shib']) && $identity['shib'] instanceof ShibUser) {
+                return $identity['shib'];
+            }
+        }
+
+        return null;
+    }
+
+
+
     /**
      * Retourne les données d'identité correspondant à l'utilisateur courant.
      *
@@ -91,6 +110,27 @@ class UserContext extends AbstractService implements EventManagerAwareInterface
     }
 
 
+    /**
+     * Retourne l'identifiant de connexion de l'utilisateur courant.
+     *
+     * @return string|null
+     */
+    public function getIdentityUsername()
+    {
+        if ($user = $this->getShibUser()) {
+            return $user->getUsername();
+        }
+        if ($user = $this->getLdapUser()) {
+            return $user->getUsername();
+        }
+        if ($user = $this->getDbUser()) {
+            return $user->getUsername();
+        }
+
+        return null;
+    }
+
+
 
     /**
      * @param string $roleId
diff --git a/src/UnicaenAuth/View/Helper/ShibConnectViewHelper.php b/src/UnicaenAuth/View/Helper/ShibConnectViewHelper.php
index 2038e8f7d86fc3d34ec46f410663bf1e508b83eb..477ddf603ee7843c61482db6866c86fcda1fa64c 100644
--- a/src/UnicaenAuth/View/Helper/ShibConnectViewHelper.php
+++ b/src/UnicaenAuth/View/Helper/ShibConnectViewHelper.php
@@ -34,7 +34,7 @@ class ShibConnectViewHelper extends AbstractHelper
      */
     private function render()
     {
-        if (! $this->shibService->isShibbolethEnable()) {
+        if (! $this->shibService->isShibbolethEnabled()) {
             return '';
         }
 
diff --git a/src/UnicaenAuth/View/Helper/UserInfo.php b/src/UnicaenAuth/View/Helper/UserInfo.php
index 83f28bd82b0acb6ff44708e632ac4453cde371b6..c837447870cf2e20557daf2ea1c11088abdbf09d 100644
--- a/src/UnicaenAuth/View/Helper/UserInfo.php
+++ b/src/UnicaenAuth/View/Helper/UserInfo.php
@@ -102,7 +102,11 @@ class UserInfo extends UserAbstract
             }
             $out .= $aucuneAffDispo;
         }
-        
+
+        // formulaire d'usurpation d'identité
+        $userUsurpationHelper = $this->view->plugin('userUsurpation'); /* @var $userUsurpationHelper \UnicaenAuth\View\Helper\UserUsurpationHelper */
+        $out .= $userUsurpationHelper();
+
         return $out;
     }
 
diff --git a/src/UnicaenAuth/View/Helper/UserUsurpationHelper.php b/src/UnicaenAuth/View/Helper/UserUsurpationHelper.php
new file mode 100644
index 0000000000000000000000000000000000000000..ab6d807fa7e95497c562f587ecdcaaf516be401a
--- /dev/null
+++ b/src/UnicaenAuth/View/Helper/UserUsurpationHelper.php
@@ -0,0 +1,141 @@
+<?php
+
+namespace UnicaenAuth\View\Helper;
+
+use UnicaenApp\Form\View\Helper\FormControlGroup;
+use UnicaenAuth\Options\ModuleOptions;
+use Zend\Form\Element\Submit;
+use Zend\Form\Element\Text;
+use Zend\Form\Form;
+use Zend\Form\View\Helper\Form as FormHelper;
+use Zend\View\Renderer\PhpRenderer;
+
+/**
+ * Aide de vue permettant de saisir et valider le login de l'utilisateur dont on veut usurper l'identité.
+ *
+ * @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
+ */
+class UserUsurpationHelper extends UserAbstract
+{
+    /**
+     * @var PhpRenderer
+     */
+    protected $view;
+
+    /**
+     * @var ModuleOptions
+     */
+    protected $moduleOptions;
+
+    /**
+     * @var string
+     */
+    private $url;
+
+    /**
+     * @var bool
+     */
+    private $usurpationEnabled = false;
+
+    /**
+     * Point d'entrée.
+     *
+     * @return self
+     */
+    public function __invoke()
+    {
+        return $this;
+    }
+    
+    /**
+     * Retourne le code HTML généré par cette aide de vue.
+     * 
+     * @return string 
+     */
+    public function __toString()
+    {
+        if (! $this->usurpationEnabled) {
+            return '';
+        }
+
+        /** @var FormHelper $formHelper */
+        $formHelper = $this->view->plugin('form');
+        /** @var FormControlGroup $formControlGroupHelper */
+        $formControlGroupHelper = $this->view->plugin('formControlGroup');
+
+        $form = new Form('user-usurpation-form');
+        $form->setAttributes([
+            'class' => 'user-usurpation-form',
+            'action' => $this->url,
+        ]);
+
+        $input = new Text('identity');
+        $input->setAttributes([
+            'id' => 'user-usurpation-input',
+            'placeholder' => "Identifiant utilisateur",
+        ]);
+
+        $submit = new Submit('submit');
+        $submit->setValue("Usurper");
+        $submit->setAttributes([
+            'class' => 'user-usurpation-submit btn btn-info',
+        ]);
+
+        $html = '';
+        $html .= $formHelper->openTag($form);
+        $html .= "<div><strong>Usurpation d'identité :</strong></div>";
+        $html .= $formControlGroupHelper->__invoke($input);
+        $html .= $formControlGroupHelper->__invoke($submit);
+        $html .= $formHelper->closeTag();
+
+        $html .= <<<EOS
+<script>
+    $(".user-usurpation-form").submit(function() {
+        $("body *").css('cursor', 'wait');
+    });
+    var input = $("#user-usurpation-input").on('input', function() {
+        updateUsurpationSubmit();
+    });
+    function updateUsurpationSubmit() {
+        $(".user-usurpation-submit").prop("disabled", input.val().length === 0);
+    }
+    updateUsurpationSubmit();
+</script>
+EOS;
+
+        return $html;
+    }
+
+    /**
+     * @param string $url
+     * @return $this
+     */
+    public function setUrl($url)
+    {
+        $this->url = $url;
+
+        return $this;
+    }
+
+    /**
+     * @param ModuleOptions $moduleOptions
+     * @return self
+     */
+    public function setModuleOptions(ModuleOptions $moduleOptions)
+    {
+        $this->moduleOptions = $moduleOptions;
+
+        return $this;
+    }
+
+    /**
+     * @param bool $usurpationEnabled
+     * @return self
+     */
+    public function setUsurpationEnabled($usurpationEnabled = true)
+    {
+        $this->usurpationEnabled = $usurpationEnabled;
+
+        return $this;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/View/Helper/UserUsurpationHelperFactory.php b/src/UnicaenAuth/View/Helper/UserUsurpationHelperFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..9aa21103a2f05a72c3d26c2587b9c97e9123de5d
--- /dev/null
+++ b/src/UnicaenAuth/View/Helper/UserUsurpationHelperFactory.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace UnicaenAuth\View\Helper;
+
+use UnicaenAuth\Options\ModuleOptions;
+use UnicaenAuth\Service\UserContext;
+use Zend\View\Helper\Url;
+use Zend\View\HelperPluginManager;
+
+class UserUsurpationHelperFactory
+{
+    /**
+     * @param HelperPluginManager $hpm
+     * @return UserUsurpationHelper
+     */
+    public function __invoke(HelperPluginManager $hpm)
+    {
+        /** @var Url $urlHelper */
+        $urlHelper = $hpm->get('url');
+        $url = $urlHelper->__invoke('utilisateur/default', ['action' => 'usurper-identite']);
+
+        /** @var UserContext $userContextService */
+        $userContextService = $hpm->getServiceLocator()->get('authUserContext');
+
+        /** @var ModuleOptions $moduleOptions */
+        $moduleOptions = $hpm->getServiceLocator()->get('unicaen-auth_module_options');
+
+        $usurpationAllowed = in_array(
+            $userContextService->getIdentityUsername(),
+            $moduleOptions->getUsurpationAllowedUsernames());
+
+        $helper = new UserUsurpationHelper($userContextService);
+        $helper->setUrl($url);
+        $helper->setUsurpationEnabled($usurpationAllowed);
+
+        return $helper;
+    }
+}
\ No newline at end of file
diff --git a/view/unicaen-auth/auth/shibboleth.phtml b/view/unicaen-auth/auth/shibboleth.phtml
index 63addc60275602b3b9b0d546d1d8adce1b6543ee..aeb1c638e32a4865322cae6c4692ebb5ceddd833 100644
--- a/view/unicaen-auth/auth/shibboleth.phtml
+++ b/view/unicaen-auth/auth/shibboleth.phtml
@@ -1,23 +1,41 @@
 <h1 class="page-header">Authentification Shibboleth</h1>
-
 <p>
     Si vous arrivez sur cette page, c'est sans doute que vous cherchez à utiliser l'authentification Shibboleth, mais
     qu'elle est mal configurée !
 </p>
 
+<h2>Configuration pour Shibboleth</h2>
 <p>
-    Vous devez activer l'authentification Shibboleth dans la config :
+    Déjà, vous devez activer l'authentification Shibboleth dans la config de UnicaenAuth :
 </p>
 <pre>
     'unicaen-auth' => [
         ...
         'shibboleth' => [
-            'enable' => true
+            'enable' => true,
         ],
-    ];
+    ],
 </pre>
-
 <p>
     Et voici ce que vous devez ajouter dans la configuration Apache de votre site :
 </p>
-<pre><?php echo htmlspecialchars(\UnicaenAuth\Service\ShibService::apacheConfigSnippet()) ?></pre>
\ No newline at end of file
+<pre><?php echo htmlspecialchars(\UnicaenAuth\Service\ShibService::apacheConfigSnippet()) ?></pre>
+
+
+<h2>Autre solution : simuler une authentification Shibboleth</h2>
+<p>
+    Il est possible de faire comme si un utilisateur s'était connecté via Shibboleth.
+    Cela se fait via la clé de config 'simulate' :
+</p>
+<pre>
+    'unicaen-auth' => [
+        ...
+        'shibboleth' => [
+            'enable' => true,
+            'simulate' => [
+                'eppn'        => 'gauthierb@unicaen.fr',
+                'supannEmpId' => '00021237',
+            ],
+        ],
+    ],
+</pre>
\ No newline at end of file