Commit cb5d0bbf authored by Laurent Lécluse's avatar Laurent Lécluse
Browse files

Merge remote-tracking branch 'origin/master'

parents 345436e2 7ffe2396
<?php <?php
use UnicaenAuth\Authentication\Adapter\ShibSimulatorAdapter;
use UnicaenAuth\Authentication\Storage\ShibSimulatorStorage;
use UnicaenAuth\Controller\AuthControllerFactory; use UnicaenAuth\Controller\AuthControllerFactory;
use UnicaenAuth\Service\ShibService; use UnicaenAuth\Service\ShibService;
use UnicaenAuth\Service\ShibServiceFactory; use UnicaenAuth\Service\ShibServiceFactory;
use UnicaenAuth\Service\UserContextFactory; use UnicaenAuth\Service\UserContextFactory;
use UnicaenAuth\View\Helper\ShibConnectViewHelperFactory; use UnicaenAuth\View\Helper\ShibConnectViewHelperFactory;
use UnicaenAuth\View\Helper\UserUsurpationHelperFactory;
$settings = [ $settings = [
/** /**
...@@ -373,6 +376,7 @@ return [ ...@@ -373,6 +376,7 @@ return [
'UnicaenAuth\Authentication\Storage\Db' => 'UnicaenAuth\Authentication\Storage\Db', 'UnicaenAuth\Authentication\Storage\Db' => 'UnicaenAuth\Authentication\Storage\Db',
'UnicaenAuth\Authentication\Storage\Ldap' => 'UnicaenAuth\Authentication\Storage\Ldap', 'UnicaenAuth\Authentication\Storage\Ldap' => 'UnicaenAuth\Authentication\Storage\Ldap',
'UnicaenAuth\Authentication\Storage\Shib' => 'UnicaenAuth\Authentication\Storage\Shib', 'UnicaenAuth\Authentication\Storage\Shib' => 'UnicaenAuth\Authentication\Storage\Shib',
'UnicaenAuth\Authentication\Storage\ShibSimulatorStorage' => ShibSimulatorStorage::class,
'UnicaenAuth\View\RedirectionStrategy' => 'UnicaenAuth\View\RedirectionStrategy', 'UnicaenAuth\View\RedirectionStrategy' => 'UnicaenAuth\View\RedirectionStrategy',
'UnicaenAuth\Service\User' => 'UnicaenAuth\Service\User', 'UnicaenAuth\Service\User' => 'UnicaenAuth\Service\User',
'UnicaenAuth\Service\CategoriePrivilege' => 'UnicaenAuth\Service\CategoriePrivilegeService', 'UnicaenAuth\Service\CategoriePrivilege' => 'UnicaenAuth\Service\CategoriePrivilegeService',
...@@ -432,6 +436,7 @@ return [ ...@@ -432,6 +436,7 @@ return [
'userInfo' => 'UnicaenAuth\View\Helper\UserInfoFactory', 'userInfo' => 'UnicaenAuth\View\Helper\UserInfoFactory',
'userProfileSelect' => 'UnicaenAuth\View\Helper\UserProfileSelectFactory', 'userProfileSelect' => 'UnicaenAuth\View\Helper\UserProfileSelectFactory',
'userProfileSelectRadioItem' => 'UnicaenAuth\View\Helper\UserProfileSelectRadioItemFactory', 'userProfileSelectRadioItem' => 'UnicaenAuth\View\Helper\UserProfileSelectRadioItemFactory',
'userUsurpation' => UserUsurpationHelperFactory::class,
'shibConnect' => ShibConnectViewHelperFactory::class, 'shibConnect' => ShibConnectViewHelperFactory::class,
], ],
'invokables' => [ 'invokables' => [
......
<?php <?php
namespace UnicaenAuth\Authentication\Adapter; namespace UnicaenAuth\Authentication\Adapter;
use phpCAS; use phpCAS;
use UnicaenApp\Exception; use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
use UnicaenAuth\Options\ModuleOptions; use UnicaenAuth\Options\ModuleOptions;
use UnicaenAuth\Service\User;
use Zend\Authentication\Exception\UnexpectedValueException; use Zend\Authentication\Exception\UnexpectedValueException;
use Zend\Authentication\Result as AuthenticationResult; use Zend\Authentication\Result as AuthenticationResult;
use Zend\EventManager\EventManager; use Zend\EventManager\EventManager;
...@@ -48,6 +50,11 @@ class Cas extends AbstractAdapter implements ServiceManagerAwareInterface, Event ...@@ -48,6 +50,11 @@ class Cas extends AbstractAdapter implements ServiceManagerAwareInterface, Event
*/ */
protected $casClient; protected $casClient;
/**
* @var LdapPeopleMapper
*/
protected $ldapPeopleMapper;
/** /**
* Réalise l'authentification. * Réalise l'authentification.
* *
...@@ -94,7 +101,12 @@ class Cas extends AbstractAdapter implements ServiceManagerAwareInterface, Event ...@@ -94,7 +101,12 @@ class Cas extends AbstractAdapter implements ServiceManagerAwareInterface, Event
$e->setCode(AuthenticationResult::SUCCESS) $e->setCode(AuthenticationResult::SUCCESS)
->setMessages(['Authentication successful.']); ->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 ...@@ -186,6 +198,31 @@ class Cas extends AbstractAdapter implements ServiceManagerAwareInterface, Event
return $this->options; 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 * Get service manager
* *
......
...@@ -12,11 +12,15 @@ use Zend\ServiceManager\ServiceLocatorInterface; ...@@ -12,11 +12,15 @@ use Zend\ServiceManager\ServiceLocatorInterface;
*/ */
class ChainServiceFactory implements FactoryInterface class ChainServiceFactory implements FactoryInterface
{ {
protected $storages = [ private $mandatoryStorages = [
200 => 'UnicaenAuth\Authentication\Storage\Ldap', 200 => 'UnicaenAuth\Authentication\Storage\Ldap',
100 => 'UnicaenAuth\Authentication\Storage\Db', 100 => 'UnicaenAuth\Authentication\Storage\Db',
76 => 'UnicaenAuth\Authentication\Storage\ShibSimulatorStorage',
75 => 'UnicaenAuth\Authentication\Storage\Shib',
]; ];
protected $storages = [];
/** /**
* Create service * Create service
* *
...@@ -27,7 +31,10 @@ class ChainServiceFactory implements FactoryInterface ...@@ -27,7 +31,10 @@ class ChainServiceFactory implements FactoryInterface
{ {
$chain = new Chain(); $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); $storage = $serviceLocator->get($name);
$chain->getEventManager()->attach('read', [$storage, 'read'], $priority); $chain->getEventManager()->attach('read', [$storage, 'read'], $priority);
$chain->getEventManager()->attach('write', [$storage, 'write'], $priority); $chain->getEventManager()->attach('write', [$storage, 'write'], $priority);
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
namespace UnicaenAuth\Authentication\Storage; namespace UnicaenAuth\Authentication\Storage;
use UnicaenAuth\Entity\Shibboleth\ShibUser; use UnicaenAuth\Entity\Shibboleth\ShibUser;
use UnicaenAuth\Options\ModuleOptions;
use UnicaenAuth\Service\ShibService; use UnicaenAuth\Service\ShibService;
use Zend\Authentication\Storage\Session; use Zend\Authentication\Storage\Session;
use Zend\Authentication\Storage\StorageInterface; use Zend\Authentication\Storage\StorageInterface;
...@@ -25,11 +24,6 @@ class Shib implements ChainableStorage, ServiceLocatorAwareInterface ...@@ -25,11 +24,6 @@ class Shib implements ChainableStorage, ServiceLocatorAwareInterface
*/ */
protected $storage; protected $storage;
/**
* @var ModuleOptions
*/
protected $options;
/** /**
* @var ShibUser * @var ShibUser
*/ */
...@@ -51,15 +45,30 @@ class Shib implements ChainableStorage, ServiceLocatorAwareInterface ...@@ -51,15 +45,30 @@ class Shib implements ChainableStorage, ServiceLocatorAwareInterface
*/ */
public function read(ChainEvent $e) public function read(ChainEvent $e)
{ {
/** @var ShibService $shib */ $shibUser = $this->getAuthenticatedUser();
$shib = $this->getServiceLocator()->get(ShibService::class);
$shibUser = $shib->getAuthenticatedUser();
$e->addContents('shib', $shibUser); $e->addContents('shib', $shibUser);
return $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 * Writes $contents to storage
* *
......
<?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
...@@ -23,6 +23,18 @@ class AuthController extends AbstractActionController ...@@ -23,6 +23,18 @@ class AuthController extends AbstractActionController
use UserServiceAwareTrait; 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 * @return Response|array
*/ */
public function shibbolethAction() public function shibbolethAction()
...@@ -30,44 +42,69 @@ class AuthController extends AbstractActionController ...@@ -30,44 +42,69 @@ class AuthController extends AbstractActionController
$operation = $this->params()->fromRoute('operation'); $operation = $this->params()->fromRoute('operation');
if ($operation === 'deconnexion') { if ($operation === 'deconnexion') {
return $this->shibbolethLogout();
}
$redirectUrl = $this->params()->fromQuery('redirect', '/');
// enclenchement de la simulation shibboleth éventuellement activée dans la config
if ($simulate = $this->shibService->getShibbolethSimulate()) {
$this->setStoredAuthenticatedUsername($simulate['eppn']); // tout simplement!
return $this->redirect()->toUrl($redirectUrl);
}
$shibUser = $this->shibService->getAuthenticatedUser();
if ($shibUser === null) {
return []; // une page d'aide s'affichera si les données issues de Shibboleth attendues sont absentes
}
// arrivé ici, l'authentification shibboleth a été faite en bonne et due forme et a réussie.
$this->setStoredAuthenticatedUsername($shibUser->getUsername());
$this->userService->userAuthenticated($shibUser);
return $this->redirect()->toUrl($redirectUrl);
}
/**
* Déconnexion Shibboleth.
*
* @return array|Response
*/
private function shibbolethLogout()
{
// déconnexion applicative quoiqu'il arrive // déconnexion applicative quoiqu'il arrive
$this->zfcUserAuthentication()->getAuthAdapter()->resetAdapters(); $this->zfcUserAuthentication()->getAuthAdapter()->resetAdapters();
$this->zfcUserAuthentication()->getAuthAdapter()->logoutAdapters(); $this->zfcUserAuthentication()->getAuthAdapter()->logoutAdapters();
$this->zfcUserAuthentication()->getAuthService()->clearIdentity(); $this->zfcUserAuthentication()->getAuthService()->clearIdentity();
// déconnexion Shibboleth le cas échéant // déconnexion Shibboleth le cas échéant
if ($this->shibService->isShibbolethEnable()) { 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]); $homeUrl = $this->url()->fromRoute('home', [], ['force_canonical' => true]);
$returnAbsoluteUrl = $this->params()->fromQuery('return', $homeUrl); $returnAbsoluteUrl = $this->params()->fromQuery('return', $homeUrl);
return $this->redirect()->toUrl($this->shibService->getLogoutUrl($returnAbsoluteUrl)); return $this->redirect()->toUrl($this->shibService->getLogoutUrl($returnAbsoluteUrl));
} else { } else {
return []; // une page d'aide s'affichera return []; // une page d'aide s'affichera
} }
} }
$shibUser = $this->shibService->getAuthenticatedUser(); /**
* @param string $username
if ($shibUser === null) { */
return []; // une page d'aide s'affichera private function setStoredAuthenticatedUsername($username)
} {
/** @var AuthenticationService $authService */ /** @var AuthenticationService $authService */
$authService = $this->getServiceLocator()->get('zfcuser_auth_service'); $authService = $this->getServiceLocator()->get('zfcuser_auth_service');
try { try {
$authService->getStorage()->write($shibUser->getId()); $authService->getStorage()->write($username);
} catch (ExceptionInterface $e) { } catch (ExceptionInterface $e) {
throw new RuntimeException("Impossible d'écrire dans le storage"); throw new RuntimeException("Impossible d'écrire dans le storage");
} }
$this->userService->userAuthenticated($shibUser);
$redirectUrl = $this->params()->fromQuery('redirect', '/');
return $this->redirect()->toUrl($redirectUrl);
}
public function shibboleth()
{
} }
} }
\ No newline at end of file
...@@ -2,16 +2,30 @@ ...@@ -2,16 +2,30 @@
namespace UnicaenAuth\Controller; 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\Request;
use Zend\Http\Response;
use Zend\Mvc\Controller\AbstractActionController; use Zend\Mvc\Controller\AbstractActionController;
use ZfcUser\Mapper\User as UserMapper;
/** /**
*
*
* @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr> * @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr>
*/ */
class UtilisateurController extends AbstractActionController class UtilisateurController extends AbstractActionController
{ {
/**
* @var LdapPeopleMapper
*/
protected $ldapPeopleMapper;
/** /**
* Traite les requêtes AJAX POST de sélection d'un profil utilisateur. * 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. * La sélection est mémorisé en session par le service AuthUserContext.
...@@ -47,10 +61,125 @@ class UtilisateurController extends AbstractActionController ...@@ -47,10 +61,125 @@ class UtilisateurController extends AbstractActionController
} }
/** /**
* @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