Commit 4d43239a authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

Modifications/améliorations pour faciliter le support d'autres modes...

Modifications/améliorations pour faciliter le support d'autres modes d'authentification (ex: unicaen/auth-token).
parent 5958fd68
Pipeline #10054 passed with stage
in 1 minute and 4 seconds
...@@ -3,8 +3,10 @@ CHANGELOG ...@@ -3,8 +3,10 @@ CHANGELOG
3.2.1 3.2.1
----- -----
- Modifications/améliorations pour faciliter le support d'autres modes d'authentification (ex: unicaen/auth-token).
- Le type d'authentification souhaité (local, shib ou cas) peut être spécifié dans l'URL de redirection via le - Le type d'authentification souhaité (local, shib ou cas) peut être spécifié dans l'URL de redirection via le
query param 'authtype' query param 'authtype'
- Ajout de la colonne CREATED_AT dans les scripts SQL de création de la table USER (non mappée dans l'entité).
- [FIX] Usurpation d'un compte local (db) depuis une authentification shib - [FIX] Usurpation d'un compte local (db) depuis une authentification shib
- [FIX] Une chaîne vide doit être considérée comme null dans ShibService::extractShibUserIdValueForDomainFromShibData() - [FIX] Une chaîne vide doit être considérée comme null dans ShibService::extractShibUserIdValueForDomainFromShibData()
- [FIX] Nécessité de clés littérales dans la config par domaine de 'shib_user_id_extractor' sinon doublons lors de la - [FIX] Nécessité de clés littérales dans la config par domaine de 'shib_user_id_extractor' sinon doublons lors de la
......
<?php <?php
namespace UnicaenAuth;
use UnicaenAuth\Authentication\Adapter\AdapterChainServiceFactory; use UnicaenAuth\Authentication\Adapter\AdapterChainServiceFactory;
use UnicaenAuth\Authentication\Adapter\Cas; use UnicaenAuth\Authentication\Adapter\Cas;
use UnicaenAuth\Authentication\Adapter\CasAdapterFactory; use UnicaenAuth\Authentication\Adapter\CasAdapterFactory;
...@@ -18,6 +20,7 @@ use UnicaenAuth\Authentication\Storage\LdapFactory; ...@@ -18,6 +20,7 @@ use UnicaenAuth\Authentication\Storage\LdapFactory;
use UnicaenAuth\Authentication\Storage\ShibFactory; use UnicaenAuth\Authentication\Storage\ShibFactory;
use UnicaenAuth\Authentication\Storage\Usurpation; use UnicaenAuth\Authentication\Storage\Usurpation;
use UnicaenAuth\Authentication\Storage\UsurpationFactory; use UnicaenAuth\Authentication\Storage\UsurpationFactory;
use UnicaenAuth\Controller\AuthController;
use UnicaenAuth\Controller\AuthControllerFactory; use UnicaenAuth\Controller\AuthControllerFactory;
use UnicaenAuth\Controller\DroitsControllerFactory; use UnicaenAuth\Controller\DroitsControllerFactory;
use UnicaenAuth\Controller\UtilisateurControllerFactory; use UnicaenAuth\Controller\UtilisateurControllerFactory;
...@@ -30,12 +33,14 @@ use UnicaenAuth\Form\ShibLoginForm; ...@@ -30,12 +33,14 @@ use UnicaenAuth\Form\ShibLoginForm;
use UnicaenAuth\Form\ShibLoginFormFactory; use UnicaenAuth\Form\ShibLoginFormFactory;
use UnicaenAuth\Guard\PrivilegeControllerFactory; use UnicaenAuth\Guard\PrivilegeControllerFactory;
use UnicaenAuth\Guard\PrivilegeRouteFactory; use UnicaenAuth\Guard\PrivilegeRouteFactory;
use UnicaenAuth\Options\ModuleOptions;
use UnicaenAuth\ORM\Event\Listeners\HistoriqueListenerFactory; use UnicaenAuth\ORM\Event\Listeners\HistoriqueListenerFactory;
use UnicaenAuth\Provider\Rule\PrivilegeRuleProviderFactory; use UnicaenAuth\Provider\Rule\PrivilegeRuleProviderFactory;
use UnicaenAuth\Service\CasService; use UnicaenAuth\Service\CasService;
use UnicaenAuth\Service\CasServiceFactory; use UnicaenAuth\Service\CasServiceFactory;
use UnicaenAuth\Service\ShibService; use UnicaenAuth\Service\ShibService;
use UnicaenAuth\Service\ShibServiceFactory; use UnicaenAuth\Service\ShibServiceFactory;
use UnicaenAuth\Service\UserContext;
use UnicaenAuth\Service\UserContextFactory; use UnicaenAuth\Service\UserContextFactory;
use UnicaenAuth\Service\UserFactory; use UnicaenAuth\Service\UserFactory;
use UnicaenAuth\Service\UserMapperFactory; use UnicaenAuth\Service\UserMapperFactory;
...@@ -70,6 +75,15 @@ use Zend\Authentication\AuthenticationService; ...@@ -70,6 +75,15 @@ use Zend\Authentication\AuthenticationService;
use Zend\ServiceManager\Proxy\LazyServiceFactory; use Zend\ServiceManager\Proxy\LazyServiceFactory;
$settings = [ $settings = [
/**
* Tous les types d'authentification supportés par le module unicaen/auth.
*/
'auth_types' => [
'local', // càd 'ldap' et 'db'
'cas',
'shib',
],
/** /**
* Configuration de l'authentification centralisée (CAS). * Configuration de l'authentification centralisée (CAS).
*/ */
...@@ -629,21 +643,22 @@ return [ ...@@ -629,21 +643,22 @@ return [
// in /var/www/sygal/module/Application/src/Application/Controller/UtilisateurController.php on line 34 // in /var/www/sygal/module/Application/src/Application/Controller/UtilisateurController.php on line 34
'service_manager' => [ 'service_manager' => [
'aliases' => [ 'aliases' => [
'unicaen-auth_module_options' => ModuleOptions::class,
'zfcuser_login_form' => LoginForm::class, 'zfcuser_login_form' => LoginForm::class,
'Zend\Authentication\AuthenticationService' => 'zfcuser_auth_service', 'Zend\Authentication\AuthenticationService' => 'zfcuser_auth_service',
'UnicaenAuth\Privilege\PrivilegeProvider' => 'UnicaenAuth\Service\Privilege', 'UnicaenAuth\Privilege\PrivilegeProvider' => 'UnicaenAuth\Service\Privilege',
'\UnicaenAuth\Guard\PrivilegeController' => 'UnicaenAuth\Guard\PrivilegeController', '\UnicaenAuth\Guard\PrivilegeController' => 'UnicaenAuth\Guard\PrivilegeController',
'unicaen-auth_user_service' => 'UnicaenAuth\Service\User', // pour la compatibilité 'unicaen-auth_user_service' => 'UnicaenAuth\Service\User', // pour la compatibilité
'authUserContext' => 'UnicaenAuth\Service\UserContext', // pour la compatibilité 'authUserContext' => UserContext::class, // pour la compatibilité
'AuthUserContext' => 'UnicaenAuth\Service\UserContext', // pour la compatibilité 'AuthUserContext' => UserContext::class, // pour la compatibilité
], ],
'invokables' => [ 'invokables' => [
'UnicaenAuth\View\RedirectionStrategy' => 'UnicaenAuth\View\RedirectionStrategy', 'UnicaenAuth\View\RedirectionStrategy' => 'UnicaenAuth\View\RedirectionStrategy',
'UnicaenAuth\Service\CategoriePrivilege' => 'UnicaenAuth\Service\CategoriePrivilegeService', 'UnicaenAuth\Service\CategoriePrivilege' => 'UnicaenAuth\Service\CategoriePrivilegeService',
], ],
'factories' => [ 'factories' => [
'unicaen-auth_module_options' => 'UnicaenAuth\Options\ModuleOptionsFactory', ModuleOptions::class => 'UnicaenAuth\Options\ModuleOptionsFactory',
'zfcuser_auth_service' => 'UnicaenAuth\Authentication\AuthenticationServiceFactory', 'zfcuser_auth_service' => 'UnicaenAuth\Authentication\AuthenticationServiceFactory',
'UnicaenAuth\Authentication\Storage\Chain' => 'UnicaenAuth\Authentication\Storage\ChainServiceFactory', 'UnicaenAuth\Authentication\Storage\Chain' => 'UnicaenAuth\Authentication\Storage\ChainServiceFactory',
'UnicaenAuth\Provider\Identity\Chain' => 'UnicaenAuth\Provider\Identity\ChainServiceFactory', 'UnicaenAuth\Provider\Identity\Chain' => 'UnicaenAuth\Provider\Identity\ChainServiceFactory',
...@@ -659,7 +674,7 @@ return [ ...@@ -659,7 +674,7 @@ return [
'zfcuser_redirect_callback' => 'UnicaenAuth\Authentication\RedirectCallbackFactory', // substituion 'zfcuser_redirect_callback' => 'UnicaenAuth\Authentication\RedirectCallbackFactory', // substituion
CasService::class => CasServiceFactory::class, CasService::class => CasServiceFactory::class,
ShibService::class => ShibServiceFactory::class, ShibService::class => ShibServiceFactory::class,
'UnicaenAuth\Service\UserContext' => UserContextFactory::class, UserContext::class => UserContextFactory::class,
'zfcuser_user_mapper' => UserMapperFactory::class, 'zfcuser_user_mapper' => UserMapperFactory::class,
'MouchardCompleterAuth' => 'UnicaenAuth\Mouchard\MouchardCompleterAuthFactory', 'MouchardCompleterAuth' => 'UnicaenAuth\Mouchard\MouchardCompleterAuthFactory',
LocalAdapter::class => LocalAdapterFactory::class, LocalAdapter::class => LocalAdapterFactory::class,
...@@ -708,10 +723,11 @@ return [ ...@@ -708,10 +723,11 @@ return [
], ],
'controllers' => [ 'controllers' => [
'invokables' => [ 'aliases' => [
'UnicaenAuth\Controller\Auth' => AuthController::class,
], ],
'factories' => [ 'factories' => [
'UnicaenAuth\Controller\Auth' => AuthControllerFactory::class, AuthController::class => AuthControllerFactory::class,
'UnicaenAuth\Controller\Utilisateur' => UtilisateurControllerFactory::class, 'UnicaenAuth\Controller\Utilisateur' => UtilisateurControllerFactory::class,
'UnicaenAuth\Controller\Droits' => DroitsControllerFactory::class, 'UnicaenAuth\Controller\Droits' => DroitsControllerFactory::class,
], ],
......
<?php
namespace UnicaenAuth\Authentication\Adapter;
use UnicaenAuth\Options\Traits\ModuleOptionsAwareTrait;
use Zend\Authentication\Result as AuthenticationResult;
use Zend\EventManager\EventInterface;
use Zend\Session\Container as SessionContainer;
use ZfcUser\Authentication\Adapter\AdapterChainEvent;
use ZfcUser\Entity\UserInterface;
use ZfcUser\Mapper\UserInterface as UserMapperInterface;
/**
* Classe abstraite des adpater d'authentification à partir de la base de données.
*
* Ajout par rapport à la classe mère : si aucune base de données ou table n'existe,
* l'authentification ne plante pas (i.e. renvoit false).
*
* @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
*/
abstract class AbstractDb extends AbstractAdapter
{
use ModuleOptionsAwareTrait;
/**
* @var string
*/
protected $type;
/**
* @var AdapterChainEvent
*/
protected $event;
/**
* @var UserMapperInterface
*/
protected $mapper;
/**
* @inheritDoc
*/
public function authenticate(EventInterface $e): bool
{
// NB: Dans la version 3.0.0 de zf-commons/zfc-user, cette méthode prend un EventInterface.
// Mais dans la branche 3.x, c'est un AdapterChainEvent !
// Si un jour c'est un AdapterChainEvent qui est attendu, plus besoin de faire $e->getTarget().
$this->event = $e->getTarget();
if ($this->event->getIdentity()) {
return true;
}
if ($this->isSatisfied()) {
$storage = $this->getStorage()->read();
$this->event
->setIdentity($storage['identity'])
->setCode(AuthenticationResult::SUCCESS)
->setMessages(array('Authentication successful.'));
return true;
}
$userObject = $this->fetchUserObject();
if ($userObject === null) {
return false;
}
if ($this->moduleOptions->getEnableUserState()) {
// Don't allow user to login if state is not in allowed list
if (!in_array($userObject->getState(), $this->moduleOptions->getAllowedLoginStates())) {
$this->event
->setCode(AuthenticationResult::FAILURE_UNCATEGORIZED)
->setMessages(["Ce compte utilisateur a été désactivé"]);
$this->setSatisfied(false);
return false;
}
}
$result = $this->authenticateUserObject($userObject);
if ($result === false) {
return false;
}
// regen the id
$session = new SessionContainer($this->getStorage()->getNamespace());
$session->getManager()->regenerateId();
// Success!
$identity = $this->createSessionIdentity($userObject->getUsername());
$this->event->setIdentity($identity);
$this->setSatisfied(true);
$storage = $this->getStorage()->read();
$storage['identity'] = $this->event->getIdentity();
$this->getStorage()->write($storage);
$this->event
->setCode(AuthenticationResult::SUCCESS)
->setMessages(array('Authentication successful.'));
return true;
}
/**
* @return \ZfcUser\Entity\UserInterface|null
*/
abstract protected function fetchUserObject(): ?UserInterface;
/**
* @param \ZfcUser\Entity\UserInterface $userObject
* @return bool
*/
abstract protected function authenticateUserObject(UserInterface $userObject): bool;
/**
* setMapper
*
* @param UserMapperInterface $mapper
* @return self
*/
public function setMapper(UserMapperInterface $mapper): self
{
$this->mapper = $mapper;
return $this;
}
}
\ No newline at end of file
<?php <?php
namespace UnicaenAuth\Authentication\Adapter; namespace UnicaenAuth\Authentication\Adapter;
use Interop\Container\ContainerInterface; use Interop\Container\ContainerInterface;
...@@ -7,19 +8,19 @@ use ZfcUser\Options\ModuleOptions; ...@@ -7,19 +8,19 @@ use ZfcUser\Options\ModuleOptions;
class AdapterChainServiceFactory class AdapterChainServiceFactory
{ {
public function __invoke(ContainerInterface $container, $requestedName, array $options = null) public function __invoke(ContainerInterface $container, $requestedName, array $options = null): AdapterChain
{ {
$chain = new AdapterChain(); $chain = new AdapterChain();
$options = $this->getOptions($container); $options = $this->getOptions($container);
$enabledTypes = array_keys($options->getEnabledAuthTypes()); // types d'auth activés $enabledTypes = array_keys($options->getEnabledAuthTypes()); // types d'auth activés
//iterate and attach multiple adapters and events if offered // on attache chaque adapter uniquement s'il est activé
foreach ($options->getAuthAdapters() as $priority => $adapterName) { foreach ($options->getAuthAdapters() as $priority => $adapterName) {
/** @var AbstractAdapter $adapter */ /** @var AbstractAdapter $adapter */
$adapter = $container->get($adapterName); $adapter = $container->get($adapterName);
if (in_array($adapter->getType(), $enabledTypes)) { if (in_array($adapter->getType(), $enabledTypes)) {
$adapter->attach($chain->getEventManager()); $adapter->attach($chain->getEventManager(), $priority);
} }
} }
...@@ -35,9 +36,9 @@ class AdapterChainServiceFactory ...@@ -35,9 +36,9 @@ class AdapterChainServiceFactory
* set options * set options
* *
* @param ModuleOptions $options * @param ModuleOptions $options
* @return AdapterChainServiceFactory * @return self
*/ */
public function setOptions(ModuleOptions $options) public function setOptions(ModuleOptions $options): self
{ {
$this->options = $options; $this->options = $options;
return $this; return $this;
...@@ -49,7 +50,7 @@ class AdapterChainServiceFactory ...@@ -49,7 +50,7 @@ class AdapterChainServiceFactory
* @param ContainerInterface|null $container (optional) Service Locator * @param ContainerInterface|null $container (optional) Service Locator
* @return ModuleOptions $options * @return ModuleOptions $options
*/ */
public function getOptions(ContainerInterface $container = null) public function getOptions(ContainerInterface $container = null): ModuleOptions
{ {
if (!$this->options) { if (!$this->options) {
if (!$container) { if (!$container) {
......
...@@ -98,10 +98,6 @@ class Cas extends AbstractAdapter ...@@ -98,10 +98,6 @@ class Cas extends AbstractAdapter
return true; return true;
} }
if (! $this->isEnabled()) {
return false;
}
error_reporting($oldErrorReporting = error_reporting() & ~E_NOTICE); error_reporting($oldErrorReporting = error_reporting() & ~E_NOTICE);
$this->getCasClient()->forceAuthentication(); $this->getCasClient()->forceAuthentication();
...@@ -131,23 +127,6 @@ class Cas extends AbstractAdapter ...@@ -131,23 +127,6 @@ class Cas extends AbstractAdapter
return true; return true;
} }
/**
* @return bool
*/
protected function isEnabled(): bool
{
$config = $this->moduleOptions->getCas();
if (! $config) {
return false;
}
if (isset($config['enabled'])) {
return (bool) $config['enabled'];
}
return true;
}
/** /**
* @inheritDoc * @inheritDoc
*/ */
...@@ -155,10 +134,6 @@ class Cas extends AbstractAdapter ...@@ -155,10 +134,6 @@ class Cas extends AbstractAdapter
{ {
parent::logout($e); parent::logout($e);
if (! $this->isEnabled()) {
return;
}
$storage = $this->getStorage()->read(); $storage = $this->getStorage()->read();
if (! isset($storage['identity'])) { if (! isset($storage['identity'])) {
return; return;
...@@ -247,9 +222,6 @@ class Cas extends AbstractAdapter ...@@ -247,9 +222,6 @@ class Cas extends AbstractAdapter
*/ */
public function reconfigureRoutesForCasAuth(RouteInterface $router) public function reconfigureRoutesForCasAuth(RouteInterface $router)
{ {
if (! $this->isEnabled()) {
return;
}
if(!$router instanceof RouteStackInterface) { if(!$router instanceof RouteStackInterface) {
return; return;
} }
......
...@@ -2,15 +2,9 @@ ...@@ -2,15 +2,9 @@
namespace UnicaenAuth\Authentication\Adapter; namespace UnicaenAuth\Authentication\Adapter;
use UnicaenAuth\Options\ModuleOptions;
use UnicaenAuth\Options\Traits\ModuleOptionsAwareTrait;
use Zend\Authentication\Result as AuthenticationResult; use Zend\Authentication\Result as AuthenticationResult;
use Zend\Crypt\Password\Bcrypt; use Zend\Crypt\Password\Bcrypt;
use Zend\EventManager\EventInterface;
use Zend\Session\Container as SessionContainer;
use ZfcUser\Authentication\Adapter\AdapterChainEvent;
use ZfcUser\Entity\UserInterface; use ZfcUser\Entity\UserInterface;
use ZfcUser\Mapper\UserInterface as UserMapperInterface;
/** /**
* Adpater d'authentification à partir de la base de données. * Adpater d'authentification à partir de la base de données.
...@@ -20,67 +14,29 @@ use ZfcUser\Mapper\UserInterface as UserMapperInterface; ...@@ -20,67 +14,29 @@ use ZfcUser\Mapper\UserInterface as UserMapperInterface;
* *
* @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr> * @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
*/ */
class Db extends AbstractAdapter class Db extends AbstractDb
{ {
use ModuleOptionsAwareTrait;
const TYPE = 'db'; const TYPE = 'db';
/**
* @var string
*/
protected $type = self::TYPE; protected $type = self::TYPE;
/**
* @var UserMapperInterface
*/
protected $mapper;
/** /**
* @var callable * @var callable
*/ */
protected $credentialPreprocessor; protected $credentialPreprocessor;
/** /**
* @var ModuleOptions * @return \ZfcUser\Entity\UserInterface|null
*/
protected $options;
/**
* @inheritDoc
*/ */
public function authenticate(EventInterface $e): bool protected function fetchUserObject(): ?UserInterface
{ {
// NB: Dans la version 3.0.0 de zf-commons/zfc-user, cette méthode prend un EventInterface. $identity = $this->event->getRequest()->getPost()->get('identity');
// Mais dans la branche 3.x, c'est un AdapterChainEvent !
// Si un jour c'est un AdapterChainEvent qui est attendu, plus besoin de faire $e->getTarget().
$event = $e->getTarget(); /* @var $event AdapterChainEvent */
if ($event->getIdentity()) {
return true;
}
if ($this->isSatisfied()) {
$storage = $this->getStorage()->read();
$event
->setIdentity($storage['identity'])
->setCode(AuthenticationResult::SUCCESS)
->setMessages(array('Authentication successful.'));
return true;
}
if (! $this->isEnabled()) {
return false;
}
$identity = $event->getRequest()->getPost()->get('identity');
$credential = $event->getRequest()->getPost()->get('credential');
$credential = $this->preProcessCredential($credential);
/** @var UserInterface|null $userObject */ /** @var UserInterface|null $userObject */
$userObject = null; $userObject = null;
// Cycle through the configured identity sources and test each // Cycle through the configured identity sources and test each
$fields = $this->options->getAuthIdentityFields(); $fields = $this->moduleOptions->getAuthIdentityFields();
while (!is_object($userObject) && count($fields) > 0) { while (!is_object($userObject) && count($fields) > 0) {
$mode = array_shift($fields); $mode = array_shift($fields);
switch ($mode) { switch ($mode) {
...@@ -94,69 +50,45 @@ class Db extends AbstractAdapter ...@@ -94,69 +50,45 @@ class Db extends AbstractAdapter
} }
if (!$userObject) { if (!$userObject) {
$event $this->event
->setCode(AuthenticationResult::FAILURE_IDENTITY_NOT_FOUND) ->setCode(AuthenticationResult::FAILURE_IDENTITY_NOT_FOUND)
->setMessages(array('A record with the supplied identity could not be found.')); ->setMessages([]); // NB: ne pas préciser la cause
$this->setSatisfied(false); $this->setSatisfied(false);
return false;
}
if ($this->options->getEnableUserState()) { return null;
// Don't allow user to login if state is not in allowed list
if (!in_array($userObject->getState(), $this->options->getAllowedLoginStates())) {
$event
->setCode(AuthenticationResult::FAILURE_UNCATEGORIZED)
->setMessages(array('A record with the supplied identity is not active.'));
$this->setSatisfied(false);
return false;
}
} }
return $userObject;
}
/**
* @param \ZfcUser\Entity\UserInterface $userObject
* @return bool
*/
protected function authenticateUserObject(UserInterface $userObject): bool
{
$credential = $this->event->getRequest()->getPost()->get('credential');
$credential = $this->preProcessCredential($credential);
$bcrypt = new Bcrypt(); $bcrypt = new Bcrypt();
$bcrypt->setCost($this->options->getPasswordCost()); $bcrypt->setCost($this->moduleOptions->getPasswordCost());
if (!$bcrypt->verify($credential, $userObject->getPassword())) { $ok = $bcrypt->verify($credential, $userObject->getPassword());
// Password does not match
$event if (!$ok) {
$this->event
->setCode(AuthenticationResult::FAILURE_CREDENTIAL_INVALID) ->setCode(AuthenticationResult::FAILURE_CREDENTIAL_INVALID)
->setMessages(array('Supplied credential is invalid.')); ->setMessages([]); // NB: