From e3e9cf986319067b34bebb14055491e6fc8e1550 Mon Sep 17 00:00:00 2001
From: Bertrand Gauthier <bertrand.gauthier@unicaen.fr>
Date: Thu, 15 Mar 2018 16:53:53 +0100
Subject: [PATCH] Authentification Shibboleth

---
 composer.json                                 |   2 +-
 config/module.config.php                      |  35 +++-
 config/unicaen-auth.local.php.dist            |   6 +
 .../Adapter/AbstractFactory.php               |  21 ++-
 .../Authentication/Adapter/Ldap.php           |  87 +++++++--
 .../Storage/ChainableStorage.php              |  14 +-
 .../Authentication/Storage/Shib.php           | 114 ++++++++++++
 src/UnicaenAuth/Controller/AuthController.php |  48 +++++
 .../Controller/AuthControllerFactory.php      |  29 +++
 src/UnicaenAuth/Entity/Db/AbstractUser.php    |   2 +-
 .../Entity/Shibboleth/ShibUser.php            | 171 ++++++++++++++++++
 .../UserRoleSelectedEventAbstractListener.php |   1 -
 .../Event/UserAuthenticatedEvent.php          |  55 ++++--
 src/UnicaenAuth/Options/ModuleOptions.php     |  56 +++---
 src/UnicaenAuth/Service/ShibService.php       | 112 ++++++++++++
 .../Service/ShibServiceFactory.php            |  20 ++
 .../Service/Traits/ShibServiceAwareTrait.php  |  21 +++
 .../Service/Traits/UserServiceAwareTrait.php  |  21 +++
 src/UnicaenAuth/Service/User.php              | 126 ++++++-------
 .../View/Helper/ShibConnectViewHelper.php     |  47 +++++
 .../Helper/ShibConnectViewHelperFactory.php   |  24 +++
 view/unicaen-auth/auth/shibboleth.phtml       |  23 +++
 view/zfc-user/user/login.phtml                |   3 +
 23 files changed, 903 insertions(+), 135 deletions(-)
 create mode 100644 src/UnicaenAuth/Authentication/Storage/Shib.php
 create mode 100644 src/UnicaenAuth/Controller/AuthController.php
 create mode 100644 src/UnicaenAuth/Controller/AuthControllerFactory.php
 create mode 100644 src/UnicaenAuth/Entity/Shibboleth/ShibUser.php
 create mode 100644 src/UnicaenAuth/Service/ShibService.php
 create mode 100644 src/UnicaenAuth/Service/ShibServiceFactory.php
 create mode 100644 src/UnicaenAuth/Service/Traits/ShibServiceAwareTrait.php
 create mode 100644 src/UnicaenAuth/Service/Traits/UserServiceAwareTrait.php
 create mode 100644 src/UnicaenAuth/View/Helper/ShibConnectViewHelper.php
 create mode 100644 src/UnicaenAuth/View/Helper/ShibConnectViewHelperFactory.php
 create mode 100644 view/unicaen-auth/auth/shibboleth.phtml

diff --git a/composer.json b/composer.json
index 6a8e036..c6bae3b 100644
--- a/composer.json
+++ b/composer.json
@@ -4,7 +4,7 @@
     "repositories": [
         {
             "type": "composer",
-            "url": "https://dev.unicaen.fr/packagist"
+            "url": "https://gest.unicaen.fr/packagist"
         }
     ],
     "require": {
diff --git a/config/module.config.php b/config/module.config.php
index c0e9b8f..999594a 100644
--- a/config/module.config.php
+++ b/config/module.config.php
@@ -1,9 +1,11 @@
 <?php
 
-use UnicaenAuth\Provider\Privilege\Privileges;
+use UnicaenAuth\Controller\AuthControllerFactory;
+use UnicaenAuth\Service\ShibService;
+use UnicaenAuth\Service\ShibServiceFactory;
+use UnicaenAuth\View\Helper\ShibConnectViewHelperFactory;
 
 $settings = [
-
     /**
      * Fournisseurs d'identité.
      */
@@ -119,6 +121,8 @@ return [
                 ['controller' => 'UnicaenApp\Controller\Application', 'action' => 'informatique-et-libertes', 'roles' => []],
                 ['controller' => 'UnicaenApp\Controller\Application', 'action' => 'refresh-session', 'roles' => []],
                 ['controller' => 'UnicaenAuth\Controller\Utilisateur', 'action' => 'selectionner-profil', 'roles' => []],
+
+                ['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'shibboleth', 'roles' => []],
             ],
         ],
     ],
@@ -167,6 +171,27 @@ return [
     ],
     'router'          => [
         'routes' => [
+            'auth'     => [
+                'type'          => 'Literal',
+                'options'       => [
+                    'route'    => '/auth',
+                    'defaults' => [
+                        'controller' => 'UnicaenAuth\Controller\Auth',
+                    ],
+                ],
+                'may_terminate' => false,
+                'child_routes'  => [
+                    'shibboleth' => [
+                        'type' => 'Literal',
+                        'options' => [
+                            'route'    => '/shibboleth',
+                            'defaults' => [
+                                'action'     => 'shibboleth',
+                            ],
+                        ],
+                    ],
+                ],
+            ],
             'zfcuser'     => [
                 'type'          => 'Literal',
                 'priority'      => 1000,
@@ -346,6 +371,7 @@ return [
         'invokables'         => [
             '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\View\RedirectionStrategy'    => 'UnicaenAuth\View\RedirectionStrategy',
             'UnicaenAuth\Service\UserContext'         => 'UnicaenAuth\Service\UserContext',
             'UnicaenAuth\Service\User'                => 'UnicaenAuth\Service\User',
@@ -369,6 +395,7 @@ return [
             'UnicaenAuth\Service\Privilege'            => 'UnicaenAuth\Service\PrivilegeServiceFactory',
             'BjyAuthorize\Service\Authorize'           => 'UnicaenAuth\Service\AuthorizeServiceFactory', // substituion
             'zfcuser_redirect_callback'                => 'UnicaenAuth\Authentication\RedirectCallbackFactory', // substituion
+            ShibService::class                         => ShibServiceFactory::class,
         ],
         'initializers'       => [
             'UnicaenAuth\Service\UserAwareInitializer',
@@ -380,6 +407,9 @@ return [
             'UnicaenAuth\Controller\Utilisateur' => 'UnicaenAuth\Controller\UtilisateurController',
             'UnicaenAuth\Controller\Droits'      => 'UnicaenAuth\Controller\DroitsController',
         ],
+        'factories' => [
+            'UnicaenAuth\Controller\Auth'        => AuthControllerFactory::class,
+        ],
     ],
 
     'form_elements' => [
@@ -397,6 +427,7 @@ return [
             'userInfo'                   => 'UnicaenAuth\View\Helper\UserInfoFactory',
             'userProfileSelect'          => 'UnicaenAuth\View\Helper\UserProfileSelectFactory',
             'userProfileSelectRadioItem' => 'UnicaenAuth\View\Helper\UserProfileSelectRadioItemFactory',
+            'shibConnect'                => ShibConnectViewHelperFactory::class,
         ],
         'invokables' => [
             'appConnection' => 'UnicaenAuth\View\Helper\AppConnection',
diff --git a/config/unicaen-auth.local.php.dist b/config/unicaen-auth.local.php.dist
index ee3a26e..8cecd5d 100644
--- a/config/unicaen-auth.local.php.dist
+++ b/config/unicaen-auth.local.php.dist
@@ -6,6 +6,12 @@
  * drop this config file in it and change the values as you wish.
  */
 $settings = [
+    /**
+     * Activation ou non de l'authentification Shibboleth.
+     */
+    'shibboleth' => [
+        'enable' => false,
+    ],
     /**
      * Paramètres de connexion au serveur CAS :
      * - pour désactiver l'authentification CAS, le tableau 'cas' doit être vide.
diff --git a/src/UnicaenAuth/Authentication/Adapter/AbstractFactory.php b/src/UnicaenAuth/Authentication/Adapter/AbstractFactory.php
index adb8ac1..d5ce373 100644
--- a/src/UnicaenAuth/Authentication/Adapter/AbstractFactory.php
+++ b/src/UnicaenAuth/Authentication/Adapter/AbstractFactory.php
@@ -1,10 +1,10 @@
 <?php
+
 namespace UnicaenAuth\Authentication\Adapter;
 
-use UnicaenApp\Exception;
-use UnicaenAuth\Authentication\Adapter\Cas;
-use UnicaenAuth\Authentication\Adapter\Db;
-use UnicaenAuth\Authentication\Adapter\Ldap;
+use UnicaenApp\Exception\LogicException;
+use Zend\EventManager\EventManager;
+use Zend\EventManager\EventManagerAwareInterface;
 use Zend\ServiceManager\AbstractFactoryInterface;
 use Zend\ServiceManager\ServiceLocatorInterface;
 
@@ -34,7 +34,7 @@ class AbstractFactory implements AbstractFactoryInterface
      * @param ServiceLocatorInterface $serviceLocator
      * @param $name
      * @param $requestedName
-     * @return mixed
+     * @return \ZfcUser\Authentication\Adapter\AbstractAdapter
      */
     public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
     {
@@ -48,12 +48,19 @@ class AbstractFactory implements AbstractFactoryInterface
             case __NAMESPACE__ . '\Cas':
                 $adapter = new Cas();
                 break;
+            //
+            // NB: pour faire simple, la stratégie de créer un adapter pour l'auth Shibboleth n'a pas été retenue.
+            //
+            // case __NAMESPACE__ . '\Shib':
+            //     $adapter = new Shib();
+            //     break;
             default:
-                throw new Exception("Service demandé inattendu : '$requestedName'!");
+                throw new LogicException("Service demandé inattendu : '$requestedName'!");
                 break;
         }
 
-        if ($adapter instanceof \Zend\EventManager\EventManagerAwareInterface) {
+        if ($adapter instanceof EventManagerAwareInterface) {
+            /** @var EventManager $eventManager */
             $eventManager = $serviceLocator->get('event_manager');
             $adapter->setEventManager($eventManager);
             $userService = $serviceLocator->get('unicaen-auth_user_service'); /* @var $userService \UnicaenAuth\Service\User */
diff --git a/src/UnicaenAuth/Authentication/Adapter/Ldap.php b/src/UnicaenAuth/Authentication/Adapter/Ldap.php
index 9053dd4..f9875ef 100644
--- a/src/UnicaenAuth/Authentication/Adapter/Ldap.php
+++ b/src/UnicaenAuth/Authentication/Adapter/Ldap.php
@@ -1,10 +1,14 @@
 <?php
+
 namespace UnicaenAuth\Authentication\Adapter;
 
+use UnicaenApp\Exception\RuntimeException;
+use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
 use UnicaenAuth\Options\ModuleOptions;
-use Zend\Authentication\Exception\UnexpectedValueException;
-use Zend\Authentication\Result as AuthenticationResult;
+use UnicaenAuth\Service\User;
 use Zend\Authentication\Adapter\Ldap as LdapAuthAdapter;
+use Zend\Authentication\Exception\ExceptionInterface;
+use Zend\Authentication\Result as AuthenticationResult;
 use Zend\EventManager\EventManager;
 use Zend\EventManager\EventManagerAwareInterface;
 use Zend\EventManager\EventManagerInterface;
@@ -38,6 +42,11 @@ class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, Even
      */
     protected $ldapAuthAdapter;
 
+    /**
+     * @var LdapPeopleMapper
+     */
+    protected $ldapPeopleMapper;
+
     /**
      * @var ModuleOptions
      */
@@ -52,16 +61,21 @@ class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, Even
      *
      * @param AuthEvent $e
      * @return boolean
-     * @throws UnexpectedValueException
+     * @throws \Zend\Authentication\Adapter\Exception\ExceptionInterface
+     * @throws \Zend\Ldap\Exception\LdapException
      * @see ChainableAdapter
      */
     public function authenticate(AuthEvent $e)
     {
         if ($this->isSatisfied()) {
-            $storage = $this->getStorage()->read();
+            try {
+                $storage = $this->getStorage()->read();
+            } catch (ExceptionInterface $e) {
+                throw new RuntimeException("Erreur de lecture du storage");
+            }
             $e->setIdentity($storage['identity'])
-                    ->setCode(AuthenticationResult::SUCCESS)
-                    ->setMessages(['Authentication successful.']);
+                ->setCode(AuthenticationResult::SUCCESS)
+                ->setMessages(['Authentication successful.']);
             return;
         }
 
@@ -73,20 +87,36 @@ class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, Even
         // Failure!
         if (! $success) {
             $e->setCode(AuthenticationResult::FAILURE)
-              ->setMessages(['LDAP bind failed.']);
+                ->setMessages(['LDAP bind failed.']);
+            $this->setSatisfied(false);
+            return false;
+        }
+
+        // recherche de l'individu dans l'annuaire LDAP
+        $ldapPeople = $this->getLdapPeopleMapper()->findOneByUsername($username);
+        if (!$ldapPeople) {
+            $e
+                ->setCode(AuthenticationResult::FAILURE)
+                ->setMessages(['Authentication failed.']);
             $this->setSatisfied(false);
             return false;
         }
 
         $e->setIdentity($this->usernameUsurpe ?: $username);
         $this->setSatisfied(true);
-        $storage = $this->getStorage()->read();
-        $storage['identity'] = $e->getIdentity();
-        $this->getStorage()->write($storage);
+        try {
+            $storage = $this->getStorage()->read();
+            $storage['identity'] = $e->getIdentity();
+            $this->getStorage()->write($storage);
+        } catch (ExceptionInterface $e) {
+            throw new RuntimeException("Erreur de concernant le storage");
+        }
         $e->setCode(AuthenticationResult::SUCCESS)
-          ->setMessages(['Authentication successful.']);
+            ->setMessages(['Authentication successful.']);
 
-        $this->getEventManager()->trigger('userAuthenticated', $e);
+        /* @var $userService User */
+        $userService = $this->getServiceManager()->get('unicaen-auth_user_service');
+        $userService->userAuthenticated($ldapPeople);
     }
 
     /**
@@ -115,9 +145,11 @@ class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, Even
     /**
      * Authentifie l'identifiant et le mot de passe spécifiés.
      *
-     * @param string $username Identifiant de connexion
+     * @param string $username   Identifiant de connexion
      * @param string $credential Mot de passe
      * @return boolean
+     * @throws \Zend\Authentication\Adapter\Exception\ExceptionInterface
+     * @throws \Zend\Ldap\Exception\LdapException
      */
     public function authenticateUsername($username, $credential)
     {
@@ -149,6 +181,31 @@ class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, Even
         return $success;
     }
 
+    /**
+     * 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;
+    }
+
     /**
      * @param ModuleOptions $options
      */
@@ -164,8 +221,8 @@ class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, Even
     {
         if (!$this->options instanceof ModuleOptions) {
             $options = array_merge(
-                    $this->getServiceManager()->get('zfcuser_module_options')->toArray(),
-                    $this->getServiceManager()->get('unicaen-auth_module_options')->toArray());
+                $this->getServiceManager()->get('zfcuser_module_options')->toArray(),
+                $this->getServiceManager()->get('unicaen-auth_module_options')->toArray());
             $this->setOptions(new ModuleOptions($options));
         }
         return $this->options;
diff --git a/src/UnicaenAuth/Authentication/Storage/ChainableStorage.php b/src/UnicaenAuth/Authentication/Storage/ChainableStorage.php
index 8da4199..99f5f1b 100644
--- a/src/UnicaenAuth/Authentication/Storage/ChainableStorage.php
+++ b/src/UnicaenAuth/Authentication/Storage/ChainableStorage.php
@@ -2,8 +2,6 @@
 
 namespace UnicaenAuth\Authentication\Storage;
 
-use UnicaenAuth\Authentication\Storage\ChainEvent;
-
 interface ChainableStorage
 {
     /**
@@ -11,25 +9,21 @@ interface ChainableStorage
      *
      * Behavior is undefined when storage is empty.
      *
-     * @throws InvalidArgumentException If reading contents from storage is impossible
-     * @return People
+     * @param \UnicaenAuth\Authentication\Storage\ChainEvent $e
      */
     public function read(ChainEvent $e);
-    
+
     /**
      * Writes $contents to storage
      *
-     * @param  mixed $contents
-     * @throws InvalidArgumentException If writing $contents to storage is impossible
-     * @return void
+     * @param \UnicaenAuth\Authentication\Storage\ChainEvent $e
      */
     public function write(ChainEvent $e);
 
     /**
      * Clears contents from storage
      *
-     * @throws InvalidArgumentException If clearing contents from storage is impossible
-     * @return void
+     * @param \UnicaenAuth\Authentication\Storage\ChainEvent $e
      */
     public function clear(ChainEvent $e);
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Authentication/Storage/Shib.php b/src/UnicaenAuth/Authentication/Storage/Shib.php
new file mode 100644
index 0000000..1bc25ef
--- /dev/null
+++ b/src/UnicaenAuth/Authentication/Storage/Shib.php
@@ -0,0 +1,114 @@
+<?php
+
+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;
+use Zend\ServiceManager\ServiceLocatorAwareInterface;
+use Zend\ServiceManager\ServiceLocatorAwareTrait;
+use Zend\ServiceManager\ServiceManager;
+
+/**
+ * Shibboleth authentication storage.
+ *
+ * @author Unicaen
+ */
+class Shib implements ChainableStorage, ServiceLocatorAwareInterface
+{
+    use ServiceLocatorAwareTrait;
+
+    /**
+     * @var StorageInterface
+     */
+    protected $storage;
+    
+    /**
+     * @var ModuleOptions
+     */
+    protected $options;
+
+    /**
+     * @var ShibUser
+     */
+    protected $resolvedIdentity;
+
+    /**
+     * @var ServiceManager
+     */
+    protected $serviceManager;
+
+    /**
+     * Returns the contents of storage
+     *
+     * Behavior is undefined when storage is empty.
+     *
+     * @param ChainEvent $e
+     * @return ShibUser
+     * @throws \Zend\Authentication\Exception\ExceptionInterface
+     */
+    public function read(ChainEvent $e)
+    {
+        /** @var ShibService $shib */
+        $shib = $this->getServiceLocator()->get(ShibService::class);
+        $shibUser = $shib->getAuthenticatedUser();
+
+        $e->addContents('shib', $shibUser);
+        
+        return $shibUser;
+    }
+
+    /**
+     * Writes $contents to storage
+     *
+     * @param ChainEvent $e
+     * @throws \Zend\Authentication\Exception\ExceptionInterface
+     */
+    public function write(ChainEvent $e)
+    {
+        $contents = $e->getParam('contents');
+        $this->resolvedIdentity = null;
+        $this->getStorage()->write($contents);
+    }
+
+    /**
+     * Clears contents from storage
+     *
+     * @param ChainEvent $e
+     * @throws \Zend\Authentication\Exception\ExceptionInterface
+     */
+    public function clear(ChainEvent $e)
+    {
+        $this->resolvedIdentity = null;
+        $this->getStorage()->clear();
+    }
+
+    /**
+     * getStorage
+     *
+     * @return StorageInterface
+     */
+    public function getStorage()
+    {
+        if (null === $this->storage) {
+            $this->setStorage(new Session());
+        }
+
+        return $this->storage;
+    }
+
+    /**
+     * setStorage
+     *
+     * @param StorageInterface $storage
+     * @return self
+     */
+    public function setStorage(StorageInterface $storage)
+    {
+        $this->storage = $storage;
+
+        return $this;
+    }
+}
diff --git a/src/UnicaenAuth/Controller/AuthController.php b/src/UnicaenAuth/Controller/AuthController.php
new file mode 100644
index 0000000..3b61708
--- /dev/null
+++ b/src/UnicaenAuth/Controller/AuthController.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace UnicaenAuth\Controller;
+
+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\Response;
+use Zend\Mvc\Controller\AbstractActionController;
+
+/**
+ * Classe ajoutée lors de l'implémentation de l'auth Shibboleth.
+ *
+ * @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr>
+ */
+class AuthController extends AbstractActionController
+{
+    use ShibServiceAwareTrait;
+    use UserServiceAwareTrait;
+
+    /**
+     * @return Response|array
+     */
+    public function shibbolethAction()
+    {
+        $shibUser = $this->shibService->getAuthenticatedUser();
+
+        if ($shibUser === null) {
+            return []; // la page d'aide s'affiche
+        }
+
+        /** @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");
+        }
+
+        $this->userService->userAuthenticated($shibUser);
+
+        $redirectUrl = $this->params()->fromQuery('redirect', '/');
+
+        return $this->redirect()->toUrl($redirectUrl);
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Controller/AuthControllerFactory.php b/src/UnicaenAuth/Controller/AuthControllerFactory.php
new file mode 100644
index 0000000..e771651
--- /dev/null
+++ b/src/UnicaenAuth/Controller/AuthControllerFactory.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace UnicaenAuth\Controller;
+
+use UnicaenAuth\Service\ShibService;
+use UnicaenAuth\Service\User as UserService;
+use Zend\Mvc\Controller\ControllerManager;
+
+class AuthControllerFactory
+{
+    /**
+     * @param ControllerManager $cm
+     * @return AuthController
+     */
+    public function __invoke(ControllerManager $cm)
+    {
+        /** @var ShibService $shibService */
+        $shibService = $cm->getServiceLocator()->get(ShibService::class);
+
+        /* @var $userService UserService */
+        $userService = $cm->getServiceLocator()->get('unicaen-auth_user_service');
+
+        $controller = new AuthController();
+        $controller->setShibService($shibService);
+        $controller->setUserService($userService);
+
+        return $controller;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Entity/Db/AbstractUser.php b/src/UnicaenAuth/Entity/Db/AbstractUser.php
index eb2918e..e19aba6 100644
--- a/src/UnicaenAuth/Entity/Db/AbstractUser.php
+++ b/src/UnicaenAuth/Entity/Db/AbstractUser.php
@@ -205,7 +205,7 @@ abstract class AbstractUser implements UserInterface, ProviderInterface
     /**
      * Get role.
      *
-     * @return RoleInterface[]
+     * @return Collection
      */
     public function getRoles()
     {
diff --git a/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php b/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php
new file mode 100644
index 0000000..a51faa9
--- /dev/null
+++ b/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php
@@ -0,0 +1,171 @@
+<?php
+
+namespace UnicaenAuth\Entity\Shibboleth;
+
+use ZfcUser\Entity\UserInterface;
+
+class ShibUser implements UserInterface
+{
+    /**
+     * @var string
+     */
+    protected $id;
+
+    /**
+     * @var string
+     */
+    protected $username;
+
+    /**
+     * @var string
+     */
+    protected $email;
+
+    /**
+     * @var string
+     */
+    protected $displayName;
+
+    /**
+     * @var int
+     */
+    protected $state = 1;
+
+
+    /**
+     * @return string
+     */
+    public function getEppn()
+    {
+        return $this->getUsername();
+    }
+
+    /**
+     * Get id.
+     *
+     * @return string
+     */
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    /**
+     * Set id.
+     *
+     * @param string $id
+     */
+    public function setId($id)
+    {
+        $this->id = $id;
+    }
+
+    /**
+     * Get username.
+     *
+     * @return string
+     */
+    public function getUsername()
+    {
+        return $this->username;
+    }
+
+    /**
+     * Set username.
+     *
+     * @param string $username
+     */
+    public function setUsername($username)
+    {
+        $this->username = $username;
+    }
+
+    /**
+     * Get email.
+     *
+     * @return string
+     */
+    public function getEmail()
+    {
+        return $this->email;
+    }
+
+    /**
+     * Set email.
+     *
+     * @param string $email
+     */
+    public function setEmail($email)
+    {
+        $this->email = $email;
+    }
+
+    /**
+     * Get displayName.
+     *
+     * @return string
+     */
+    public function getDisplayName()
+    {
+        return $this->displayName;
+    }
+
+    /**
+     * Set displayName.
+     *
+     * @param string $displayName
+     */
+    public function setDisplayName($displayName)
+    {
+        $this->displayName = $displayName;
+    }
+
+    /**
+     * Get password.
+     *
+     * @return string
+     */
+    public function getPassword()
+    {
+        return 'shib';
+    }
+
+    /**
+     * Set password.
+     *
+     * @param string $password
+     */
+    public function setPassword($password)
+    {
+
+    }
+
+    /**
+     * Get state.
+     *
+     * @return int
+     */
+    public function getState()
+    {
+        return $this->state;
+    }
+
+    /**
+     * Set state.
+     *
+     * @param int $state
+     */
+    public function setState($state)
+    {
+        $this->state = $state;
+    }
+
+    /**
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->getDisplayName();
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Event/Listener/UserRoleSelectedEventAbstractListener.php b/src/UnicaenAuth/Event/Listener/UserRoleSelectedEventAbstractListener.php
index 9d56da0..24410c2 100644
--- a/src/UnicaenAuth/Event/Listener/UserRoleSelectedEventAbstractListener.php
+++ b/src/UnicaenAuth/Event/Listener/UserRoleSelectedEventAbstractListener.php
@@ -28,7 +28,6 @@ abstract class UserRoleSelectedEventAbstractListener implements ListenerAggregat
      * Renseigne les relations 'intervenant' et 'personnel' avant que l'objet soit persisté.
      *
      * @param UserRoleSelectedEvent $e
-     * @return
      */
     abstract public function postSelection(UserRoleSelectedEvent $e);
 
diff --git a/src/UnicaenAuth/Event/UserAuthenticatedEvent.php b/src/UnicaenAuth/Event/UserAuthenticatedEvent.php
index dfb893f..725bf30 100644
--- a/src/UnicaenAuth/Event/UserAuthenticatedEvent.php
+++ b/src/UnicaenAuth/Event/UserAuthenticatedEvent.php
@@ -2,6 +2,7 @@
 
 namespace UnicaenAuth\Event;
 
+use UnicaenAuth\Entity\Shibboleth\ShibUser;
 use UnicaenApp\Entity\Ldap\People;
 use Zend\EventManager\Event;
 use ZfcUser\Entity\UserInterface;
@@ -14,12 +15,26 @@ use ZfcUser\Entity\UserInterface;
 class UserAuthenticatedEvent extends Event
 {
     const PRE_PERSIST      = 'prePersist';
+
     const PARAM_DB_USER    = 'db_user';
     const PARAM_LDAP_USER  = 'ldap_user';
-    
+    const PARAM_SHIB_USER  = 'shib_user';
+
+    /**
+     * Spécifie l'entité utilisateur issue de la base de données.
+     *
+     * @param UserInterface $dbUser
+     * @return UserAuthenticatedEvent
+     */
+    public function setDbUser(UserInterface $dbUser)
+    {
+        $this->setParam(self::PARAM_DB_USER, $dbUser);
+        return $this;
+    }
+
     /**
      * Retourne l'entité utilisateur issue de la base de données.
-     * 
+     *
      * @return UserInterface
      */
     public function getDbUser()
@@ -27,9 +42,21 @@ class UserAuthenticatedEvent extends Event
         return $this->getParam(self::PARAM_DB_USER);
     }
 
+    /**
+     * Spécifie l'entité utilisateur issue de l'annuaire LDAP.
+     *
+     * @param People $ldapUser
+     * @return UserAuthenticatedEvent
+     */
+    public function setLdapUser(People $ldapUser)
+    {
+        $this->setParam(self::PARAM_LDAP_USER, $ldapUser);
+        return $this;
+    }
+
     /**
      * Retourne l'entité utilisateur issue de l'annuaire LDAP.
-     * 
+     *
      * @return People
      */
     public function getLdapUser()
@@ -38,26 +65,24 @@ class UserAuthenticatedEvent extends Event
     }
 
     /**
-     * Spécifie l'entité utilisateur issue de la base de données.
-     * 
-     * @param UserInterface $dbUser
+     * Spécifie l'entité utilisateur issue de l'authentification Shibboleth.
+     *
+     * @param ShibUser $shibUser
      * @return UserAuthenticatedEvent
      */
-    public function setDbUser(UserInterface $dbUser)
+    public function setShibUser(ShibUser $shibUser)
     {
-        $this->setParam(self::PARAM_DB_USER, $dbUser);
+        $this->setParam(self::PARAM_SHIB_USER, $shibUser);
         return $this;
     }
 
     /**
-     * Spécifie l'entité utilisateur issue de l'annuaire LDAP.
-     * 
-     * @param People $ldapUser
-     * @return UserAuthenticatedEvent
+     * Retourne l'entité utilisateur issue de l'authentification Shibboleth.
+     *
+     * @return ShibUser
      */
-    public function setLdapUser(People $ldapUser)
+    public function getShibUser()
     {
-        $this->setParam(self::PARAM_LDAP_USER, $ldapUser);
-        return $this;
+        return $this->getParam(self::PARAM_SHIB_USER);
     }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Options/ModuleOptions.php b/src/UnicaenAuth/Options/ModuleOptions.php
index c5f942a..4d3b5fb 100644
--- a/src/UnicaenAuth/Options/ModuleOptions.php
+++ b/src/UnicaenAuth/Options/ModuleOptions.php
@@ -1,4 +1,5 @@
 <?php
+
 namespace UnicaenAuth\Options;
 
 /**
@@ -18,6 +19,11 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      */
     protected $saveLdapUserInDatabase = false;
 
+    /**
+     * @var array
+     */
+    protected $shibboleth = [];
+
     /**
      * @var array
      */
@@ -27,9 +33,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      * @var string
      */
     protected $entityManagerName = 'doctrine.entitymanager.orm_default';
-
-
-
+    
     /**
      * set usernames allowed to make usurpation
      *
@@ -44,8 +48,6 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
         return $this;
     }
 
-
-
     /**
      * get usernames allowed to make usurpation
      *
@@ -56,8 +58,6 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
         return $this->usurpationAllowedUsernames;
     }
 
-
-
     /**
      * Spécifie si l'utilisateur authentifié doit être enregistré dans la base
      * de données de l'appli
@@ -72,9 +72,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
 
         return $this;
     }
-
-
-
+    
     /**
      * Retourne la valeur du flag spécifiant si l'utilisateur authentifié doit être
      * enregistré dans la base de données de l'appli
@@ -85,9 +83,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
     {
         return $this->saveLdapUserInDatabase;
     }
-
-
-
+    
     /**
      * set cas connection params
      *
@@ -101,9 +97,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
 
         return $this;
     }
-
-
-
+    
     /**
      * get cas connection params
      *
@@ -113,9 +107,31 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
     {
         return $this->cas;
     }
+    
+    /**
+     * set shibboleth connection params
+     *
+     * @param array $shibboleth
+     *
+     * @return ModuleOptions
+     */
+    public function setShibboleth(array $shibboleth = [])
+    {
+        $this->shibboleth = $shibboleth;
 
+        return $this;
+    }
 
-
+    /**
+     * get shibboleth connection params
+     *
+     * @return array
+     */
+    public function getShibboleth()
+    {
+        return $this->shibboleth;
+    }
+    
     /**
      * @return string
      */
@@ -123,9 +139,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
     {
         return $this->entityManagerName;
     }
-
-
-
+    
     /**
      * @param string $entityManagerName
      *
@@ -137,6 +151,4 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
 
         return $this;
     }
-
-
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/ShibService.php b/src/UnicaenAuth/Service/ShibService.php
new file mode 100644
index 0000000..f72de2f
--- /dev/null
+++ b/src/UnicaenAuth/Service/ShibService.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace UnicaenAuth\Service;
+
+use UnicaenApp\Exception\RuntimeException;
+use UnicaenAuth\Entity\Shibboleth\ShibUser;
+use UnicaenAuth\Options\ModuleOptions;
+
+/**
+ * Shibboleth service
+ *
+ * @author Unicaen
+ */
+class ShibService
+{
+    /**
+     * @var ModuleOptions
+     */
+    protected $options;
+
+    /**
+     * @var \UnicaenAuth\Entity\Shibboleth\ShibUser
+     */
+    protected $authenticatedUser;
+
+    /**
+     * @return string
+     */
+    static public function apacheConfigSnippet()
+    {
+        $text = <<<EOS
+<Location "/">
+    AuthType Shibboleth
+    ShibRequestSetting requireSession false
+    Require shibboleth
+</Location>
+<Location "/auth/shibboleth">
+        AuthType Shibboleth
+        ShibRequestSetting requireSession true
+        Require shibboleth
+</Location>
+EOS;
+        return $text;
+    }
+
+    /**
+     * @return boolean
+     */
+    public function isShibbolethEnable()
+    {
+        $options = $this->options->getShibboleth();
+
+        return array_key_exists('enable', $options) && (bool) $options['enable'];
+    }
+
+    /**
+     * @return ShibUser|null
+     */
+    public function getAuthenticatedUser()
+    {
+        if ($this->authenticatedUser === null) {
+            if (empty($_SERVER['REMOTE_USER'])) {
+                return null;
+            }
+            $this->authenticatedUser = $this->createShibUser();
+        }
+
+        return $this->authenticatedUser;
+    }
+
+    /**
+     * @return ShibUser
+     */
+    private function createShibUser()
+    {
+        $eppn = $_SERVER['REMOTE_USER'];
+
+        if (isset($_SERVER['supannEtuId'])) {
+            $id = $_SERVER['supannEtuId'];
+        } elseif (isset($_SERVER['supannEmpId'])) {
+            $id = $_SERVER['supannEmpId'];
+        } else {
+            throw new RuntimeException('Un au moins des attributs suivants doivent exister dans $_SERVER : supannEtuId, supannEmpId.');
+        }
+
+        $mail = null;
+        if (isset($_SERVER['mail'])) {
+            $mail = $_SERVER['mail'];
+        }
+
+        $displayName = null;
+        if (isset($_SERVER['displayName'])) {
+            $displayName = $_SERVER['displayName'];
+        }
+
+        $shibUser = new ShibUser();
+        $shibUser->setId($id);
+        $shibUser->setUsername($eppn);
+        $shibUser->setDisplayName($displayName);
+        $shibUser->setEmail($mail);
+
+        return $shibUser;
+    }
+
+    /**
+     * @param ModuleOptions $options
+     */
+    public function setOptions(ModuleOptions $options)
+    {
+        $this->options = $options;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/ShibServiceFactory.php b/src/UnicaenAuth/Service/ShibServiceFactory.php
new file mode 100644
index 0000000..418f717
--- /dev/null
+++ b/src/UnicaenAuth/Service/ShibServiceFactory.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace UnicaenAuth\Service;
+
+use UnicaenAuth\Options\ModuleOptions;
+use Zend\ServiceManager\ServiceLocatorInterface;
+
+class ShibServiceFactory
+{
+    public function __invoke(ServiceLocatorInterface $sl)
+    {
+        /** @var ModuleOptions $options */
+        $options = $sl->get('unicaen-auth_module_options');
+
+        $service = new ShibService();
+        $service->setOptions($options);
+
+        return $service;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/Traits/ShibServiceAwareTrait.php b/src/UnicaenAuth/Service/Traits/ShibServiceAwareTrait.php
new file mode 100644
index 0000000..825e898
--- /dev/null
+++ b/src/UnicaenAuth/Service/Traits/ShibServiceAwareTrait.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace UnicaenAuth\Service\Traits;
+
+use UnicaenAuth\Service\ShibService;
+
+trait ShibServiceAwareTrait
+{
+    /**
+     * @var ShibService
+     */
+    protected $shibService;
+
+    /**
+     * @param ShibService $shibService
+     */
+    public function setShibService(ShibService $shibService)
+    {
+        $this->shibService = $shibService;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/Traits/UserServiceAwareTrait.php b/src/UnicaenAuth/Service/Traits/UserServiceAwareTrait.php
new file mode 100644
index 0000000..74a268f
--- /dev/null
+++ b/src/UnicaenAuth/Service/Traits/UserServiceAwareTrait.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace UnicaenAuth\Service\Traits;
+
+use UnicaenAuth\Service\User as UserService;
+
+trait UserServiceAwareTrait
+{
+    /**
+     * @var UserService
+     */
+    protected $userService;
+
+    /**
+     * @param UserService $userService
+     */
+    public function setUserService(UserService $userService)
+    {
+        $this->userService = $userService;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/User.php b/src/UnicaenAuth/Service/User.php
index c1f2f67..81875d7 100644
--- a/src/UnicaenAuth/Service/User.php
+++ b/src/UnicaenAuth/Service/User.php
@@ -1,18 +1,19 @@
 <?php
+
 namespace UnicaenAuth\Service;
 
+use UnicaenAuth\Event\UserAuthenticatedEvent;
 use PDOException;
-use UnicaenApp\Exception;
+use UnicaenApp\Entity\Ldap\People;
+use UnicaenApp\Exception\RuntimeException;
 use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
+use UnicaenAuth\Entity\Shibboleth\ShibUser;
 use UnicaenAuth\Options\ModuleOptions;
-use UnicaenAuth\Event\UserAuthenticatedEvent;
-use Zend\ServiceManager\ServiceLocatorAwareInterface;
-use Zend\ServiceManager\ServiceLocatorAwareTrait;
-use Zend\ServiceManager\ServiceManager;
-use Zend\ServiceManager\ServiceManagerAwareInterface;
 use Zend\EventManager\EventManagerAwareInterface;
 use Zend\EventManager\EventManagerInterface;
-use ZfcUser\Authentication\Adapter\AdapterChainEvent as AuthEvent;
+use Zend\ServiceManager\ServiceLocatorAwareInterface;
+use Zend\ServiceManager\ServiceLocatorAwareTrait;
+use ZfcUser\Entity\UserInterface;
 use ZfcUser\Options\AuthenticationOptionsInterface;
 use ZfcUser\Options\ModuleOptions as ZfcUserModuleOptions;
 
@@ -20,17 +21,13 @@ use ZfcUser\Options\ModuleOptions as ZfcUserModuleOptions;
  * Service d'enregistrement dans la table des utilisateurs de l'application
  * de l'utilisateur authentifié avec succès.
  *
- * Est notifié via la méthode 'userAuthenticated()' lorsque l'authentification
- * est terminée avec succès.
- *
  * @see \UnicaenAuth\Authentication\Adapter\AbstractFactory
- * @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr>
+ * @author Unicaen
  */
 class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
 {
     use ServiceLocatorAwareTrait;
 
-
     const EVENT_USER_AUTHENTICATED_PRE_PERSIST = 'userAuthenticated.prePersist';
 
     /**
@@ -56,14 +53,35 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
     /**
      * Save authenticated user in database from LDAP data.
      *
+     * @param UserInterface|People $userData
      * @return bool
      */
-    public function userAuthenticated(AuthEvent $e)
+    public function userAuthenticated($userData)
     {
         if (!$this->getOptions()->getSaveLdapUserInDatabase()) {
             return false;
         }
-        if (!($username = $e->getIdentity())) {
+
+        switch (true) {
+            case $userData instanceof People:
+                $username = $userData->getSupannAliasLogin();
+                $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();
+                $email = $userData->getEmail();
+                $password = 'shib';
+                $state = 1;
+                break;
+            default:
+                throw new RuntimeException("A implémenter!!");
+                break;
+        }
+
+        if (!$username) {
             return false;
         }
 
@@ -73,18 +91,13 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
         }
 
         if (!is_string($username)) {
-            throw new Exception("Identité rencontrée inattendue.");
-        }
-
-        // recherche de l'individu dans l'annuaire LDAP
-        $ldapPeople = $this->getLdapPeopleMapper()->findOneByUsername($username);
-        if (!$ldapPeople) {
-            return false;
+            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();
@@ -95,30 +108,44 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
             else {
                 $method = 'update';
             }
-            $entity->setEmail($ldapPeople->getMail());
-            $entity->setDisplayName($ldapPeople->getDisplayName());
-            $entity->setPassword('ldap');
-            $entity->setState(in_array('deactivated', ldap_explode_dn($ldapPeople->getDn(), 1)) ? 0 : 1);
-
-            // déclenche l'événement donnant aux applications clientes l'opportunité de modifier l'entité
-            // utilisateur avant qu'elle ne soit persistée
-            $event = new UserAuthenticatedEvent(UserAuthenticatedEvent::PRE_PERSIST);
-            $event
-                    ->setDbUser($entity)
-                    ->setLdapUser($ldapPeople)
-                    ->setTarget($this);
-            $this->getEventManager()->trigger($event);
+            $entity->setEmail($email);
+            $entity->setDisplayName($userData->getDisplayName());
+            $entity->setPassword($password);
+            $entity->setState($state);
+
+            // pre-persist
+            $this->triggerUserAuthenticatedEvent($entity, $userData);
 
             // persist
             $mapper->$method($entity);
         }
         catch (PDOException $pdoe) {
-            throw new Exception("Impossible d'enregistrer l'utilisateur authentifié dans la base de données.", null, $pdoe);
+            throw new RuntimeException("Impossible d'enregistrer l'utilisateur authentifié dans la base de données.", null, $pdoe);
         }
 
         return true;
     }
 
+    /**
+     * Déclenche l'événement donnant aux applications clientes l'opportunité de modifier l'entité
+     * utilisateur avant qu'elle ne soit persistée.
+     *
+     * @param mixed $entity
+     * @param People|ShibUser $userData
+     */
+    private function triggerUserAuthenticatedEvent($entity, $userData)
+    {
+        $event = new UserAuthenticatedEvent(UserAuthenticatedEvent::PRE_PERSIST);
+        $event->setTarget($this);
+        $event->setDbUser($entity);
+        if ($userData instanceof People) {
+            $event->setLdapUser($userData);
+        } elseif ($userData instanceof ShibUser) {
+            $event->setShibUser($userData);
+        }
+
+        $this->getEventManager()->trigger($event);
+    }
 
     /**
      * Retrieve the event manager
@@ -136,7 +163,7 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
      * Inject an EventManager instance
      *
      * @param  EventManagerInterface $eventManager
-     * @return void
+     * @return self
      */
     public function setEventManager(EventManagerInterface $eventManager)
     {
@@ -148,33 +175,9 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
         return $this;
     }
 
-    /**
-     * get ldap people mapper
-     *
-     * @return LdapPeopleMapper
-     */
-    public function getLdapPeopleMapper()
-    {
-        if (null === $this->ldapPeopleMapper) {
-            $this->ldapPeopleMapper = $this->getServiceLocator()->get('ldap_people_mapper');
-        }
-        return $this->ldapPeopleMapper;
-    }
-
-    /**
-     * set ldap people mapper
-     *
-     * @param LdapPeopleMapper $mapper
-     * @return User
-     */
-    public function setLdapPeopleMapper(LdapPeopleMapper $mapper)
-    {
-        $this->ldapPeopleMapper = $mapper;
-        return $this;
-    }
-
     /**
      * @param ModuleOptions $options
+     * @return self
      */
     public function setOptions(ModuleOptions $options)
     {
@@ -195,6 +198,7 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
 
     /**
      * @param ZfcUserModuleOptions $options
+     * @return self
      */
     public function setZfcUserOptions(ZfcUserModuleOptions $options)
     {
diff --git a/src/UnicaenAuth/View/Helper/ShibConnectViewHelper.php b/src/UnicaenAuth/View/Helper/ShibConnectViewHelper.php
new file mode 100644
index 0000000..72bde61
--- /dev/null
+++ b/src/UnicaenAuth/View/Helper/ShibConnectViewHelper.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace UnicaenAuth\View\Helper;
+
+use UnicaenAuth\Service\Traits\ShibServiceAwareTrait;
+use Zend\View\Helper\AbstractHelper;
+use Zend\View\Renderer\PhpRenderer;
+
+/**
+ * Aide de vue dessinant le bouton de connexion via Shibboleth,
+ * si l'authentification Shibboleth est activée.
+ *
+ * @method PhpRenderer getView()
+ * @author Unicaen
+ */
+class ShibConnectViewHelper extends AbstractHelper
+{
+    use ShibServiceAwareTrait;
+
+    /**
+     * @return string
+     */
+    public function __toString()
+    {
+        try {
+            return $this->render();
+        } catch (\Exception $e) {
+            return '<p>' . $e->getMessage() . '</p><p>' . $e->getTraceAsString() . '</p>';
+        }
+    }
+
+    /**
+     * @return string
+     */
+    private function render()
+    {
+        if (! $this->shibService->isShibbolethEnable()) {
+            return '';
+        }
+
+        $shibUrl = $this->getView()->url('auth/shibboleth', [], ['query' => $this->getView()->queryParams()], true);
+
+        return <<<EOS
+<a href="$shibUrl" class="btn btn-info btn-lg">Se connecter via la fédération d'identité</a>
+EOS;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/View/Helper/ShibConnectViewHelperFactory.php b/src/UnicaenAuth/View/Helper/ShibConnectViewHelperFactory.php
new file mode 100644
index 0000000..f9828c0
--- /dev/null
+++ b/src/UnicaenAuth/View/Helper/ShibConnectViewHelperFactory.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace UnicaenAuth\View\Helper;
+
+use UnicaenAuth\Service\ShibService;
+use Zend\View\HelperPluginManager;
+
+class ShibConnectViewHelperFactory
+{
+    /**
+     * @param HelperPluginManager $hpm
+     * @return ShibConnectViewHelper
+     */
+    public function __invoke(HelperPluginManager $hpm)
+    {
+        /** @var ShibService $shibService */
+        $shibService = $hpm->getServiceLocator()->get(ShibService::class);
+
+        $helper = new ShibConnectViewHelper();
+        $helper->setShibService($shibService);
+
+        return $helper;
+    }
+}
\ No newline at end of file
diff --git a/view/unicaen-auth/auth/shibboleth.phtml b/view/unicaen-auth/auth/shibboleth.phtml
new file mode 100644
index 0000000..63addc6
--- /dev/null
+++ b/view/unicaen-auth/auth/shibboleth.phtml
@@ -0,0 +1,23 @@
+<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>
+
+<p>
+    Vous devez activer l'authentification Shibboleth dans la config :
+</p>
+<pre>
+    'unicaen-auth' => [
+        ...
+        'shibboleth' => [
+            '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
diff --git a/view/zfc-user/user/login.phtml b/view/zfc-user/user/login.phtml
index e52f5d7..fce5180 100644
--- a/view/zfc-user/user/login.phtml
+++ b/view/zfc-user/user/login.phtml
@@ -48,6 +48,9 @@ $form->setAttributes([
 
     <?php echo $this->form()->closeTag() ?>
 
+    <!-- Bouton de connexion Shibboleth -->
+    <?php echo $this->shibConnect() ?>
+
     <?php if ($this->enableRegistration) : ?>
         <?php echo $this->translate("Not registered?"); ?> <a href="<?php echo $this->url('zfcuser/register') . ($this->redirect ? '?redirect=' . $this->redirect : '') ?>"><?php echo $this->translate("Sign up!"); ?></a>
     <?php endif; ?>
-- 
GitLab