diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b4bbdf78392e390df14a5015d7cf98ad3e07332..6bbc8178a432622a9390eaa3ab7372bbf7b4bcae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ CHANGELOG ========= +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 + 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] 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 + fusion des configs + + 3.2.0 ----- - Configuration de la stratégie d'extraction d'un identifiant utile parmi les données d'authentification shibboleth diff --git a/config/module.config.php b/config/module.config.php index 438010a5697f3cd93533db1777c899e099f98b6b..984369974ea4208a2687ff0cc6787ec8b6ae88ea 100644 --- a/config/module.config.php +++ b/config/module.config.php @@ -1,5 +1,7 @@ <?php +namespace UnicaenAuth; + use UnicaenAuth\Authentication\Adapter\AdapterChainServiceFactory; use UnicaenAuth\Authentication\Adapter\Cas; use UnicaenAuth\Authentication\Adapter\CasAdapterFactory; @@ -18,6 +20,7 @@ use UnicaenAuth\Authentication\Storage\LdapFactory; use UnicaenAuth\Authentication\Storage\ShibFactory; use UnicaenAuth\Authentication\Storage\Usurpation; use UnicaenAuth\Authentication\Storage\UsurpationFactory; +use UnicaenAuth\Controller\AuthController; use UnicaenAuth\Controller\AuthControllerFactory; use UnicaenAuth\Controller\DroitsControllerFactory; use UnicaenAuth\Controller\UtilisateurControllerFactory; @@ -30,12 +33,14 @@ use UnicaenAuth\Form\ShibLoginForm; use UnicaenAuth\Form\ShibLoginFormFactory; use UnicaenAuth\Guard\PrivilegeControllerFactory; use UnicaenAuth\Guard\PrivilegeRouteFactory; +use UnicaenAuth\Options\ModuleOptions; use UnicaenAuth\ORM\Event\Listeners\HistoriqueListenerFactory; use UnicaenAuth\Provider\Rule\PrivilegeRuleProviderFactory; use UnicaenAuth\Service\CasService; use UnicaenAuth\Service\CasServiceFactory; use UnicaenAuth\Service\ShibService; use UnicaenAuth\Service\ShibServiceFactory; +use UnicaenAuth\Service\UserContext; use UnicaenAuth\Service\UserContextFactory; use UnicaenAuth\Service\UserFactory; use UnicaenAuth\Service\UserMapperFactory; @@ -70,6 +75,15 @@ use Zend\Authentication\AuthenticationService; use Zend\ServiceManager\Proxy\LazyServiceFactory; $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). */ @@ -197,19 +211,19 @@ $settings = [ /* // domaine (ex: 'unicaen.fr') de l'EPPN (ex: hochonp@unicaen.fr') 'unicaen.fr' => [ - [ + 'supannRefId' => [ // nom du 1er attribut recherché 'name' => 'supannRefId', // ex: '{OCTOPUS:ID}1234;{ISO15693}044D1AZE7A5P80' // pattern éventuel pour extraire la partie intéressante 'preg_match_pattern' => '|\{OCTOPUS:ID\}(\d+)|', // ex: permet d'extraire '1234' ], - [ + 'supannEmpId' => [ // nom du 2e attribut recherché, si le 1er est introuvable 'name' => 'supannEmpId', // pas de pattern donc valeur brute utilisée 'preg_match_pattern' => null, ], - [ + 'supannEtuId' => [ // nom du 3e attribut recherché, si le 2e est introuvable 'name' => 'supannEtuId', ], @@ -217,10 +231,10 @@ $settings = [ */ // config de repli pour tous les autres domaines 'default' => [ - [ + 'supannEmpId' => [ 'name' => 'supannEmpId', ], - [ + 'supannEtuId' => [ 'name' => 'supannEtuId', ], ], @@ -629,21 +643,22 @@ return [ // in /var/www/sygal/module/Application/src/Application/Controller/UtilisateurController.php on line 34 'service_manager' => [ 'aliases' => [ + 'unicaen-auth_module_options' => ModuleOptions::class, 'zfcuser_login_form' => LoginForm::class, 'Zend\Authentication\AuthenticationService' => 'zfcuser_auth_service', 'UnicaenAuth\Privilege\PrivilegeProvider' => 'UnicaenAuth\Service\Privilege', '\UnicaenAuth\Guard\PrivilegeController' => 'UnicaenAuth\Guard\PrivilegeController', 'unicaen-auth_user_service' => 'UnicaenAuth\Service\User', // pour la compatibilité - 'authUserContext' => 'UnicaenAuth\Service\UserContext', // pour la compatibilité - 'AuthUserContext' => 'UnicaenAuth\Service\UserContext', // pour la compatibilité + 'authUserContext' => UserContext::class, // pour la compatibilité + 'AuthUserContext' => UserContext::class, // pour la compatibilité ], 'invokables' => [ 'UnicaenAuth\View\RedirectionStrategy' => 'UnicaenAuth\View\RedirectionStrategy', 'UnicaenAuth\Service\CategoriePrivilege' => 'UnicaenAuth\Service\CategoriePrivilegeService', ], 'factories' => [ - 'unicaen-auth_module_options' => 'UnicaenAuth\Options\ModuleOptionsFactory', + ModuleOptions::class => 'UnicaenAuth\Options\ModuleOptionsFactory', 'zfcuser_auth_service' => 'UnicaenAuth\Authentication\AuthenticationServiceFactory', 'UnicaenAuth\Authentication\Storage\Chain' => 'UnicaenAuth\Authentication\Storage\ChainServiceFactory', 'UnicaenAuth\Provider\Identity\Chain' => 'UnicaenAuth\Provider\Identity\ChainServiceFactory', @@ -659,7 +674,7 @@ return [ 'zfcuser_redirect_callback' => 'UnicaenAuth\Authentication\RedirectCallbackFactory', // substituion CasService::class => CasServiceFactory::class, ShibService::class => ShibServiceFactory::class, - 'UnicaenAuth\Service\UserContext' => UserContextFactory::class, + UserContext::class => UserContextFactory::class, 'zfcuser_user_mapper' => UserMapperFactory::class, 'MouchardCompleterAuth' => 'UnicaenAuth\Mouchard\MouchardCompleterAuthFactory', LocalAdapter::class => LocalAdapterFactory::class, @@ -708,10 +723,11 @@ return [ ], 'controllers' => [ - 'invokables' => [ + 'aliases' => [ + 'UnicaenAuth\Controller\Auth' => AuthController::class, ], 'factories' => [ - 'UnicaenAuth\Controller\Auth' => AuthControllerFactory::class, + AuthController::class => AuthControllerFactory::class, 'UnicaenAuth\Controller\Utilisateur' => UtilisateurControllerFactory::class, 'UnicaenAuth\Controller\Droits' => DroitsControllerFactory::class, ], diff --git a/config/unicaen-auth.local.php.dist b/config/unicaen-auth.local.php.dist index 1b182e74cd1ef57b1aa4be36331bf8be75f69581..1dba9f252f588e72d2422bd284d7546f68d330a3 100644 --- a/config/unicaen-auth.local.php.dist +++ b/config/unicaen-auth.local.php.dist @@ -124,29 +124,29 @@ return [ 'shib_user_id_extractor' => [ // domaine (ex: 'unicaen.fr') de l'EPPN (ex: hochonp@unicaen.fr') // 'unicaen.fr' => [ -// [ +// 'supannRefId' => [ // // nom du 1er attribut recherché // 'name' => 'supannRefId', // ex: '{OCTOPUS:ID}1234;{ISO15693}044D1AZE7A5P80' // // pattern éventuel pour extraire la partie intéressante // 'preg_match_pattern' => '|\{OCTOPUS:ID\}(\d+)|', // ex: permet d'extraire '1234' // ], -// [ +// 'supannEmpId' => [ // // nom du 2e attribut recherché // 'name' => 'supannEmpId', // // pas de pattern donc valeur brute utilisée // 'preg_match_pattern' => null, // ], -// [ +// 'supannEtuId' => [ // // nom du 3e attribut recherché // 'name' => 'supannEtuId', // ], // ], // config de repli pour tous les autres domaines 'default' => [ - [ + 'supannEmpId' => [ 'name' => 'supannEmpId', ], - [ + 'supannEtuId' => [ 'name' => 'supannEtuId', ], ], diff --git a/data/schema.sqlite.sql b/data/schema.sqlite.sql index 5e8a087ac525dad531be819c7f0320c28125fd3c..6339105798318c15b5488d67a01cb0798d8469ba 100644 --- a/data/schema.sqlite.sql +++ b/data/schema.sqlite.sql @@ -6,7 +6,8 @@ CREATE TABLE IF NOT EXISTS user display_name VARCHAR(64) DEFAULT NULL, password VARCHAR(128) NOT NULL, state SMALLINT default 1, - last_role_id INTEGER default null + last_role_id INTEGER default null, + created_on DATE default current_date not null ); ALTER TABLE user ADD PASSWORD_RESET_TOKEN varchar(256) DEFAULT NULL; CREATE UNIQUE INDEX user_unique_username ON user(username); diff --git a/data/schema_mysql.sql b/data/schema_mysql.sql index 4b17d9a36ad48bc9b61bb77c651f7465a989cb4a..244b9f7bbf68013d2b4570a868aa23f73f773305 100644 --- a/data/schema_mysql.sql +++ b/data/schema_mysql.sql @@ -6,6 +6,7 @@ CREATE TABLE user ( password VARCHAR(128) NOT NULL, state SMALLINT default 1, last_role_id INT(11) default null, + created_on DATE default now() not null, PRIMARY KEY (id), UNIQUE INDEX unique_username (username ASC) ) ENGINE=InnoDB DEFAULT CHARACTER SET = utf8 COLLATE = utf8_unicode_ci; diff --git a/data/schema_oracle.sql b/data/schema_oracle.sql index 19cea72888b9d66f4f4f9d45dfe712d756d98653..ff894f228f26df55e8448a43feaa002949ec8926 100644 --- a/data/schema_oracle.sql +++ b/data/schema_oracle.sql @@ -6,6 +6,7 @@ CREATE TABLE "USER" "PASSWORD" VARCHAR2(128 CHAR) NOT NULL ENABLE, "STATE" SMALLINT DEFAULT 1 NOT NULL ENABLE, "last_role_id" NUMBER(*,0), + created_on DATE default sysdate not null, CONSTRAINT "USER_PK" PRIMARY KEY ("ID"), CONSTRAINT "USER_USERNAME_UN" UNIQUE ("USERNAME"), CONSTRAINT "USER_LAST_ROLE_FK" FOREIGN KEY ("last_role_id") REFERENCES USER_ROLE ("ID") ENABLE @@ -64,6 +65,24 @@ INSERT INTO USER_ROLE_LINKER(user_id, role_id) SELECT u.id, r.id FROM "USER" u, user_role r WHERE u.username = 'demo' and r.role_id = 'Standard'; +CREATE TABLE USER_TOKEN +( + ID NUMBER(*, 0) NOT NULL, + USER_ID NUMBER(*,0) NOT NULL ENABLE, + TOKEN VARCHAR2(256) NOT NULL, + ACTION VARCHAR2(256) NOT NULL, + NB_ACTIONS smallint default 0 NOT NULL, + NB_ACTIONS_MAX smallint default 1 NOT NULL, + created_on DATE default sysdate not null, + expired_on DATE not null, + last_used_on DATE, + CONSTRAINT USER_TOKEN_PK PRIMARY KEY (ID), + CONSTRAINT USER_TOKEN_USER_FK UNIQUE (USER_ID) +); +CREATE INDEX USER_TOKEN_USER_IDX ON USER_TOKEN (USER_ID); +CREATE SEQUENCE USER_TOKEN_ID_SEQ; + + /** * Privilèges diff --git a/data/schema_postgresql.sql b/data/schema_postgresql.sql index 21b51a142c81b81ce4e9b90c877992203d0a8b14..634b9916833e84178a3c62e81e999c6594f614ad 100644 --- a/data/schema_postgresql.sql +++ b/data/schema_postgresql.sql @@ -6,6 +6,7 @@ CREATE TABLE "user" ( password VARCHAR(128) NOT NULL, state SMALLINT default 1, last_role_id SMALLINT, + created_on DATE default current_timestamp not null, FOREIGN KEY (last_role_id) REFERENCES user_role (id) ON DELETE SET NULL ) ; CREATE UNIQUE INDEX user_username_unique ON "user" (username); diff --git a/src/UnicaenAuth/Authentication/Adapter/AbstractDb.php b/src/UnicaenAuth/Authentication/Adapter/AbstractDb.php new file mode 100644 index 0000000000000000000000000000000000000000..942b496ca2e948f58f10ccc27bf120b9a7be4f53 --- /dev/null +++ b/src/UnicaenAuth/Authentication/Adapter/AbstractDb.php @@ -0,0 +1,125 @@ +<?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 diff --git a/src/UnicaenAuth/Authentication/Adapter/AdapterChainServiceFactory.php b/src/UnicaenAuth/Authentication/Adapter/AdapterChainServiceFactory.php index 9f710458a25f4b0c94c0462ef216874e552a6baf..f17f58f4761252084d9743bf46ee693c54946279 100644 --- a/src/UnicaenAuth/Authentication/Adapter/AdapterChainServiceFactory.php +++ b/src/UnicaenAuth/Authentication/Adapter/AdapterChainServiceFactory.php @@ -1,4 +1,5 @@ <?php + namespace UnicaenAuth\Authentication\Adapter; use Interop\Container\ContainerInterface; @@ -7,19 +8,19 @@ use ZfcUser\Options\ModuleOptions; class AdapterChainServiceFactory { - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) + public function __invoke(ContainerInterface $container, $requestedName, array $options = null): AdapterChain { $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 + // on attache chaque adapter uniquement s'il est activé foreach ($options->getAuthAdapters() as $priority => $adapterName) { /** @var AbstractAdapter $adapter */ $adapter = $container->get($adapterName); if (in_array($adapter->getType(), $enabledTypes)) { - $adapter->attach($chain->getEventManager()); + $adapter->attach($chain->getEventManager(), $priority); } } @@ -35,9 +36,9 @@ class AdapterChainServiceFactory * set options * * @param ModuleOptions $options - * @return AdapterChainServiceFactory + * @return self */ - public function setOptions(ModuleOptions $options) + public function setOptions(ModuleOptions $options): self { $this->options = $options; return $this; @@ -49,7 +50,7 @@ class AdapterChainServiceFactory * @param ContainerInterface|null $container (optional) Service Locator * @return ModuleOptions $options */ - public function getOptions(ContainerInterface $container = null) + public function getOptions(ContainerInterface $container = null): ModuleOptions { if (!$this->options) { if (!$container) { diff --git a/src/UnicaenAuth/Authentication/Adapter/Cas.php b/src/UnicaenAuth/Authentication/Adapter/Cas.php index a973c3c011a99c3cdbd092cd000fa91c5c7385d6..6a8afa84875ee7645d789861c2953b12dfecee72 100644 --- a/src/UnicaenAuth/Authentication/Adapter/Cas.php +++ b/src/UnicaenAuth/Authentication/Adapter/Cas.php @@ -98,10 +98,6 @@ class Cas extends AbstractAdapter return true; } - if (! $this->isEnabled()) { - return false; - } - error_reporting($oldErrorReporting = error_reporting() & ~E_NOTICE); $this->getCasClient()->forceAuthentication(); @@ -131,23 +127,6 @@ class Cas extends AbstractAdapter 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 */ @@ -155,10 +134,6 @@ class Cas extends AbstractAdapter { parent::logout($e); - if (! $this->isEnabled()) { - return; - } - $storage = $this->getStorage()->read(); if (! isset($storage['identity'])) { return; @@ -247,9 +222,6 @@ class Cas extends AbstractAdapter */ public function reconfigureRoutesForCasAuth(RouteInterface $router) { - if (! $this->isEnabled()) { - return; - } if(!$router instanceof RouteStackInterface) { return; } diff --git a/src/UnicaenAuth/Authentication/Adapter/Db.php b/src/UnicaenAuth/Authentication/Adapter/Db.php index 1591bb15d921166ee1a65db0aefde151f9f95956..228d2dd3160ace9230ffc300271058505f205bc0 100644 --- a/src/UnicaenAuth/Authentication/Adapter/Db.php +++ b/src/UnicaenAuth/Authentication/Adapter/Db.php @@ -2,15 +2,9 @@ namespace UnicaenAuth\Authentication\Adapter; -use UnicaenAuth\Options\ModuleOptions; -use UnicaenAuth\Options\Traits\ModuleOptionsAwareTrait; use Zend\Authentication\Result as AuthenticationResult; 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\Mapper\UserInterface as UserMapperInterface; /** * Adpater d'authentification à partir de la base de données. @@ -20,67 +14,29 @@ use ZfcUser\Mapper\UserInterface as UserMapperInterface; * * @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr> */ -class Db extends AbstractAdapter +class Db extends AbstractDb { - use ModuleOptionsAwareTrait; - const TYPE = 'db'; - /** - * @var string - */ protected $type = self::TYPE; - /** - * @var UserMapperInterface - */ - protected $mapper; - /** * @var callable */ protected $credentialPreprocessor; /** - * @var ModuleOptions - */ - protected $options; - - /** - * @inheritDoc + * @return \ZfcUser\Entity\UserInterface|null */ - 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. - // 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 = $this->event->getRequest()->getPost()->get('identity'); - $identity = $event->getRequest()->getPost()->get('identity'); - $credential = $event->getRequest()->getPost()->get('credential'); - $credential = $this->preProcessCredential($credential); /** @var UserInterface|null $userObject */ $userObject = null; // Cycle through the configured identity sources and test each - $fields = $this->options->getAuthIdentityFields(); + $fields = $this->moduleOptions->getAuthIdentityFields(); while (!is_object($userObject) && count($fields) > 0) { $mode = array_shift($fields); switch ($mode) { @@ -94,69 +50,45 @@ class Db extends AbstractAdapter } if (!$userObject) { - $event + $this->event ->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); - return false; - } - if ($this->options->getEnableUserState()) { - // 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 null; } + 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->setCost($this->options->getPasswordCost()); - if (!$bcrypt->verify($credential, $userObject->getPassword())) { - // Password does not match - $event + $bcrypt->setCost($this->moduleOptions->getPasswordCost()); + $ok = $bcrypt->verify($credential, $userObject->getPassword()); + + if (!$ok) { + $this->event ->setCode(AuthenticationResult::FAILURE_CREDENTIAL_INVALID) - ->setMessages(array('Supplied credential is invalid.')); + ->setMessages([]); // NB: ne pas préciser la cause $this->setSatisfied(false); + return false; } - // regen the id - $session = new SessionContainer($this->getStorage()->getNamespace()); - $session->getManager()->regenerateId(); - - // Success! - $identity = $this->createSessionIdentity($userObject->getUsername()); - $event->setIdentity($identity); // Update user's password hash if the cost parameter has changed $this->updateUserPasswordHash($userObject, $credential, $bcrypt); - $this->setSatisfied(true); - $storage = $this->getStorage()->read(); - $storage['identity'] = $event->getIdentity(); - $this->getStorage()->write($storage); - $event - ->setCode(AuthenticationResult::SUCCESS) - ->setMessages(array('Authentication successful.')); return true; } - /** - * @return bool - */ - protected function isEnabled(): bool - { - $config = $this->moduleOptions->getDb(); - - if (isset($config['enabled'])) { - return (bool) $config['enabled']; - } - - return false; - } - protected function updateUserPasswordHash(UserInterface $userObject, $password, Bcrypt $bcrypt): self { $hash = explode('$', $userObject->getPassword()); @@ -179,19 +111,6 @@ class Db extends AbstractAdapter return $credential; } - /** - * setMapper - * - * @param UserMapperInterface $mapper - * @return self - */ - public function setMapper(UserMapperInterface $mapper): self - { - $this->mapper = $mapper; - - return $this; - } - /** * Get credentialPreprocessor. * @@ -213,22 +132,4 @@ class Db extends AbstractAdapter $this->credentialPreprocessor = $credentialPreprocessor; return $this; } - - /** - * @param \ZfcUser\Options\ModuleOptions $options - * @return self - */ - public function setOptions(\ZfcUser\Options\ModuleOptions $options): self - { - $this->options = $options; - return $this; - } - - /** - * @return ModuleOptions - */ - protected function getOptions(): ModuleOptions - { - return $this->options; - } } \ No newline at end of file diff --git a/src/UnicaenAuth/Authentication/Adapter/DbAdapterFactory.php b/src/UnicaenAuth/Authentication/Adapter/DbAdapterFactory.php index 91fef9611f5d8c14ac26fe797092d03abba4d382..ac7619121af708193926ae6aee522118e0b09146 100644 --- a/src/UnicaenAuth/Authentication/Adapter/DbAdapterFactory.php +++ b/src/UnicaenAuth/Authentication/Adapter/DbAdapterFactory.php @@ -5,18 +5,15 @@ namespace UnicaenAuth\Authentication\Adapter; use Interop\Container\ContainerInterface; use UnicaenAuth\Options\ModuleOptions; use Zend\Authentication\Storage\Session; -use Zend\EventManager\EventManagerAwareInterface; use ZfcUser\Mapper\UserInterface as UserMapperInterface; class DbAdapterFactory { /** * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return mixed|Cas|Db|Ldap|EventManagerAwareInterface + * @return Db */ - public function __invoke(ContainerInterface $container, string $requestedName, array $options = null) + public function __invoke(ContainerInterface $container): Db { /** @var UserMapperInterface $userMapper */ $userMapper = $container->get('zfcuser_user_mapper'); @@ -30,7 +27,6 @@ class DbAdapterFactory $container->get('unicaen-auth_module_options')->toArray()); $moduleOptions = new ModuleOptions($options); $adapter->setModuleOptions($moduleOptions); - $adapter->setOptions($moduleOptions); $substitut = $moduleOptions->getDb()['type'] ?? null; if ($substitut !== null) { diff --git a/src/UnicaenAuth/Authentication/Adapter/Ldap.php b/src/UnicaenAuth/Authentication/Adapter/Ldap.php index eb39f8e7ba0a9d43eaa45f67994f7e53e907d43f..8bceddcacfa56a8185147021ccb1eed5dff13899 100644 --- a/src/UnicaenAuth/Authentication/Adapter/Ldap.php +++ b/src/UnicaenAuth/Authentication/Adapter/Ldap.php @@ -103,10 +103,6 @@ class Ldap extends AbstractAdapter implements EventManagerAwareInterface return true; } - if (! $this->isEnabled()) { - return false; - } - $username = $event->getRequest()->getPost()->get('identity'); $credential = $event->getRequest()->getPost()->get('credential'); @@ -122,7 +118,7 @@ class Ldap extends AbstractAdapter implements EventManagerAwareInterface if (! $success) { $event ->setCode(AuthenticationResult::FAILURE) - ->setMessages(['LDAP bind failed.']); + ->setMessages([/*'LDAP bind failed.'*/]); $this->setSatisfied(false); return false; } @@ -132,7 +128,7 @@ class Ldap extends AbstractAdapter implements EventManagerAwareInterface if (!$ldapPeople) { $event ->setCode(AuthenticationResult::FAILURE) - ->setMessages(['Authentication failed.']); + ->setMessages([/*'Authentication failed.'*/]); $this->setSatisfied(false); return false; } @@ -158,20 +154,6 @@ class Ldap extends AbstractAdapter implements EventManagerAwareInterface return true; } - /** - * @return bool - */ - protected function isEnabled(): bool - { - $config = $this->moduleOptions->getLdap(); - - if (isset($config['enabled'])) { - return (bool) $config['enabled']; - } - - return false; - } - /** * Extrait le loginUsurpateur et le loginUsurpé si l'identifiant spécifé est de la forme * "loginUsurpateur=loginUsurpé". diff --git a/src/UnicaenAuth/Authentication/Adapter/LocalAdapter.php b/src/UnicaenAuth/Authentication/Adapter/LocalAdapter.php index d91764cc2997fd3cd173709609b3fa173f342114..1890a225009e88f43004a90af32253d9521e92f7 100644 --- a/src/UnicaenAuth/Authentication/Adapter/LocalAdapter.php +++ b/src/UnicaenAuth/Authentication/Adapter/LocalAdapter.php @@ -40,10 +40,6 @@ class LocalAdapter extends AbstractAdapter return false; } - if (! $this->isEnabled()) { - return false; - } - $result = $this->getEventManager()->triggerUntil(function ($result) { return $result === true || $result instanceof Response; }, 'authenticate', $event); @@ -79,18 +75,4 @@ class LocalAdapter extends AbstractAdapter } } } - - /** - * @return bool - */ - protected function isEnabled() - { - $config = $this->moduleOptions->getLocal(); - - if (isset($config['enabled'])) { - return (bool) $config['enabled']; - } - - return false; - } } \ No newline at end of file diff --git a/src/UnicaenAuth/Authentication/Adapter/Shib.php b/src/UnicaenAuth/Authentication/Adapter/Shib.php index 56cdcaebcfa50f219debfff84f91090744a0debb..4b8952df2b027b994234730d0faeae25e9f3a01f 100644 --- a/src/UnicaenAuth/Authentication/Adapter/Shib.php +++ b/src/UnicaenAuth/Authentication/Adapter/Shib.php @@ -96,10 +96,6 @@ class Shib extends AbstractAdapter return true; } - if (! $this->isEnabled()) { - return false; - } - $shibUser = $this->shibService->getAuthenticatedUser(); if ($shibUser === null) { @@ -135,20 +131,6 @@ class Shib extends AbstractAdapter return true; } - /** - * @return bool - */ - protected function isEnabled(): bool - { - $config = $this->moduleOptions->getShib(); - - if (isset($config['enabled'])) { - return (bool) $config['enabled']; - } - - return false; - } - /** * @inheritDoc */ @@ -156,10 +138,6 @@ class Shib extends AbstractAdapter { parent::logout($e); - if (! $this->isEnabled()) { - return; - } - $storage = $this->getStorage()->read(); if (! isset($storage['identity'])) { return; diff --git a/src/UnicaenAuth/Authentication/Storage/Shib.php b/src/UnicaenAuth/Authentication/Storage/Shib.php index 4248137accf6dbc6df6bb9cfca53c34973f3c37a..3ee3ac78197025ba716a0d42b90e91d0e3d048c9 100644 --- a/src/UnicaenAuth/Authentication/Storage/Shib.php +++ b/src/UnicaenAuth/Authentication/Storage/Shib.php @@ -46,11 +46,10 @@ class Shib extends AbstractStorage $sessionIdentity = $this->storage->read(); $username = $sessionIdentity->getUsername(); -// // L'identité en session doit ressembler à un EPPN. -// $looksLikeEppn = strpos($username, '@') !== false; -// if (! $looksLikeEppn) { -// return null; -// } + // L'identité en session doit ressembler à un EPPN. + if (! ShibUser::isEppn($username)) { + return null; + } return $this->shibService->getAuthenticatedUser(); } diff --git a/src/UnicaenAuth/Controller/AuthController.php b/src/UnicaenAuth/Controller/AuthController.php index 8fd1a6f0c00698ca1abde4f587681d8d69a9471b..ff5a73b40b59dc792473458598206c20730d20fe 100644 --- a/src/UnicaenAuth/Controller/AuthController.php +++ b/src/UnicaenAuth/Controller/AuthController.php @@ -14,7 +14,6 @@ use UnicaenAuth\Options\Traits\ModuleOptionsAwareTrait; use UnicaenAuth\Service\ShibService; use UnicaenAuth\Service\Traits\ShibServiceAwareTrait; use UnicaenAuth\Service\Traits\UserServiceAwareTrait; -use Zend\Form\FormInterface; use Zend\Http\Request; use Zend\Http\Response; use Zend\Mvc\Controller\AbstractActionController; @@ -33,15 +32,26 @@ use ZfcUser\Controller\Plugin\ZfcUserAuthentication; */ class AuthController extends AbstractActionController { - const TYPES_LOCAL = ['db', 'ldap']; - const TYPE_LOCAL = 'local'; + const AUTH_TYPE_LOCAL = 'local'; + const AUTH_TYPE_LOCAL_DB = 'db'; + const AUTH_TYPE_LOCAL_LDAP = 'ldap'; + const AUTH_TYPES_LOCAL = [self::AUTH_TYPE_LOCAL_DB, self::AUTH_TYPE_LOCAL_LDAP]; + + const AUTH_TYPE_TOKEN = 'token'; + + const AUTH_TYPE_QUERY_PARAM = 'authtype'; use ShibServiceAwareTrait; use UserServiceAwareTrait; use ModuleOptionsAwareTrait; /** - * @var FormInterface[] ['type' => FormInterface] + * @var string + */ + protected $defaultAuthType = self::AUTH_TYPE_LOCAL_DB; + + /** + * @var LoginForm[] ['type' => LoginForm] */ protected $loginFormForType; @@ -64,11 +74,10 @@ class AuthController extends AbstractActionController * @param string $type * @return LoginForm */ - public function getLoginFormForType(string $type): FormInterface + public function getLoginFormForType(string $type): LoginForm { - if ($type === self::TYPE_LOCAL) { - $types = self::TYPES_LOCAL; - $type = reset($types); + if ($type === self::AUTH_TYPE_LOCAL) { + $type = $this->defaultAuthType; } if (! isset($this->loginFormForType[$type])) { @@ -94,7 +103,7 @@ class AuthController extends AbstractActionController /** * @var string */ - protected $failedLoginMessage = "Identifiant ou mot de passe incorrect."; + protected $failedLoginMessage = "L'authentification a échoué, merci de réessayer."; /** * Login form @@ -105,28 +114,24 @@ class AuthController extends AbstractActionController return $this->redirect()->toRoute($this->moduleOptions->getLoginRedirectRoute()); } - $request = $this->getRequest(); - $originalType = $this->params('type'); - - $type = $this->processedType($originalType); - if ($type !== $originalType) { + $typeFromRoute = $this->params('type'); + $typeFromRequest = $this->getRequestedAuthenticationType(); + $type = $this->processedType($typeFromRequest); + if ($type !== $typeFromRoute) { return $this->redirect()->toRoute(null, ['type' => $type], ['query' => $this->params()->fromQuery()], true); } + $request = $this->getRequest(); $form = $this->getLoginFormForType($type); + $form->initFromRequest($request); // si le formulaire POSTé ne possède aucun champ identifiant, on va directement à authenticateAction() if ($request->isPost() and ! $request->getPost()->get('identity')) { return $this->redirect()->toRoute('zfcuser/authenticate', [], ['query' => $this->params()->fromQuery()], true); } - if ($this->moduleOptions->getUseRedirectParameterIfPresent() && $request->getQuery()->get('redirect')) { - $redirect = $request->getQuery()->get('redirect'); - } else { - $redirect = false; - } - - $queryParams = ['query' => $redirect ? ['redirect' => $redirect] : []]; + $redirect = $this->getRequestedRedirect(); + $queryParams = ['query' => ($redirect ? ['redirect' => $redirect] : [])]; $url = $this->url()->fromRoute(null, [], $queryParams, true); $form->setAttribute('action', $url); @@ -155,13 +160,48 @@ class AuthController extends AbstractActionController return $this->authenticateAction(); } + /** + * @return string|null + */ + protected function getRequestedAuthenticationType(): ?string + { + // si un type est spécifié dans la route, on prend + if ($requestedType = $this->params('type')) { + return $requestedType; + } + + $requestedType = null; + + // un type d'auth peut être demandé dans l'URL de redirection + if ($redirect = $this->getRequestedRedirect()) { + parse_str(parse_url(urldecode($redirect), PHP_URL_QUERY), $queryParams); + if (isset($queryParams[self::AUTH_TYPE_QUERY_PARAM])) { + $requestedType = $queryParams[self::AUTH_TYPE_QUERY_PARAM]; + } + } + + return $requestedType; + } + + /** + * @return string|null + */ + protected function getRequestedRedirect(): ?string + { + if (! $this->moduleOptions->getUseRedirectParameterIfPresent()) { + return null; + } + + return $this->params()->fromQuery('redirect'); + } + /** * @param string|null $type * @return string */ private function processedType(string $type = null): string { - if ($type === self::TYPE_LOCAL) { + if ($type === self::AUTH_TYPE_LOCAL) { return $type; } @@ -173,8 +213,8 @@ class AuthController extends AbstractActionController } // type spécial pour les modes d'authentification nécessitant un formulaire username/password - if (in_array($type, self::TYPES_LOCAL)) { - $type = self::TYPE_LOCAL; + if (in_array($type, self::AUTH_TYPES_LOCAL)) { + $type = self::AUTH_TYPE_LOCAL; } return $type; @@ -205,7 +245,8 @@ class AuthController extends AbstractActionController $auth = $this->zfcUserAuthentication()->getAuthService()->authenticate($adapter); if (!$auth->isValid()) { - $this->flashMessenger()->setNamespace('zfcuser-login-form')->addMessage($this->failedLoginMessage); + $message = $auth->getMessages()[0] ?? $this->failedLoginMessage; + $this->flashMessenger()->setNamespace('zfcuser-login-form')->addMessage($message); $adapter->resetAdapters(); $url = $this->url()->fromRoute(null, [], ['query' => $redirect ? ['redirect' => $redirect] : []], true); return $this->redirect()->toUrl($url); diff --git a/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php b/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php index 68617a3b1c08c7f0fd2a84a0d40e00a1ac2fa650..46d47487c7fa859013016c17e52b0909b3e5df02 100644 --- a/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php +++ b/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php @@ -3,6 +3,7 @@ namespace UnicaenAuth\Entity\Shibboleth; use UnicaenAuth\Entity\Db\AbstractUser; +use Webmozart\Assert\Assert; use ZfcUser\Entity\UserInterface; class ShibUser implements UserInterface @@ -47,6 +48,41 @@ class ShibUser implements UserInterface */ protected $state = 1; + /** + * Teste si une chaîne ressemble à un EPPN. + * + * @param string $username + * @return bool + */ + static public function isEppn(string $username) + { + if (($pos = strpos($username, '@')) === false) { + return false; + } + + $domain = substr($username, $pos + 1); + if (filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) === false) { + return false; + } + + return true; + } + + /** + * Extrait le domaine de l'EPPN spécifié. + * + * @param string $eppn + * @return string + */ + static public function extractDomainFromEppn(string $eppn) + { + Assert::true(static::isEppn($eppn), "La chaîne suivante n'est pas un EPPN valide : " . $eppn); + + $parts = explode('@', $eppn); + + return $parts[1]; + } + /** * Retourne la partie domaine DNS de l'EPPN. * Retourne par exemple "unicaen.fr" lorsque l'EPPN est "tartempion@unicaen.fr" @@ -55,9 +91,7 @@ class ShibUser implements UserInterface */ public function getEppnDomain() { - $parts = explode('@', $this->getEppn()); - - return $parts[1]; + return static::extractDomainFromEppn($this->getEppn()); } /** @@ -75,6 +109,8 @@ class ShibUser implements UserInterface */ public function setEppn($eppn) { + Assert::true(static::isEppn($eppn), "La chaîne suivante n'est pas un EPPN valide : " . $eppn); + $this->setUsername($eppn); } diff --git a/src/UnicaenAuth/Form/LoginForm.php b/src/UnicaenAuth/Form/LoginForm.php index 1ef8270bf57244a2aee3a2c8697e21c8e3d1bd07..8e0cdd901164feed4c4b845ebb321a164dc99c1f 100644 --- a/src/UnicaenAuth/Form/LoginForm.php +++ b/src/UnicaenAuth/Form/LoginForm.php @@ -2,6 +2,7 @@ namespace UnicaenAuth\Form; +use Zend\Http\Request; use ZfcUser\Form\Login; class LoginForm extends Login @@ -11,6 +12,11 @@ class LoginForm extends Login */ protected $types = ['db', 'ldap']; + /** + * @var bool + */ + protected $hidden = false; + /** * @param string[] $types * @return self @@ -28,4 +34,31 @@ class LoginForm extends Login { return $this->types; } + + /** + * @param \Zend\Http\Request $request + * @return void + */ + public function initFromRequest(Request $request) + { + + } + + /** + * @return bool + */ + public function isHidden(): bool + { + return $this->hidden; + } + + /** + * @param bool $hidden + * @return self + */ + public function setHidden(bool $hidden): self + { + $this->hidden = $hidden; + return $this; + } } diff --git a/src/UnicaenAuth/Options/ModuleOptions.php b/src/UnicaenAuth/Options/ModuleOptions.php index ae2daf8d8c00c342a34a8983d7aaaa9e12abef59..dfae29844512e5c54fdcaa8fcf80d31736a1153d 100644 --- a/src/UnicaenAuth/Options/ModuleOptions.php +++ b/src/UnicaenAuth/Options/ModuleOptions.php @@ -60,10 +60,41 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions */ protected $entityManagerName = 'doctrine.entitymanager.orm_default'; + /** + * @var array + */ + protected $authTypes; + + /** + * @var array|\Traversable|null + */ + protected $originalOptions; + + /** + * @inheritDoc + */ + public function __construct($options = null) + { + parent::__construct($options); + + /** On conserve le tableau de config original, @see __get(). */ + $this->originalOptions = $options; + } + + /** + * @param array $authTypes + * @return self + */ + public function setAuthTypes(array $authTypes): self + { + $this->authTypes = $authTypes; + return $this; + } + /** * @return array */ - public function getLocal() + public function getLocal(): array { return $this->local; } @@ -72,7 +103,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions * @param array $local * @return self */ - public function setLocal(array $local) + public function setLocal(array $local): self { $this->local = $local; return $this; @@ -81,19 +112,19 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions /** * @return array */ - public function getDb() + public function getDb(): array { return $this->local['db']; } /** - * @param array $db - * @return ModuleOptions + * @param array $config + * @return self */ - public function setDb(array $db) + public function setDb(array $config): self { - $this->db = $db; - $this->local['db'] = $db; + $this->db = $config; + $this->local['db'] = $config; return $this; } @@ -103,7 +134,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions * * @return array */ - public function getLdap() + public function getLdap(): array { return $this->local['ldap']; } @@ -114,7 +145,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions * @param array $ldap * @return self */ - public function setLdap(array $ldap) + public function setLdap(array $ldap): self { $this->ldap = $ldap; $this->local['ldap'] = $ldap; @@ -123,15 +154,18 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions } /** - * @return array[] Ex: ['db' => ['enabled'=>true, 'type'=>'local'], 'shib' => ['enabled'=>true]] + * Configs ordonnées des types d'authentification activés. + * + * @return array[] Exemple : ['local' => ['enabled'=>true, ...], 'shib' => ['enabled'=>true, ...]] */ - public function getEnabledAuthTypes() + public function getEnabledAuthTypes(): array { - $array = [ - 'local' => $this->getLocal(), - 'cas' => $this->getCas(), - 'shib' => $this->getShib(), - ]; + $array = []; + foreach ($this->authTypes as $authType) { + /** @var array $authTypeConfig */ + $authTypeConfig = $this->__get($authType); + $array[$authType] = $authTypeConfig; + } $array = array_filter($array, function(array $config) { return @@ -149,10 +183,9 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions * set usernames allowed to make usurpation * * @param array $usurpationAllowedUsernames - * - * @return ModuleOptions + * @return self */ - public function setUsurpationAllowedUsernames(array $usurpationAllowedUsernames = []) + public function setUsurpationAllowedUsernames(array $usurpationAllowedUsernames = []): self { $this->usurpationAllowedUsernames = $usurpationAllowedUsernames; @@ -164,7 +197,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions * * @return array */ - public function getUsurpationAllowedUsernames() + public function getUsurpationAllowedUsernames(): array { return $this->usurpationAllowedUsernames; } @@ -174,10 +207,9 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions * de données de l'appli * * @param bool $flag - * - * @return ModuleOptions + * @return self */ - public function setSaveLdapUserInDatabase($flag = true) + public function setSaveLdapUserInDatabase($flag = true): self { $this->saveLdapUserInDatabase = (bool)$flag; @@ -190,7 +222,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions * * @return bool */ - public function getSaveLdapUserInDatabase() + public function getSaveLdapUserInDatabase(): bool { return $this->saveLdapUserInDatabase; } @@ -198,17 +230,16 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions /** * @return string */ - public function getLdapUsername() + public function getLdapUsername(): string { return $this->ldapUsername; } /** * @param string $ldapUsername - * - * @return ModuleOptions + * @return self */ - public function setLdapUsername($ldapUsername) + public function setLdapUsername(string $ldapUsername): self { $this->ldapUsername = $ldapUsername; @@ -219,10 +250,9 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions * set cas connection params * * @param array $cas - * - * @return ModuleOptions + * @return self */ - public function setCas(array $cas = []) + public function setCas(array $cas = []): self { $this->cas = $cas; @@ -234,7 +264,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions * * @return array */ - public function getCas() + public function getCas(): array { return $this->cas; } @@ -243,10 +273,10 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions * set shibboleth connection params * * @param array $shibboleth - * @return ModuleOptions + * @return self * @deprecated Utiliser setShib() */ - public function setShibboleth(array $shibboleth = []) + public function setShibboleth(array $shibboleth = []): self { return $this->setShib($shibboleth); } @@ -257,7 +287,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions * @return array * @deprecated Utiliser getShib() */ - public function getShibboleth() + public function getShibboleth(): array { return $this->getShib(); } @@ -265,10 +295,9 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions /** * set shibboleth connection params * - * @param array $shib - * @return ModuleOptions + * @return self */ - public function setShib(array $shib = []) + public function setShib(array $shib = []): self { $this->shib = $shib; @@ -280,7 +309,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions * * @return array */ - public function getShib() + public function getShib(): array { return $this->shib; } @@ -288,20 +317,41 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions /** * @return string */ - public function getEntityManagerName() + public function getEntityManagerName(): string { return $this->entityManagerName; } /** * @param string $entityManagerName - * - * @return ModuleOptions + * @return self */ - public function setEntityManagerName($entityManagerName) + public function setEntityManagerName(string $entityManagerName): self { $this->entityManagerName = $entityManagerName; return $this; } + + /** + * Cette classe hérite de {@see \Zend\Stdlib\AbstractOptions} qui impose d'avoir un getter correspondant à chaque + * clé de config fournie. Cela empêche par ex unicaen/auth-token d'ajouter le type d'authentification 'token' + * sans ajouter un getToken() à cette classe, ce qui serait une dépendance inversée. + * + * Donc l'idée est de pouvoir récupérer un bout de config même s'il n'y a pas de getter associé. + * + * @inheritDoc + */ + public function __get($key) + { + try { + return parent::__get($key); + } catch (\BadMethodCallException $e) { + if (array_key_exists($key, $this->originalOptions)) { + return $this->originalOptions[$key]; + } else { + throw $e; + } + } + } } \ No newline at end of file diff --git a/src/UnicaenAuth/Options/ModuleOptionsFactory.php b/src/UnicaenAuth/Options/ModuleOptionsFactory.php index 5c129849350d72be0c92aa6ba0f4ee43ed77fcc6..582d8519d9b3b2d24aafa297e03e5796b67f9191 100644 --- a/src/UnicaenAuth/Options/ModuleOptionsFactory.php +++ b/src/UnicaenAuth/Options/ModuleOptionsFactory.php @@ -12,6 +12,8 @@ use UnicaenApp\Exception\RuntimeException; */ class ModuleOptionsFactory { + protected $class = ModuleOptions::class; + /** * Create service * @@ -21,18 +23,18 @@ class ModuleOptionsFactory public function __invoke(ContainerInterface $container) { $config = $container->get('Configuration'); - $moduleConfig = isset($config['unicaen-auth']) ? $config['unicaen-auth'] : []; + $moduleConfig = $config['unicaen-auth'] ?? []; $moduleConfig = array_merge($config['zfcuser'], $moduleConfig); $this->validateConfig($moduleConfig); - return new ModuleOptions($moduleConfig); + return new $this->class($moduleConfig); } /** * @param array $config */ - private function validateConfig(array $config) + protected function validateConfig(array $config) { // // Config authentification shibboleth. @@ -65,7 +67,6 @@ class ModuleOptionsFactory // // Config authentification Db. // - $ldapConfig = []; if (array_key_exists($k = 'db', $config)) { throw new RuntimeException( "La clé de config 'unicaen-auth.$k' et son contenu doivent être déplacés sous la nouvelle clé 'unicaen-auth.local'" @@ -75,13 +76,10 @@ class ModuleOptionsFactory // // Config authentification LDAP. // - $ldapConfig = []; if (array_key_exists($k = 'ldap', $config)) { throw new RuntimeException( "La clé de config 'unicaen-auth.$k' et son contenu doivent être déplacés sous la nouvelle clé 'unicaen-auth.local'" ); } - - } } \ No newline at end of file diff --git a/src/UnicaenAuth/Options/Traits/ModuleOptionsAwareTrait.php b/src/UnicaenAuth/Options/Traits/ModuleOptionsAwareTrait.php index 2d492bb48155e793603e90b1f36df81e5eb98b5f..939378632ae046e97cb62093b8afa0b8698a2cb2 100644 --- a/src/UnicaenAuth/Options/Traits/ModuleOptionsAwareTrait.php +++ b/src/UnicaenAuth/Options/Traits/ModuleOptionsAwareTrait.php @@ -2,7 +2,6 @@ namespace UnicaenAuth\Options\Traits; -use RuntimeException; use UnicaenAuth\Options\ModuleOptions; /** diff --git a/src/UnicaenAuth/Service/ShibService.php b/src/UnicaenAuth/Service/ShibService.php index f4cce5a0d66cab3f046a6e840168722d0d8bd366..ead128108108ec9f092844eef9cd819f414ffe67 100644 --- a/src/UnicaenAuth/Service/ShibService.php +++ b/src/UnicaenAuth/Service/ShibService.php @@ -319,6 +319,11 @@ EOS; */ public function activateUsurpation(ShibUser $currentShibUser, AbstractUser $utilisateurUsurpe): self { + if (! ShibUser::isEppn($utilisateurUsurpe->getUsername())) { + // cas d'usurpation d'un compte local (db) depuis une authentification shib + return $this; + } + $toShibUser = new ShibUser(); $toShibUser->setEppn($utilisateurUsurpe->getUsername()); $toShibUser->setId(uniqid()); // peut pas mieux faire pour l'instant @@ -501,7 +506,7 @@ EOS; foreach ($config as $array) { $name = $array['name']; - $value = $this->getValueFromShibData($name, $data); + $value = $this->getValueFromShibData($name, $data) ?: null; if ($value !== null) { $pregMatchPattern = $array['preg_match_pattern'] ?? null; if ($pregMatchPattern !== null) { diff --git a/src/UnicaenAuth/View/Helper/ConnectViewHelper.php b/src/UnicaenAuth/View/Helper/ConnectViewHelper.php index e26adee8854e1bdd85d3a60b25d8ad35dcf1e050..d6fefe50e6f126a407de8f58961db9601882e623 100644 --- a/src/UnicaenAuth/View/Helper/ConnectViewHelper.php +++ b/src/UnicaenAuth/View/Helper/ConnectViewHelper.php @@ -2,7 +2,6 @@ namespace UnicaenAuth\View\Helper; -use UnicaenApp\Exception\RuntimeException; use Zend\Form\Form; use Zend\View\Helper\AbstractHelper; use Zend\View\Renderer\PhpRenderer; @@ -16,52 +15,12 @@ use Zend\View\Renderer\PhpRenderer; class ConnectViewHelper extends AbstractHelper { /** - * @param string $type + * @param string $type 'local', 'shib', ldap', etc. * @param Form $form * @return AbstractConnectViewHelper */ - public function __invoke(string $type, Form $form) + public function __invoke(string $type, Form $form): AbstractConnectViewHelper { - switch ($type) { - case 'shib': - return $this->shibConnect($form); - case 'cas': - return $this->casConnect($form); -// case 'db': -// return $this->view->dbConnect($form); -// case 'ldap': -// return $this->view->ldapConnect($form); - case 'local': - return $this->localConnect($form); - } - - throw new RuntimeException("Aucune aide de vue pour le type '$type'"); - } - - /** - * @param Form $form - * @return ShibConnectViewHelper - */ - private function shibConnect(Form $form) - { - return $this->view->plugin('shibConnect')($form); - } - - /** - * @param Form $form - * @return CasConnectViewHelper - */ - private function casConnect(Form $form) - { - return $this->view->plugin('casConnect')($form); - } - - /** - * @param Form $form - * @return LocalConnectViewHelper - */ - private function localConnect(Form $form) - { - return $this->view->plugin('localConnect')($form); + return $this->view->plugin($type . 'Connect')($form); // ex: 'localConnect' } } \ No newline at end of file diff --git a/src/UnicaenAuth/View/Helper/partial/connect.phtml b/src/UnicaenAuth/View/Helper/partial/connect.phtml index 6c0a834002f37ba01227541819f8748ead2eaea7..95defbde540db74121903026be43a563abefce50 100644 --- a/src/UnicaenAuth/View/Helper/partial/connect.phtml +++ b/src/UnicaenAuth/View/Helper/partial/connect.phtml @@ -27,13 +27,13 @@ use Zend\Form\Form; <?php endif ?> <?php if ($messages = $this->flashMessenger('zfcuser-login-form')): ?> +<?php foreach ($messages as $message): ?> <div class="messenger alert alert-danger "> <button type="button" class="close" title="Fermer cette alerte" data-dismiss="alert">×</button> <span class="glyphicon glyphicon-warning-sign"></span> - <?php foreach ($messages as $message): ?> <?php echo $message ?> <br> - <?php endforeach ?> </div> +<?php endforeach ?> <?php endif ?> <?php echo $this->render('form.phtml', compact('form', 'type', 'redirect', 'passwordReset')) ?> diff --git a/view/unicaen-auth/auth/login-tabs.phtml b/view/unicaen-auth/auth/login-tabs.phtml index 0ad80fce2b0d606dbf84cb9165a454c542289e8b..b22f9a9764402ea06db97b18b86d26799abb60d1 100644 --- a/view/unicaen-auth/auth/login-tabs.phtml +++ b/view/unicaen-auth/auth/login-tabs.phtml @@ -10,14 +10,13 @@ use Zend\Form\Form; * * @var array[] $types Types d'authentification activés, ex: ['db' => ['enabled'=>true, 'type'=>'local'], 'shib' => ['enabled'=>true]] * @var string $type Type d'authentification dont il faut afficher le formulaire : ex: 'local', {@see Adapter\Shib::TYPE} - * @var Form $form Formulaire de connexion + * @var LoginForm $form Formulaire de connexion du type spécifié dans la requête * @var LoginForm[] $forms Formulaires de connexion possibles * @var string $redirect URL demandée nécessitant authentification * * @method AbstractConnectViewHelper connect() */ -/** @var Form $form */ $form->prepare(); $form->setAttributes([ 'class' => 'form-horizontal', @@ -27,7 +26,7 @@ $form->setAttributes([ /** @var AbstractConnectViewHelper[] $helpers */ $helpers = []; foreach ($types as $t => $config) { - $helpers[$t] = $this->connect($t, $form); + $helpers[$t] = $this->connect($t, $form); /** @see \UnicaenAuth\View\Helper\ConnectViewHelper */ } $activeHelper = null; @@ -40,6 +39,9 @@ $activeHelper = null; $activeHelper = $helper; $activeClass = 'active'; } else { + if (isset($forms[$key]) && $forms[$key]->isHidden()) { + continue; + } $activeClass = ''; } ?>