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

Merge branch 'release-3.2.0'

parents 098e285b 23c62cc7
Pipeline #9950 passed with stage
in 16 seconds
......@@ -37,3 +37,11 @@ Première version officielle sous ZF3.
3.1.2
-----
- Aide de vue UserUsurpationHelper : ajout de la possibilité de dessiner un simple bouton.
3.2.0
-----
- Configuration de la stratégie d'extraction d'un identifiant utile parmi les données d'authentification shibboleth
(config 'shib_user_id_extractor').
- 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.
- [FIX] Usurpation d'un compte local en BDD.
......@@ -75,15 +75,6 @@ class Module implements AutoloaderProviderInterface, ConfigProviderInterface, Se
},
//===========================================
// verrue pour forcer le label de l'identifiant qqsoit l'options 'auth_identity_fields'
'zfcuser_login_form' => function ($sm) {
$options = $sm->get('zfcuser_module_options');
$form = new Login(null, $options);
$form->setInputFilter(new LoginFilter($options));
$form->get('identity')->setLabel("Username");
return $form;
},
],
];
}
......
<?php
use UnicaenAuth\Authentication\Adapter\AdapterChainServiceFactory;
use UnicaenAuth\Authentication\Adapter\Cas;
use UnicaenAuth\Authentication\Adapter\CasAdapterFactory;
use UnicaenAuth\Authentication\Adapter\Db;
use UnicaenAuth\Authentication\Adapter\DbAdapterFactory;
use UnicaenAuth\Authentication\Adapter\Ldap;
use UnicaenAuth\Authentication\Adapter\LdapAdapterFactory;
use UnicaenAuth\Authentication\Adapter\LocalAdapter;
use UnicaenAuth\Authentication\Adapter\LocalAdapterFactory;
use UnicaenAuth\Authentication\Adapter\Shib;
use UnicaenAuth\Authentication\Adapter\ShibAdapterFactory;
use UnicaenAuth\Authentication\Storage\Auth;
use UnicaenAuth\Authentication\Storage\AuthFactory;
use UnicaenAuth\Authentication\Storage\DbFactory;
use UnicaenAuth\Authentication\Storage\LdapFactory;
use UnicaenAuth\Authentication\Storage\ShibFactory;
use UnicaenAuth\Authentication\Storage\Usurpation;
use UnicaenAuth\Authentication\Storage\UsurpationFactory;
use UnicaenAuth\Controller\AuthControllerFactory;
use UnicaenAuth\Controller\DroitsControllerFactory;
use UnicaenAuth\Controller\UtilisateurControllerFactory;
use UnicaenAuth\Form\CasLoginForm;
use UnicaenAuth\Form\CasLoginFormFactory;
use UnicaenAuth\Form\Droits\RoleFormFactory;
use UnicaenAuth\Form\LoginFormFactory;
use UnicaenAuth\Form\LoginForm;
use UnicaenAuth\Form\ShibLoginForm;
use UnicaenAuth\Form\ShibLoginFormFactory;
use UnicaenAuth\Guard\PrivilegeControllerFactory;
......@@ -59,9 +71,9 @@ use Zend\ServiceManager\Proxy\LazyServiceFactory;
$settings = [
/**
* Configuration de l'authentification via la fédération d'identité (Shibboleth).
* Configuration de l'authentification centralisée (CAS).
*/
'shib' => [
'cas' => [
/**
* Ordre d'affichage du formulaire de connexion.
*/
......@@ -70,12 +82,77 @@ $settings = [
/**
* Activation ou non de ce mode d'authentification.
*/
'enabled' => false,
'enabled' => true,
/**
* Description facultative de ce mode d'authentification qui apparaîtra sur la page de connexion.
*/
'description' => "Cliquez sur le bouton ci-dessous pour accéder à l'authentification centralisée.",
/**
* Adapter compétent pour réaliser l'authentification de l'utilisateur.
*/
'adapter' => Cas::class,
/**
* Service/formulaire d'authentification à utiliser.
*/
'form' => CasLoginForm::class,
/**
* Description facultative de ce mode d'authentification qui apparaîtra sur le formulaire de connexion.
* Infos de connexion au serveur CAS.
*/
'connection' => [
'default' => [
'params' => [
'hostname' => 'host.domain.fr',
'port' => 443,
'version' => "2.0",
'uri' => "",
'debug' => false,
],
],
]
],
/**
* Configuration de l'authentification locale (compte LDAP établissement, ou compte BDD application).
*/
'local' => [
'order' => 2,
'enabled' => true,
'description' => "Utilisez ce formulaire si vous possédez un compte LDAP établissement ou un compte local dédié à l'application.",
'form' => LoginForm::class,
/**
* Mode d'authentification à l'aide d'un compte dans la BDD de l'application.
*/
'db' => [
'enabled' => true, // doit être activé pour que l'usurpation fonctionne (cf. Authentication/Storage/Db::read()) :-/ todo: faire mieux
'adapter' => Db::class,
'form' => LoginForm::class,
],
/**
* Mode d'authentification à l'aide d'un compte LDAP.
*/
'ldap' => [
'enabled' => true,
'adapter' => Ldap::class,
'form' => LoginForm::class,
],
],
/**
* Configuration de l'authentification via la fédération d'identité (Shibboleth).
*/
'shib' => [
'order' => 3,
'enabled' => false,
'description' => "Cliquez sur le bouton ci-dessous pour accéder à l'authentification via la fédération d'identité.",
'adapter' => Shib::class,
'form' => ShibLoginForm::class,
/**
* URL de déconnexion.
......@@ -110,68 +187,33 @@ $settings = [
'supannEtuId|supannEmpId',
],
*/
],
/**
* Configuration de l'authentification LDAP (compte établissement).
*/
'ldap' => [
'order' => 2,
'enabled' => true,
'description' => "Utilisez ce formulaire pour vous connecter avec votre compte numérique établissement.",
/**
* Type de substitution.
* Permet de "fusionner" les types d'authentification locale (db) et établissement (ldap) et donc leurs
* formulaires de connexion respectifs.
*/
'type' => 'local',
],
/**
* Configuration de l'authentification locale (compte propre à l'appli).
*/
'db' => [
'order' => 3,
'enabled' => false,
/**
* Type de substitution.
* Permet de "grouper" les types d'authentification locale (db) et établissement (ldap) sous un même
* formulaire de connexion.
*/
'type' => 'local',
/**
* Description facultative de ce mode d'authentification qui apparaîtra sur le formulaire d'authentification.
* NB: si la valeur de 'order' pour le type 'db' est supérieure à celle pour le type 'ldap',
* c'est cette description qui sera visible.
* Configuration de la stratégie d'extraction d'un identifiant utile parmi les données d'authentification
* shibboleth.
* Ex: identifiant de l'usager au sein du référentiel établissement, transmis par l'IDP via le supannRefId.
*/
'description' => "Utilisez ce formulaire si vous possédez un compte local propre à l'application.",
],
/**
* Configuration de l'authentification centralisée (CAS).
*/
'cas' => [
'order' => 4,
'enabled' => false,
'description' => "Cliquez sur le bouton ci-dessous pour accéder à l'authentification centralisée.",
/**
* Infos de connexion au serveur CAS.
*/
'connection' => [
'default' => [
'params' => [
'hostname' => 'host.domain.fr',
'port' => 443,
'version' => "2.0",
'uri' => "",
'debug' => false,
'shib_user_id_extractor' => [
// domaine (ex: 'unicaen.fr') de l'EPPN (ex: hochonp@unicaen.fr')
'unicaen.fr' => [
[
// nom du 1er attribut recherché
'name' => 'supannRefId', // ex: '{REFERENTIEL}1234;{ISO15693}044D1AZE7A5P80'
// pattern éventuel pour extraire la partie intéressante
'preg_match_pattern' => '|\{REFERENTIEL\}(\d+)|', // ex: permet d'extraire '1234'
],
[
// nom du 2e attribut recherché
'name' => 'supannEmpId',
// pas de pattern donc valeur directement utilisable
'preg_match_pattern' => null,
],
[
// nom du 3e attribut recherché
'name' => 'supannEtuId',
],
],
]
],
],
/**
......@@ -239,9 +281,8 @@ return [
* Default value: array containing 'ZfcUser\Authentication\Adapter\Db' with priority 100
* Accepted values: array containing services that implement 'ZfcUser\Authentication\Adapter\ChainableAdapter'
*/
'auth_adapters' => [
300 => 'UnicaenAuth\Authentication\Adapter\Ldap',
200 => 'UnicaenAuth\Authentication\Adapter\Db',
'auth_adapters' => [
400 => LocalAdapter::class, // délègue à Db et Ldap
100 => 'UnicaenAuth\Authentication\Adapter\Cas',
50 => 'UnicaenAuth\Authentication\Adapter\Shib',
],
......@@ -295,6 +336,8 @@ return [
['controller' => 'UnicaenApp\Controller\Application', 'action' => 'informatique-et-libertes', 'roles' => 'guest'],
['controller' => 'UnicaenApp\Controller\Application', 'action' => 'refresh-session', 'roles' => 'guest'],
['controller' => 'UnicaenAuth\Controller\Utilisateur', 'action' => 'selectionner-profil', 'roles' => 'guest'],
['controller' => 'UnicaenAuth\Controller\Utilisateur', 'action' => 'usurper-identite', 'roles' => 'guest'],
['controller' => 'UnicaenAuth\Controller\Utilisateur', 'action' => 'stopper-usurpation', 'roles' => 'guest'],
['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'login', 'roles' => 'guest'],
['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'authenticate', 'roles' => 'guest'],
......@@ -575,6 +618,7 @@ return [
// in /var/www/sygal/module/Application/src/Application/Controller/UtilisateurController.php on line 34
'service_manager' => [
'aliases' => [
'zfcuser_login_form' => LoginForm::class,
'Zend\Authentication\AuthenticationService' => 'zfcuser_auth_service',
'UnicaenAuth\Privilege\PrivilegeProvider' => 'UnicaenAuth\Service\Privilege',
'\UnicaenAuth\Guard\PrivilegeController' => 'UnicaenAuth\Guard\PrivilegeController',
......@@ -607,6 +651,7 @@ return [
'UnicaenAuth\Service\UserContext' => UserContextFactory::class,
'zfcuser_user_mapper' => UserMapperFactory::class,
'MouchardCompleterAuth' => 'UnicaenAuth\Mouchard\MouchardCompleterAuthFactory',
LocalAdapter::class => LocalAdapterFactory::class,
'UnicaenAuth\Authentication\Adapter\Ldap' => LdapAdapterFactory::class,
'UnicaenAuth\Authentication\Adapter\Db' => DbAdapterFactory::class,
'UnicaenAuth\Authentication\Adapter\Cas' => CasAdapterFactory::class,
......@@ -614,11 +659,16 @@ return [
'UnicaenAuth\Authentication\Storage\Db' => DbFactory::class,
'UnicaenAuth\Authentication\Storage\Ldap' => LdapFactory::class,
'UnicaenAuth\Authentication\Storage\Shib' => ShibFactory::class,
Usurpation::class => UsurpationFactory::class,
Auth::class => AuthFactory::class,
'UnicaenAuth\Service\User' => UserFactory::class,
'UnicaenAuth\Guard\PrivilegeController' => PrivilegeControllerFactory::class,
'UnicaenAuth\Guard\PrivilegeRoute' => PrivilegeRouteFactory::class,
'UnicaenAuth\Provider\Rule\PrivilegeRuleProvider' => PrivilegeRuleProviderFactory::class,
// verrue pour forcer le label de l'identifiant qqsoit l'options 'auth_identity_fields'
LoginForm::class => LoginFormFactory::class,
CasLoginForm::class => CasLoginFormFactory::class,
ShibLoginForm::class => ShibLoginFormFactory::class,
'ZfcUser\Authentication\Adapter\AdapterChain' => AdapterChainServiceFactory::class,
......
<?php
use UnicaenAuth\Authentication\Adapter\Shib;
use UnicaenAuth\Authentication\Adapter\Cas;
use UnicaenAuth\Authentication\Adapter\Ldap;
use UnicaenAuth\Authentication\Adapter\Db;
return [
'unicaen-auth' => [
/**
* Authentification LDAP (compte établissement).
* Configuration de l'authentification centralisée (CAS).
*/
'ldap' => [
'cas' => [
/**
* Ordre d'affichage du formulaire de connexion.
*/
......@@ -23,39 +18,9 @@ return [
'enabled' => true,
/**
* Type de substitution.
* Permet de "fusionner" les types d'authentification applicative (db) et établissement (ldap) et donc leurs
* formulaires de connexion respectifs.
*/
'type' => 'local',
/**
* Description facultative de ce mode d'authentification qui apparaîtra sur le formulaire de connexion.
* Description facultative de ce mode d'authentification qui apparaîtra sur la page de connexion.
*/
'description' => "Utilisez ce formulaire si vous possédez un compte établissement.",
],
/**
* Authentification BDD (compte dédié à l'appli).
*/
'db' => [
'order' => 2,
'enabled' => true,
'type' => 'local',
/**
* Description facultative de ce mode d'authentification qui apparaîtra sur le formulaire d'authentification.
* (NB: Si l'authentification LDAP est également activée, c'est cette description qui sera utilisée)
*/
'description' => "Utilisez ce formulaire si vous possédez un compte local dédié à cette application.",
],
/**
* Authentification centralisée (CAS).
*/
'cas' => [
'order' => 3,
'enabled' => false,
'description' => "Cliquez sur le bouton ci-dessous pour accéder à l'authentification centralisée.",
/**
* Infos de connexion au serveur CAS.
......@@ -70,6 +35,28 @@ return [
'debug' => false,
],
],
]
],
/**
* Configuration de l'authentification locale (compte LDAP établissement, ou compte BDD application).
*/
'local' => [
'order' => 2,
'enabled' => true,
'description' => "Utilisez ce formulaire si vous possédez un compte LDAP établissement ou un compte local dédié à l'application.",
/**
* Mode d'authentification à l'aide d'un compte dans la BDD de l'application.
*/
'db' => [
'enabled' => true, // doit être activé pour que l'usurpation fonctionne (cf. Authentication/Storage/Db::read()) :-/
],
/**
* Mode d'authentification à l'aide d'un compte LDAP.
*/
'ldap' => [
'enabled' => true,
],
],
......@@ -77,7 +64,7 @@ return [
* Authentification via la fédération d'identité (Shibboleth).
*/
'shib' => [
'order' => 4,
'order' => 3,
'enabled' => false,
'description' =>
"Cliquez sur le bouton ci-dessous pour accéder à l'authentification via la fédération d'identité. " .
......@@ -128,6 +115,33 @@ return [
// 'givenName',
// 'supannEtuId|supannEmpId',
//],
/**
* Configuration de la stratégie d'extraction d'un identifiant utile parmi les données d'authentification
* shibboleth.
* Ex: identifiant de l'usager au sein du référentiel établissement, transmis par l'IDP via le supannRefId.
*/
'shib_user_id_extractor' => [
// domaine (ex: 'unicaen.fr') de l'EPPN (ex: hochonp@unicaen.fr')
'unicaen.fr' => [
[
// nom du 1er attribut recherché
'name' => 'supannRefId', // ex: '{REFERENTIEL}1234;{ISO15693}044D1AZE7A5P80'
// pattern éventuel pour extraire la partie intéressante
'preg_match_pattern' => '|\{REFERENTIEL\}(\d+)|', // ex: permet d'extraire '1234'
],
[
// nom du 2e attribut recherché
'name' => 'supannEmpId',
// pas de pattern donc valeur directement utilisable
'preg_match_pattern' => null,
],
[
// nom du 3e attribut recherché
'name' => 'supannEtuId',
],
],
],
],
/**
......
......@@ -2,18 +2,25 @@
namespace UnicaenAuth\Authentication\Adapter;
use Zend\Authentication\Storage;
use UnicaenAuth\Authentication\SessionIdentity;
use Zend\Authentication\Storage\StorageInterface;
use Zend\EventManager\EventInterface;
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
use Zend\EventManager\ListenerAggregateTrait;
use ZfcUser\Authentication\Adapter\ChainableAdapter;
abstract class AbstractAdapter implements ChainableAdapter
abstract class AbstractAdapter implements ChainableAdapter, ListenerAggregateInterface
{
use ListenerAggregateTrait;
/**
* @var string
*/
protected $type;
/**
* @var Storage\StorageInterface
* @var StorageInterface
*/
protected $storage;
......@@ -27,29 +34,31 @@ abstract class AbstractAdapter implements ChainableAdapter
return $this;
}
/**
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* Returns the persistent storage handler
*
* Session storage is used by default unless a different storage adapter has been set.
*
* @return Storage\StorageInterface
* @return StorageInterface
*/
public function getStorage()
public function getStorage(): StorageInterface
{
if (null === $this->storage) {
$this->setStorage(new Storage\Session(get_class($this)));
}
return $this->storage;
}
/**
* Sets the persistent storage handler
*
* @param Storage\StorageInterface $storage
* @return AbstractAdapter Provides a fluent interface
* @param StorageInterface $storage
* @return self Provides a fluent interface
*/
public function setStorage(Storage\StorageInterface $storage)
public function setStorage(StorageInterface $storage): self
{
$this->storage = $storage;
return $this;
......@@ -60,7 +69,7 @@ abstract class AbstractAdapter implements ChainableAdapter
*
* @return bool
*/
public function isSatisfied()
public function isSatisfied(): bool
{
$storage = $this->getStorage()->read();
return (isset($storage['is_satisfied']) && true === $storage['is_satisfied']);
......@@ -70,13 +79,41 @@ abstract class AbstractAdapter implements ChainableAdapter
* Set if this adapter is satisfied or not
*
* @param bool $bool
* @return AbstractAdapter
* @return self
*/
public function setSatisfied($bool = true)
public function setSatisfied($bool = true): self
{
$storage = $this->getStorage()->read() ?: array();
$storage['is_satisfied'] = $bool;
$this->getStorage()->write($storage);
return $this;
}
/**
* @param string $username
* @return SessionIdentity
*/
protected function createSessionIdentity(string $username): SessionIdentity
{
return SessionIdentity::newInstance($username, $this->type);
}
/**
* Called when user id logged out
*
* @param EventInterface $e
*/
public function logout(EventInterface $e)
{
$this->getStorage()->clear();
}
/**
* @inheritDoc
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$events->attach('authenticate', [$this, 'authenticate'], $priority);
$events->attach('logout', [$this, 'logout'], $priority);
}
}
......@@ -22,21 +22,21 @@ class AdapterChain extends \ZfcUser\Authentication\Adapter\AdapterChain
$this->getEventManager()->trigger('authenticate.pre', $e);
$result = $this->getEventManager()->triggerUntil(function ($test) {
return ($test instanceof Response);
$result = $this->getEventManager()->triggerUntil(function ($result) {
return $result === true || $result instanceof Response;
}, 'authenticate', $e);
if ($result->stopped()) {
if ($result->last() instanceof Response) {
return $result->last();
$last = $result->last();
if ($last === true || $last instanceof Response) {
return $last;
}
throw new Exception\AuthenticationEventException(
sprintf(
'Auth event was stopped without a response. Got "%s" instead',
is_object($result->last()) ? get_class($result->last()) : gettype($result->last())
)
);
// throw new Exception\AuthenticationEventException(
// sprintf(
// 'Auth event was stopped without a response. Got "%s" instead',
// is_object($result->last()) ? get_class($result->last()) : gettype($result->last())
// )
// );
}
if ($e->getIdentity()) {
......@@ -54,7 +54,7 @@ class AdapterChain extends \ZfcUser\Authentication\Adapter\AdapterChain
*
* @return Response|null
*/
public function logoutAdapters()
public function logoutAdapters(): ?Response
{
//Adapters might need to perform additional cleanup after logout
$responseCollection = $this->getEventManager()->triggerUntil(function ($test) {
......
......@@ -12,17 +12,14 @@ class AdapterChainServiceFactory
$chain = new AdapterChain();
$options = $this->getOptions($container);
$enabledTypes = array_keys($options->getEnabledAuthTypes()); // types d'auth activés
//iterate and attach multiple adapters and events if offered
foreach ($options->getAuthAdapters() as $priority => $adapterName) {
/** @var AbstractAdapter $adapter */
$adapter = $container->get($adapterName);
if (is_callable(array($adapter, 'authenticate'))) {
$chain->getEventManager()->attach('authenticate', array($adapter, 'authenticate'), $priority);
}
if (is_callable(array($adapter, 'logout'))) {
$chain->getEventManager()->attach('logout', array($adapter, 'logout'), $priority);
if (in_array($adapter->getType(), $enabledTypes)) {
$adapter->attach($chain->getEventManager());
}
}
......@@ -62,7 +59,7 @@ class AdapterChainServiceFactory
);
}