diff --git a/CHANGELOG.md b/CHANGELOG.md index b5fc83d94450c95194ca1e82e49790f2474d372d..04f599fb224ea7e86cbefeb89bc5636f6cc5a144 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ CHANGELOG ========= +3.2.10 +----- +- Possibilité d'activer ou non (en config) les logs des échecs d'authentification LDAP. + 3.2.8 ----- - [FIX] Données d'authentification : utilisation du SessionManager global pour avoir les durées de conservation des cookies correctes. diff --git a/Module.php b/Module.php index 41de4f3cb1bcf505a39815665a205e4450a68006..b7ba68ad35b60b8153e3315ca608b33cf9bd2231 100644 --- a/Module.php +++ b/Module.php @@ -2,20 +2,37 @@ namespace UnicaenAuth; +use UnicaenAuth\Event\Listener\LdapAuthenticationFailureLoggerListener; +use UnicaenAuth\Options\ModuleOptions; use Zend\EventManager\EventInterface; use Zend\ModuleManager\Feature\AutoloaderProviderInterface; +use Zend\ModuleManager\Feature\BootstrapListenerInterface; use Zend\ModuleManager\Feature\ConfigProviderInterface; use Zend\ModuleManager\Feature\ServiceProviderInterface; -use ZfcUser\Form\Login; -use ZfcUser\Form\LoginFilter; +use Zend\Mvc\MvcEvent; /** * Point d'entrée du module d'authentification Unicaen. * * @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr> */ -class Module implements AutoloaderProviderInterface, ConfigProviderInterface, ServiceProviderInterface +class Module implements AutoloaderProviderInterface, ConfigProviderInterface, ServiceProviderInterface, BootstrapListenerInterface { + /** + * @var \UnicaenAuth\Options\ModuleOptions + */ + private $moduleOptions; + + /** + * @var \Zend\EventManager\EventManagerInterface + */ + private $eventManager; + + /** + * @var \Zend\ServiceManager\ServiceManager + */ + private $serviceManager; + /** * @return array * @see ConfigProviderInterface @@ -41,16 +58,30 @@ class Module implements AutoloaderProviderInterface, ConfigProviderInterface, Se } /** - * This method is called once the MVC bootstrapping is complete, - * after the "loadModule.post" event, once $application->bootstrap() is called. - * - * @param EventInterface $e - * - * @see BootstrapListenerInterface + * @inheritDoc */ public function onBootstrap(EventInterface $e) { + if ($e instanceof MvcEvent) { + /** @var \Zend\Mvc\Application $application */ + $application = $e->getApplication(); + $this->serviceManager = $application->getServiceManager(); + $this->eventManager = $application->getEventManager(); + $this->moduleOptions = $this->serviceManager->get(ModuleOptions::class); + $this->attachEventListeners(); + } + } + + protected function attachEventListeners() + { + // log éventuel des erreurs d'authentification LDAP + $logLdapAuthenticationFailure = $this->moduleOptions->getLdap()['log_failures'] ?? false; + if ($logLdapAuthenticationFailure) { + /** @var LdapAuthenticationFailureLoggerListener $listener */ + $listener = $this->serviceManager->get(LdapAuthenticationFailureLoggerListener::class); + $listener->attach($this->eventManager); + } } /** diff --git a/config/module.config.php b/config/module.config.php index 984369974ea4208a2687ff0cc6787ec8b6ae88ea..34d7bcfe3024e99555961b6f917df32c6b460076 100644 --- a/config/module.config.php +++ b/config/module.config.php @@ -24,6 +24,8 @@ use UnicaenAuth\Controller\AuthController; use UnicaenAuth\Controller\AuthControllerFactory; use UnicaenAuth\Controller\DroitsControllerFactory; use UnicaenAuth\Controller\UtilisateurControllerFactory; +use UnicaenAuth\Event\Listener\LdapAuthenticationFailureLoggerListener; +use UnicaenAuth\Event\Listener\LdapAuthenticationFailureLoggerListenerFactory; use UnicaenAuth\Form\CasLoginForm; use UnicaenAuth\Form\CasLoginFormFactory; use UnicaenAuth\Form\Droits\RoleFormFactory; @@ -155,6 +157,8 @@ $settings = [ 'enabled' => true, 'adapter' => Ldap::class, 'form' => LoginForm::class, + + 'log_failures' => false, /** @see \UnicaenAuth\Event\Listener\LdapAuthenticationFailureLoggerListener */ ], ], @@ -701,7 +705,9 @@ return [ 'UnicaenApp\HistoriqueListener' => HistoriqueListenerFactory::class, 'UnicaenAuth\HistoriqueListener' => HistoriqueListenerFactory::class, - \UnicaenAuth\Event\EventManager::class => \UnicaenAuth\Event\EventManagerFactory::class + \UnicaenAuth\Event\EventManager::class => \UnicaenAuth\Event\EventManagerFactory::class, + + LdapAuthenticationFailureLoggerListener::class => LdapAuthenticationFailureLoggerListenerFactory::class, ], 'lazy_services' => [ // Mapping services to their class names is required since the ServiceManager is not a declarative DIC. diff --git a/config/unicaen-auth.local.php.dist b/config/unicaen-auth.local.php.dist index 1dba9f252f588e72d2422bd284d7546f68d330a3..f6ade16da81c630a7b90e696ea5284aed05bf1bb 100644 --- a/config/unicaen-auth.local.php.dist +++ b/config/unicaen-auth.local.php.dist @@ -57,6 +57,11 @@ return [ */ 'ldap' => [ 'enabled' => true, + + /** + * Activation ou non des logs (via `error_log` par défaut) à propos des échecs d'authentification LDAP. + */ + 'log_failures' => false, /** @see \UnicaenAuth\Event\Listener\LdapAuthenticationFailureLoggerListener */ ], ], diff --git a/src/UnicaenAuth/Authentication/Adapter/Ldap.php b/src/UnicaenAuth/Authentication/Adapter/Ldap.php index a3edc9af9337266e775e71299f24bb4ada886ec9..3858f8d994a667d55d64e991b42ed03b51bac922 100644 --- a/src/UnicaenAuth/Authentication/Adapter/Ldap.php +++ b/src/UnicaenAuth/Authentication/Adapter/Ldap.php @@ -206,24 +206,22 @@ class Ldap extends AbstractAdapter implements EventManagerAwareInterface // LDAP auth $result = $this->getLdapAuthAdapter()->setUsername($username)->setPassword($credential)->authenticate(); + $success = $result->isValid(); + + // Déclenchement d'un événement contenant le nécessaire pour réagir à l'échec d'authentification (log, etc.) + if (!$success) { + $errorEvent = new Event(self::LDAP_AUTHENTIFICATION_FAIL, $this, [ + 'result' => $result, + 'username' => $username, - // Envoi des erreurs LDAP dans un événement - if (!$result->isValid()) { - $messages = "LDAP ERROR : "; - $errorMessages = $result->getMessages(); - if (count($errorMessages) > 0) { + // Clé conservée pour compatibilité : + 'messages' => implode(PHP_EOL, array_slice($result->getMessages(), 0, 2)), // On ne prend que les 2 premières lignes d'erreur (les suivantes contiennent souvent - // les mots de passe de l'utilisateur, et les mot de passe dans les logs... bof bof). - for ($i = 0; $i < 2 && count($errorMessages) >= $i; $i++) { - $messages .= $errorMessages[$i] . " "; - } - } - $errorEvent = new Event(self::LDAP_AUTHENTIFICATION_FAIL, null, ['messages' => $messages]); - $this->getEventManager()->triggerEvent($errorEvent); + // les mots de passe de l'utilisateur, et les mots de passe dans les logs... bof bof). + ]); + $this->eventManager->triggerEvent($errorEvent); } - $success = $result->isValid(); - // verif existence du login usurpé if ($this->usernameUsurpe) { // s'il nexiste pas, échec de l'authentification diff --git a/src/UnicaenAuth/Event/Listener/LdapAuthenticationFailureLoggerListener.php b/src/UnicaenAuth/Event/Listener/LdapAuthenticationFailureLoggerListener.php new file mode 100644 index 0000000000000000000000000000000000000000..3d8a10aab3737c46e361f8d4abb6925fa3a47530 --- /dev/null +++ b/src/UnicaenAuth/Event/Listener/LdapAuthenticationFailureLoggerListener.php @@ -0,0 +1,77 @@ +<?php + +namespace UnicaenAuth\Event\Listener; + +use Psr\Log\LoggerInterface; +use UnicaenAuth\Authentication\Adapter\Ldap; +use Zend\Authentication\Result; +use Zend\EventManager\Event; +use Zend\EventManager\EventManagerInterface; +use Zend\EventManager\ListenerAggregateInterface; +use Zend\EventManager\ListenerAggregateTrait; + +/** + * Permet de scruter les erreurs d'authentification LDAP pour les ajouter aux logs. + * + * @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr> + */ +class LdapAuthenticationFailureLoggerListener implements ListenerAggregateInterface +{ + use ListenerAggregateTrait; + + /** + * @var \Psr\Log\LoggerInterface + */ + private $logger; + + public function setLogger(LoggerInterface $logger): self + { + $this->logger = $logger; + return $this; + } + + public function attach(EventManagerInterface $events, $priority = 1) + { + $this->listeners[] = $events->getSharedManager()->attach( + "*", + Ldap::LDAP_AUTHENTIFICATION_FAIL, + [$this, "onLdapError"], + 100 + ); + } + + public function onLdapError(Event $event) + { + /** @var \Zend\Authentication\Result $result */ + $result = $event->getParam('result'); + $details = $result->getMessages() ?: ["Aucun détail supplémentaire."]; + $username = $event->getParam('username'); + + $messages = []; + switch ($result->getCode()) { + case Result::FAILURE_IDENTITY_NOT_FOUND: + $messages[] = "Identifiant de connexion inconnu : '$username'."; + break; + case Result::FAILURE_CREDENTIAL_INVALID: + $messages[] = "Mot de passe incorrect pour l'identifiant de connexion '$username'."; + break; + case Result::FAILURE: + default: + $messages[] = "Erreur rencontrée lors de l'authentification : "; + break; + } + $messages = array_merge($messages, $details); + $error = "[SyGAL LDAP AUTH] " . implode(PHP_EOL, $messages); + + $this->log($error); + } + + protected function log(string $message) + { + if ($this->logger !== null) { + $this->logger->error($message); + } else { + error_log($message); + } + } +} \ No newline at end of file diff --git a/src/UnicaenAuth/Event/Listener/LdapAuthenticationFailureLoggerListenerFactory.php b/src/UnicaenAuth/Event/Listener/LdapAuthenticationFailureLoggerListenerFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..42e9b1f5941b863f73064692e5c1a0d0f723a73c --- /dev/null +++ b/src/UnicaenAuth/Event/Listener/LdapAuthenticationFailureLoggerListenerFactory.php @@ -0,0 +1,13 @@ +<?php + +namespace UnicaenAuth\Event\Listener; + +use Interop\Container\ContainerInterface; + +class LdapAuthenticationFailureLoggerListenerFactory +{ + public function __invoke(ContainerInterface $container): LdapAuthenticationFailureLoggerListener + { + return new LdapAuthenticationFailureLoggerListener(); + } +} \ No newline at end of file