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

Merge branch 'release-3.2.0'

parents ff93d62a 06ac47cc
Pipeline #9952 passed with stage
in 14 seconds
...@@ -45,3 +45,6 @@ Première version officielle sous ZF3. ...@@ -45,3 +45,6 @@ Première version officielle sous ZF3.
- Possibilité de connaître la source de l'authentification (db, ldap, cas, shib). - Possibilité de connaître la source de l'authentification (db, ldap, cas, shib).
- Possibilité de stopper l'usurpation en cours pour revenir à l'identité d'origine. - Possibilité de stopper l'usurpation en cours pour revenir à l'identité d'origine.
- [FIX] Usurpation d'un compte local en BDD. - [FIX] Usurpation d'un compte local en BDD.
- [FIX] Lorsque l'usurpateur stoppait l'usurpation, il ne récupérait pas son dernier endossé
- [FIX] En cas d'usurpation puis de déconnexion puis de reconnexion, on revenait dans la peau de l'usurpé sans pouvoir
stopper l'usurpation
\ No newline at end of file
...@@ -194,22 +194,33 @@ $settings = [ ...@@ -194,22 +194,33 @@ $settings = [
* Ex: identifiant de l'usager au sein du référentiel établissement, transmis par l'IDP via le supannRefId. * Ex: identifiant de l'usager au sein du référentiel établissement, transmis par l'IDP via le supannRefId.
*/ */
'shib_user_id_extractor' => [ 'shib_user_id_extractor' => [
/*
// domaine (ex: 'unicaen.fr') de l'EPPN (ex: hochonp@unicaen.fr') // domaine (ex: 'unicaen.fr') de l'EPPN (ex: hochonp@unicaen.fr')
'unicaen.fr' => [ 'unicaen.fr' => [
[ [
// nom du 1er attribut recherché // nom du 1er attribut recherché
'name' => 'supannRefId', // ex: '{REFERENTIEL}1234;{ISO15693}044D1AZE7A5P80' 'name' => 'supannRefId', // ex: '{OCTOPUS:ID}1234;{ISO15693}044D1AZE7A5P80'
// pattern éventuel pour extraire la partie intéressante // pattern éventuel pour extraire la partie intéressante
'preg_match_pattern' => '|\{REFERENTIEL\}(\d+)|', // ex: permet d'extraire '1234' 'preg_match_pattern' => '|\{OCTOPUS:ID\}(\d+)|', // ex: permet d'extraire '1234'
], ],
[ [
// nom du 2e attribut recherché // nom du 2e attribut recherché, si le 1er est introuvable
'name' => 'supannEmpId', 'name' => 'supannEmpId',
// pas de pattern donc valeur directement utilisable // pas de pattern donc valeur brute utilisée
'preg_match_pattern' => null, 'preg_match_pattern' => null,
], ],
[ [
// nom du 3e attribut recherché // nom du 3e attribut recherché, si le 2e est introuvable
'name' => 'supannEtuId',
],
],
*/
// config de repli pour tous les autres domaines
'default' => [
[
'name' => 'supannEmpId',
],
[
'name' => 'supannEtuId', 'name' => 'supannEtuId',
], ],
], ],
......
...@@ -123,21 +123,30 @@ return [ ...@@ -123,21 +123,30 @@ return [
*/ */
'shib_user_id_extractor' => [ 'shib_user_id_extractor' => [
// domaine (ex: 'unicaen.fr') de l'EPPN (ex: hochonp@unicaen.fr') // domaine (ex: 'unicaen.fr') de l'EPPN (ex: hochonp@unicaen.fr')
'unicaen.fr' => [ // 'unicaen.fr' => [
[ // [
// nom du 1er attribut recherché // // nom du 1er attribut recherché
'name' => 'supannRefId', // ex: '{REFERENTIEL}1234;{ISO15693}044D1AZE7A5P80' // 'name' => 'supannRefId', // ex: '{OCTOPUS:ID}1234;{ISO15693}044D1AZE7A5P80'
// pattern éventuel pour extraire la partie intéressante // // pattern éventuel pour extraire la partie intéressante
'preg_match_pattern' => '|\{REFERENTIEL\}(\d+)|', // ex: permet d'extraire '1234' // 'preg_match_pattern' => '|\{OCTOPUS:ID\}(\d+)|', // ex: permet d'extraire '1234'
], // ],
// [
// // nom du 2e attribut recherché
// 'name' => 'supannEmpId',
// // pas de pattern donc valeur brute utilisée
// 'preg_match_pattern' => null,
// ],
// [
// // nom du 3e attribut recherché
// 'name' => 'supannEtuId',
// ],
// ],
// config de repli pour tous les autres domaines
'default' => [
[ [
// nom du 2e attribut recherché
'name' => 'supannEmpId', 'name' => 'supannEmpId',
// pas de pattern donc valeur directement utilisable
'preg_match_pattern' => null,
], ],
[ [
// nom du 3e attribut recherché
'name' => 'supannEtuId', 'name' => 'supannEtuId',
], ],
], ],
......
...@@ -28,11 +28,11 @@ class LocalAdapterFactory ...@@ -28,11 +28,11 @@ class LocalAdapterFactory
// db adapter // db adapter
if (array_key_exists('db', $localConfig)) { if (array_key_exists('db', $localConfig)) {
$this->attachSubAdapter($localAdapter, $container, $localConfig['db']); $this->attachSubAdapter($localAdapter, $container, $localConfig['db'], 20); // en 1er
} }
// ldap adapter // ldap adapter
if (array_key_exists('ldap', $localConfig)) { if (array_key_exists('ldap', $localConfig)) {
$this->attachSubAdapter($localAdapter, $container, $localConfig['ldap']); $this->attachSubAdapter($localAdapter, $container, $localConfig['ldap'], 10); // en 2e
} }
return $localAdapter; return $localAdapter;
...@@ -42,8 +42,9 @@ class LocalAdapterFactory ...@@ -42,8 +42,9 @@ class LocalAdapterFactory
* @param LocalAdapter $localAdapter * @param LocalAdapter $localAdapter
* @param ContainerInterface $container * @param ContainerInterface $container
* @param array $config * @param array $config
* @param int $priority
*/ */
private function attachSubAdapter(LocalAdapter $localAdapter, ContainerInterface $container, array $config) private function attachSubAdapter(LocalAdapter $localAdapter, ContainerInterface $container, array $config, int $priority)
{ {
$enabled = isset($config['enabled']) && $config['enabled'] === true; $enabled = isset($config['enabled']) && $config['enabled'] === true;
if (! $enabled) { if (! $enabled) {
...@@ -52,7 +53,7 @@ class LocalAdapterFactory ...@@ -52,7 +53,7 @@ class LocalAdapterFactory
/** @var AbstractAdapter $subAdapter */ /** @var AbstractAdapter $subAdapter */
$subAdapter = $container->get($config['adapter']); $subAdapter = $container->get($config['adapter']);
$subAdapter->attach($localAdapter->getEventManager()); $subAdapter->attach($localAdapter->getEventManager(), $priority);
} }
} }
\ No newline at end of file
...@@ -54,4 +54,14 @@ class Shib extends AbstractStorage ...@@ -54,4 +54,14 @@ class Shib extends AbstractStorage
return $this->shibService->getAuthenticatedUser(); return $this->shibService->getAuthenticatedUser();
} }
/**
* @inheritDoc
*/
public function clear(ChainEvent $e)
{
parent::clear($e);
$this->shibService->deactivateUsurpation();
}
} }
...@@ -6,6 +6,7 @@ use BjyAuthorize\Provider\Role\ProviderInterface; ...@@ -6,6 +6,7 @@ use BjyAuthorize\Provider\Role\ProviderInterface;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use UnicaenAuth\Entity\Db\Role;
/** /**
* User entity abstract mother class. * User entity abstract mother class.
...@@ -71,6 +72,13 @@ abstract class AbstractUser implements UserInterface, ProviderInterface ...@@ -71,6 +72,13 @@ abstract class AbstractUser implements UserInterface, ProviderInterface
*/ */
protected $roles; protected $roles;
/**
* @var AbstractRole
* @ORM\ManyToOne(targetEntity="UnicaenAuth\Entity\Db\Role")
* @ORM\JoinColumn(name="last_role_id", referencedColumnName="id")
*/
protected $lastRole;
/** /**
* Initialies the roles variable. * Initialies the roles variable.
*/ */
...@@ -259,6 +267,25 @@ abstract class AbstractUser implements UserInterface, ProviderInterface ...@@ -259,6 +267,25 @@ abstract class AbstractUser implements UserInterface, ProviderInterface
return $this; return $this;
} }
/**
* @return AbstractRole|null
*/
public function getLastRole()
{
return $this->lastRole;
}
/**
* @param AbstractRole|null $lastRole
* @return self
*/
public function setLastRole(AbstractRole $lastRole = null)
{
$this->lastRole = $lastRole;
return $this;
}
/** /**
* Retourne true si cet utilisateur est local. * Retourne true si cet utilisateur est local.
* *
......
...@@ -2,11 +2,14 @@ ...@@ -2,11 +2,14 @@
namespace UnicaenAuth\Event\Listener; namespace UnicaenAuth\Event\Listener;
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
use UnicaenApp\Service\EntityManagerAwareInterface; use UnicaenApp\Service\EntityManagerAwareInterface;
use UnicaenApp\Service\EntityManagerAwareTrait; use UnicaenApp\Service\EntityManagerAwareTrait;
use UnicaenAuth\Entity\Db\AbstractUser;
use UnicaenAuth\Event\UserAuthenticatedEvent; use UnicaenAuth\Event\UserAuthenticatedEvent;
use UnicaenAuth\Service\Traits\UserContextServiceAwareTrait;
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
use Zend\EventManager\ListenerAggregateTrait;
/** /**
* Classe abstraites pour les classes désirant scruter un événement déclenché lors de l'authentification * Classe abstraites pour les classes désirant scruter un événement déclenché lors de l'authentification
...@@ -14,16 +17,19 @@ use UnicaenAuth\Event\UserAuthenticatedEvent; ...@@ -14,16 +17,19 @@ use UnicaenAuth\Event\UserAuthenticatedEvent;
* *
* Événements disponibles : * Événements disponibles :
* - juste avant que l'entité utilisateur ne soit persistée. * - juste avant que l'entité utilisateur ne soit persistée.
* - juste après que l'entité utilisateur ait été persistée.
* *
* @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr> * @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr>
* @see UserAuthenticatedEvent * @see UserAuthenticatedEvent
*/ */
abstract class AuthenticatedUserSavedAbstractListener implements ListenerAggregateInterface, EntityManagerAwareInterface abstract class AuthenticatedUserSavedAbstractListener implements ListenerAggregateInterface, EntityManagerAwareInterface
{ {
use ListenerAggregateTrait;
use EntityManagerAwareTrait; use EntityManagerAwareTrait;
use UserContextServiceAwareTrait;
/** /**
* @var \Zend\Stdlib\CallbackHandler[] * @var callable[]
*/ */
protected $listeners = []; protected $listeners = [];
...@@ -31,20 +37,33 @@ abstract class AuthenticatedUserSavedAbstractListener implements ListenerAggrega ...@@ -31,20 +37,33 @@ abstract class AuthenticatedUserSavedAbstractListener implements ListenerAggrega
* Méthode appelée juste avant que l'entité utilisateur soit persistée. * Méthode appelée juste avant que l'entité utilisateur soit persistée.
* *
* @param UserAuthenticatedEvent $e * @param UserAuthenticatedEvent $e
* @return void
*/ */
public function onUserAuthenticatedPrePersist(UserAuthenticatedEvent $e) public function onUserAuthenticatedPrePersist(UserAuthenticatedEvent $e)
{ {
/** @var AbstractUser $user */
$user = $e->getDbUser();
// Sélection du dernier rôle endossé.
$this->selectLastUserRole($user);
} }
/** /**
* Méthode appelée juste après que l'entité utilisateur soit persistée. * Méthode appelée juste après que l'entité utilisateur soit persistée.
* *
* @param UserAuthenticatedEvent $e * @param UserAuthenticatedEvent $e
* @return void
*/ */
public function onUserAuthenticatedPostPersist(UserAuthenticatedEvent $e) public function onUserAuthenticatedPostPersist(UserAuthenticatedEvent $e)
{ {
// nop
}
protected function selectLastUserRole(AbstractUser $user)
{
if ($role = $user->getLastRole()) {
$this->serviceUserContext->setNextSelectedIdentityRole($role);
}
} }
/** /**
...@@ -66,18 +85,4 @@ abstract class AuthenticatedUserSavedAbstractListener implements ListenerAggrega ...@@ -66,18 +85,4 @@ abstract class AuthenticatedUserSavedAbstractListener implements ListenerAggrega
[$this, 'onUserAuthenticatedPostPersist'], [$this, 'onUserAuthenticatedPostPersist'],
100); 100);
} }
/**
* Detach all previously attached listeners
*
* @param EventManagerInterface $events
*/
public function detach(EventManagerInterface $events)
{
foreach ($this->listeners as $index => $listener) {
if ($events->detach($listener)) {
unset($this->listeners[$index]);
}
}
}
} }
\ No newline at end of file
...@@ -21,6 +21,8 @@ class ShibService ...@@ -21,6 +21,8 @@ class ShibService
{ {
const SHIB_USER_ID_EXTRACTOR = 'shib_user_id_extractor'; const SHIB_USER_ID_EXTRACTOR = 'shib_user_id_extractor';
const DOMAIN_DEFAULT = 'default';
const KEY_fromShibUser = 'fromShibUser'; const KEY_fromShibUser = 'fromShibUser';
const KEY_toShibUser = 'toShibUser'; const KEY_toShibUser = 'toShibUser';
...@@ -197,13 +199,21 @@ EOS; ...@@ -197,13 +199,21 @@ EOS;
return (array)$this->shibbolethConfig['required_attributes']; return (array)$this->shibbolethConfig['required_attributes'];
} }
/**
* @return array
*/
private function getShibUserIdExtractorDefaultConfig(): array
{
return $this->getShibUserIdExtractorConfigForDomain(self::DOMAIN_DEFAULT);
}
/** /**
* Retourne la config permettant d'extraire l'id à partir des attributs. * Retourne la config permettant d'extraire l'id à partir des attributs.
* *
* @param string $domain * @param string $domain
* @return array * @return array
*/ */
private function getShibUserIdExtractorForDomain(string $domain): array private function getShibUserIdExtractorConfigForDomain(string $domain): array
{ {
$key = self::SHIB_USER_ID_EXTRACTOR; $key = self::SHIB_USER_ID_EXTRACTOR;
if (! array_key_exists($key, $this->shibbolethConfig)) { if (! array_key_exists($key, $this->shibbolethConfig)) {
...@@ -213,7 +223,7 @@ EOS; ...@@ -213,7 +223,7 @@ EOS;
$config = $this->shibbolethConfig[$key]; $config = $this->shibbolethConfig[$key];
if (! array_key_exists($domain, $config)) { if (! array_key_exists($domain, $config)) {
throw new RuntimeException("Aucune config '$key' trouvée pour le domaine '$domain'."); return [];
} }
return $config[$domain]; return $config[$domain];
...@@ -481,14 +491,23 @@ EOS; ...@@ -481,14 +491,23 @@ EOS;
*/ */
private function extractShibUserIdValueForDomainFromShibData(string $domain, array $data): ?string private function extractShibUserIdValueForDomainFromShibData(string $domain, array $data): ?string
{ {
$config = $this->getShibUserIdExtractorForDomain($domain); $config = $this->getShibUserIdExtractorConfigForDomain($domain);
if (empty($config)) {
$config = $this->getShibUserIdExtractorDefaultConfig();
if (empty($config)) {
throw new RuntimeException("Aucune config trouvée ni pour le domaine '$domain' ni par défaut.");
}
}
foreach ($config as $array) { foreach ($config as $array) {
$name = $array['name']; $name = $array['name'];
$value = $this->getValueFromShibData($name, $data); $value = $this->getValueFromShibData($name, $data);
if ($value !== null) { if ($value !== null) {
$pregMatchPattern = $array['preg_match_pattern'] ?? null; $pregMatchPattern = $array['preg_match_pattern'] ?? null;
if ($pregMatchPattern !== null && preg_match($pregMatchPattern, $value, $matches)) { if ($pregMatchPattern !== null) {
if (preg_match($pregMatchPattern, $value, $matches) === 0) {
throw new RuntimeException("Le pattern '$pregMatchPattern' n'a pas permis d'extraire une valeur de '$name'.");
}
$value = $matches[1]; $value = $matches[1];
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
namespace UnicaenAuth\Service; namespace UnicaenAuth\Service;
use BjyAuthorize\Acl\Role; use Doctrine\ORM\ORMException;
use UnicaenApp\Exception\RuntimeException; use UnicaenApp\Exception\RuntimeException;
use UnicaenApp\Traits\SessionContainerTrait; use UnicaenApp\Traits\SessionContainerTrait;
use UnicaenAuth\Acl\NamedRole; use UnicaenAuth\Acl\NamedRole;
...@@ -10,6 +10,8 @@ use UnicaenAuth\Authentication\SessionIdentity; ...@@ -10,6 +10,8 @@ use UnicaenAuth\Authentication\SessionIdentity;
use UnicaenAuth\Authentication\Storage\Auth; use UnicaenAuth\Authentication\Storage\Auth;
use UnicaenAuth\Authentication\Storage\Usurpation; use UnicaenAuth\Authentication\Storage\Usurpation;
use UnicaenAuth\Entity\Db\AbstractUser; use UnicaenAuth\Entity\Db\AbstractUser;
use UnicaenAuth\Entity\Db\AbstractRole;
use UnicaenAuth\Entity\Db\Role;
use UnicaenAuth\Entity\Ldap\People; use UnicaenAuth\Entity\Ldap\People;
use UnicaenAuth\Entity\Shibboleth\ShibUser; use UnicaenAuth\Entity\Shibboleth\ShibUser;
use UnicaenAuth\Event\UserRoleSelectedEvent; use UnicaenAuth\Event\UserRoleSelectedEvent;
...@@ -19,8 +21,6 @@ use UnicaenAuth\Provider\Identity\Chain; ...@@ -19,8 +21,6 @@ use UnicaenAuth\Provider\Identity\Chain;
use Zend\Authentication\AuthenticationService; use Zend\Authentication\AuthenticationService;
use Zend\EventManager\EventManagerAwareInterface; use Zend\EventManager\EventManagerAwareInterface;
use Zend\EventManager\EventManagerAwareTrait; use Zend\EventManager\EventManagerAwareTrait;
use Zend\Http\Request;
use Zend\Http\Response;
use Zend\Permissions\Acl\Role\RoleInterface; use Zend\Permissions\Acl\Role\RoleInterface;
use ZfcUser\Entity\UserInterface; use ZfcUser\Entity\UserInterface;
...@@ -335,11 +335,35 @@ class UserContext extends AbstractService implements EventManagerAwareInterface ...@@ -335,11 +335,35 @@ class UserContext extends AbstractService implements EventManagerAwareInterface
unset($this->getSessionContainer()->selectedIdentityRole); unset($this->getSessionContainer()->selectedIdentityRole);
} }
$role = $this->getSelectableIdentityRoles()[$role];
if ($role instanceof AbstractRole) {
$this->saveUserLastRole($role);
}
$this->triggerUserRoleSelectedEvent(UserRoleSelectedEvent::POST_SELECTION, $role); $this->triggerUserRoleSelectedEvent(UserRoleSelectedEvent::POST_SELECTION, $role);
return $this; return $this;
} }
/**
* @param AbstractRole $role
*/
private function saveUserLastRole(AbstractRole $role)
{
/** @var AbstractUser $user */
$user = $this->getDbUser();
if (! $user) {
return;
}
$user->setLastRole($role);
try {
$this->getEntityManager()->flush($user);
} catch (ORMException $e) {
throw new RuntimeException("Erreur rencontrée lors de l'enregistrement en bdd", null, $e);
}
}
/** /**
* Retourne l'éventuel rôle spécifié en session devant être le prochain rôle sélectionné. * Retourne l'éventuel rôle spécifié en session devant être le prochain rôle sélectionné.
* *
...@@ -372,6 +396,11 @@ class UserContext extends AbstractService implements EventManagerAwareInterface ...@@ -372,6 +396,11 @@ class UserContext extends AbstractService implements EventManagerAwareInterface
unset($this->getSessionContainer()->nextSelectedIdentityRole); unset($this->getSessionContainer()->nextSelectedIdentityRole);
} }
$role = $this->getSelectableIdentityRoles()[$role];
if ($role instanceof AbstractRole) {
$this->saveUserLastRole($role);
}
$this->triggerUserRoleSelectedEvent(UserRoleSelectedEvent::POST_SELECTION, $role); $this->triggerUserRoleSelectedEvent(UserRoleSelectedEvent::POST_SELECTION, $role);
return $this; return $this;
...@@ -499,5 +528,10 @@ class UserContext extends AbstractService implements EventManagerAwareInterface ...@@ -499,5 +528,10 @@ class UserContext extends AbstractService implements EventManagerAwareInterface
$sessionIdentity = SessionIdentity::newInstance($usurpateur->getUsername(), $this->getAuthenticationType()); $sessionIdentity = SessionIdentity::newInstance($usurpateur->getUsername(), $this->getAuthenticationType());
$this->authenticationService->getStorage()->write($sessionIdentity); $this->authenticationService->getStorage()->write($sessionIdentity);
// Sélection du dernier rôle endossé.
if ($role = $usurpateur->getLastRole()) {
$this->setNextSelectedIdentityRole($role);
}
} }
} }
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment