Commit e3e9cf98 authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

Authentification Shibboleth

parent ffe7b256
......@@ -4,7 +4,7 @@
"repositories": [
{
"type": "composer",
"url": "https://dev.unicaen.fr/packagist"
"url": "https://gest.unicaen.fr/packagist"
}
],
"require": {
......
<?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',
......
......@@ -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.
......
<?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 */
......
<?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;
......
......@@ -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
<?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;
}
}
<?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
<?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
......@@ -205,7 +205,7 @@ abstract class AbstractUser implements UserInterface, ProviderInterface
/**
* Get role.
*
* @return RoleInterface[]
* @return Collection
*/
public function getRoles()
{
......
<?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