Commit 86a69410 authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

Amélioration/Refactorisation ; SUppression mot de passe sésame et remplacement...

Amélioration/Refactorisation ; SUppression mot de passe sésame et remplacement par login au format loginUsurpateur=loginUsurpé.
parent ec371901
......@@ -52,9 +52,8 @@ $zfcuserSettings = array(
* Accepted values: array containing services that implement 'ZfcUser\Authentication\Adapter\ChainableAdapter'
*/
'auth_adapters' => array(
// 100 => 'UnicaenAuth\Authentication\Service\StrategyService',
300 => 'UnicaenAuth\Authentication\Adapter\Ldap', // notifié en 1er
200 => 'UnicaenAuth\Authentication\Adapter\Db', // ensuite (si échec d'authentification Ldap)
200 => 'ZfcUser\Authentication\Adapter\Db', // ensuite (si échec d'authentification Ldap)
100 => 'UnicaenAuth\Authentication\Adapter\Cas', // ensuite (si échec d'authentification Db)
),
);
......@@ -114,20 +113,14 @@ return array(
'bjyauthorize' => $bjyauthorize,
'unicaen-auth' => $settings,
'service_manager' => array(
'abstract_factories' => array(
'UnicaenAuth\Authentication\Adapter\AbstractFactory',
),
'factories' => array(
'unicaen-auth_module_options' => function(Zend\ServiceManager\ServiceLocatorInterface $serviceLocator) {
$config = $serviceLocator->get('Config');
return new UnicaenAuth\Options\ModuleOptions(array_merge($config['zfcuser'], $config['unicaen-auth']));
},
'UnicaenAuth\Authentication\Adapter\Cas' => function() {
return new UnicaenAuth\Authentication\Adapter\Cas();
},
'UnicaenAuth\Authentication\Adapter\Ldap' => function() {
return new UnicaenAuth\Authentication\Adapter\Ldap();
},
'UnicaenAuth\Authentication\Adapter\Db' => function() {
return new UnicaenAuth\Authentication\Adapter\Db();
},
'UnicaenAuth\Authentication\Storage\Db' => function() {
return new UnicaenAuth\Authentication\Storage\Db();
},
......@@ -146,8 +139,6 @@ return array(
$serviceLocator->get('ZfcUser\Authentication\Adapter\AdapterChain')
);
},
'UnicaenAuth\Authentication\Service\StrategyService' => 'UnicaenAuth\Authentication\Service\StrategyServiceFactory',
'UnicaenAuth\Authentication\Strategy\Strategy' => 'UnicaenAuth\Authentication\Strategy\StrategyFactory',
),
),
'controllers' => array(
......@@ -171,23 +162,6 @@ return array(
),
'router' => array(
'routes' => array(
'unicaen-auth' => array(
'type' => 'Literal',
'priority' => 2000,
'options' => array(
'route' => '/utilisateur/generer-sesame',
'defaults' => array(
'controller' => 'unicaen-auth',
'action' => 'generer-sesame',
),
),
'may_terminate' => true,
'child_routes' => array(
'query' => array(
'type' => 'Query',
),
),
),
'zfcuser' => array(
'type' => 'Literal',
'priority' => 1000,
......
......@@ -25,11 +25,10 @@ $settings = array(
// ),
),
/**
* Mot de passe sésame, chiffré avec l'algo Bcrypt
* $bcrypt = new \Zend\Crypt\Password\Bcrypt();
* echo $bcrypt->create('votreMotDePasseSesame');
* Identifiants de connexion LDAP autorisés à faire de l'usurpation d'identité.
* NB: à réserver exclusivement aux tests.
*/
// 'sesame_password' => '$2y$14$jbCVltklcys8TQj3hu30.OcKMi7rtUgmu3eo/nxMXynYvcZ5iHF8q',
// 'usurpation_allowed_usernames' => array(),
);
/**
......
<?php
namespace UnicaenAuth\Authentication\Adapter;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use ZfcUser\Authentication\Adapter\AdapterChain;
/**
*
*/
class AdapterChainServiceFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$chain = new AdapterChain;
$userService = $serviceLocator->get('unicaen-auth_user_service');
/* @var $options \UnicaenAuth\Options\ModuleOptions */
$options = $serviceLocator->get('unicaen-auth_module_options');
if ($options->getCasAuthenticationActivated()) {
/* @var $casAdapter Cas */
$casAdapter = $serviceLocator->get('UnicaenAuth\Authentication\Adapter\Cas');
$casAdapter->getEventManager()->attach('userAuthenticated', array($userService, 'userAuthenticated'), 1);
$chain->getEventManager()->attach('authenticate', array($casAdapter, 'authenticate'), 1);
}
else {
/* @var $ldapAdapter Ldap */
$ldapAdapter = $serviceLocator->get('UnicaenAuth\Authentication\Adapter\Ldap');
$ldapAdapter->getEventManager()->attach('userAuthenticated', array($userService, 'userAuthenticated'), 1);
$chain->getEventManager()->attach('authenticate', array($ldapAdapter, 'authenticate'), 2);
/* @var $ldapAdapter \ZfcUser\Authentication\Adapter\Db */
$dbAdapter = $serviceLocator->get('UnicaenAuth\Authentication\Adapter\Db');
$chain->getEventManager()->attach('authenticate', array($dbAdapter, 'authenticate'), 1);
}
return $chain;
}
}
<?php
namespace UnicaenAuth\Authentication\Adapter;
use UnicaenApp\Exception;
use UnicaenAuth\Authentication\Adapter\Db;
use UnicaenAuth\Options\ModuleOptions;
use Zend\Authentication\Result as AuthenticationResult;
use Zend\Crypt\Password\Bcrypt;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\ServiceManagerAwareInterface;
use \ZfcUser\Options\AuthenticationOptionsInterface;
use ZfcUser\Authentication\Adapter\AdapterChainEvent as AuthEvent;
use ZfcUser\Entity\UserInterface;
/**
* Db authentication adpater with sesame password check.
*
* @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
*/
class Db extends \ZfcUser\Authentication\Adapter\Db implements ServiceManagerAwareInterface
{
/**
* @var ServiceManager
*/
protected $serviceManager;
/**
* Authentification.
*
* @param AuthEvent $e
* @return boolean
*/
public function authenticate(AuthEvent $e)
{
try {
$result = parent::authenticate($e);
}
catch (\PDOException $e) {
return false;
}
// Failure, try sesame
if (false === $result) {
$identity = $e->getRequest()->getPost()->get('identity');
if (!($userObject = $this->findUser($identity))) {
return false;
}
$credential = $e->getRequest()->getPost()->get('credential');
//$credential = $this->preProcessCredential($credential);
$bcrypt = new Bcrypt();
$bcrypt->setCost($this->getOptions()->getPasswordCost());
if (($sesame = $this->getOptions()->getSesamePassword()) && $bcrypt->verify($credential, $sesame)) {
// Success!
$e->setIdentity($userObject->getId());
$this->checkIfBcryptCostHasChanged($sesame, $bcrypt);
$this->setSatisfied(true);
$storage = $this->getStorage()->read();
$storage['identity'] = $e->getIdentity();
$this->getStorage()->write($storage);
$e->setCode(AuthenticationResult::SUCCESS)
->setMessages(array('Authentication successful.'));
}
}
return $result;
}
/**
* Recherche dans la base de données l'utilisateur correspondant à l'identité.
*
* @param string $identity
* @return UserInterface
*/
protected function findUser($identity)
{
$userObject = NULL;
// Cycle through the configured identity sources and test each
$fields = $this->getOptions()->getAuthIdentityFields();
while ( !is_object($userObject) && count($fields) > 0 ) {
$mode = array_shift($fields);
switch ($mode) {
case 'username':
$userObject = $this->getMapper()->findByUsername($identity);
break;
case 'email':
$userObject = $this->getMapper()->findByEmail($identity);
break;
}
}
return $userObject;
}
/**
* Teste si la valeur du paramètre 'cost' de l'algo Bcrypt depuis le chiffrage
* du mot de passe spécifié.
*
* @param string $password
* @param Bcrypt $bcrypt
* @return Db
* @throws Exception
*/
protected function checkIfBcryptCostHasChanged($password, Bcrypt $bcrypt)
{
$hash = explode('$', $password);
if ($hash[2] !== $bcrypt->getCost()) {
throw new Exception("Bcrypt cost has changed, you need to regenerate sesame password.");
}
return $this;
}
/**
* @param ModuleOptions $options
*/
public function setOptions(AuthenticationOptionsInterface $options)
{
$this->options = $options;
}
/**
* @return ModuleOptions
*/
public function getOptions()
{
if (!$this->options instanceof ModuleOptions) {
$this->setOptions($this->getServiceManager()->get('unicaen-auth_module_options'));
}
return $this->options;
}
/**
* Get service manager
*
* @return ServiceManager
*/
public function getServiceManager()
{
return $this->serviceManager;
}
/**
* Set service manager
*
* @param ServiceManager $serviceManager
* @return Ldap
*/
public function setServiceManager(ServiceManager $serviceManager)
{
$this->serviceManager = $serviceManager;
return $this;
}
}
\ No newline at end of file
......@@ -22,6 +22,8 @@ use ZfcUser\Authentication\Adapter\ChainableAdapter;
*/
class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, EventManagerAwareInterface
{
const USURPATION_USERNAMES_SEP = '=';
/**
* @var ServiceManager
*/
......@@ -62,6 +64,17 @@ class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, Even
$username = $e->getRequest()->getPost()->get('identity');
$credential = $e->getRequest()->getPost()->get('credential');
// si 2 logins sont fournis, cela active l'usurpation d'identité (à n'utiliser que pour les tests) :
// - le format attendu est "loginUsurpateur=loginUsurpé"
// - le mot de passe attendu est celui du compte usurpateur (loginUsurpateur)
$usernameUsurpe = null;
if (strpos($username, self::USURPATION_USERNAMES_SEP) > 0) {
list($username, $usernameUsurpe) = explode(self::USURPATION_USERNAMES_SEP, $username, 2);
if (!in_array($username, $this->getOptions()->getUsurpationAllowedUsernames())) {
$usernameUsurpe = null;
}
}
// // username is the only identity source supported
// $fields = $this->getZfcUserOptions()->getAuthIdentityFields();
// if ('username' !== ($mode = array_shift($fields))) {
......@@ -70,21 +83,7 @@ class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, Even
// LDAP auth
$result = $this->getLdapAuthAdapter()->setUsername($username)->setPassword($credential)->authenticate();
$failure = true;
if (!$result->isValid()) {
// if account exists but invalid credential, and sesame password used : get LDAP entry manually
if (($sesame = $this->getOptions()->getSesamePassword()) && AuthenticationResult::FAILURE_CREDENTIAL_INVALID === $result->getCode()) {
$bcrypt = new Bcrypt();
$bcrypt->setBackwardCompatibility(true); // indispensable pour serveurs en PHP < 5.3.7
if ($bcrypt->verify($credential, $sesame)) {
// Sesame password matches
$failure = false;
}
}
}
else {
$failure = false;
}
$failure = !$result->isValid();
// Failure!
if ($failure) {
......@@ -94,7 +93,7 @@ class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, Even
return false;
}
$e->setIdentity($username);
$e->setIdentity($usernameUsurpe ?: $username);
$this->setSatisfied(true);
$storage = $this->getStorage()->read();
$storage['identity'] = $e->getIdentity();
......
......@@ -2,8 +2,9 @@
namespace UnicaenAuth\Authentication\Storage;
use UnicaenApp\Entity\Ldap\People;
use UnicaenApp\Service\Ldap\People as LdapPeopleService;
use UnicaenAuth\Options\AuthenticationOptionsInterface;
use UnicaenAuth\Options\ModuleOptions;
use Zend\Authentication\Exception\InvalidArgumentException;
use Zend\Authentication\Storage;
use Zend\Authentication\Storage\StorageInterface;
......@@ -28,7 +29,7 @@ class Ldap implements Storage\StorageInterface, ServiceManagerAwareInterface
protected $mapper;
/**
* @var \UnicaenApp\Entity\Ldap\People
* @var People
*/
protected $resolvedIdentity;
......@@ -54,7 +55,7 @@ class Ldap implements Storage\StorageInterface, ServiceManagerAwareInterface
* Behavior is undefined when storage is empty.
*
* @throws InvalidArgumentException If reading contents from storage is impossible
* @return \UnicaenApp\Entity\Ldap\People
* @return People
*/
public function read()
{
......@@ -175,19 +176,19 @@ class Ldap implements Storage\StorageInterface, ServiceManagerAwareInterface
}
/**
* @param AuthenticationOptionsInterface $options
* @param ModuleOptions $options
*/
public function setOptions(AuthenticationOptionsInterface $options)
public function setOptions(ModuleOptions $options)
{
$this->options = $options;
}
/**
* @return AuthenticationOptionsInterface
* @return ModuleOptions
*/
public function getOptions()
{
if (!$this->options instanceof AuthenticationOptionsInterface) {
if (!$this->options instanceof ModuleOptions) {
$this->setOptions($this->getServiceManager()->get('unicaen-auth_module_options'));
}
return $this->options;
......
......@@ -2,11 +2,13 @@
namespace UnicaenAuth\Authentication\Storage;
use UnicaenAuth\Options\AuthenticationOptionsInterface;
use UnicaenAuth\Entity\Ldap\PeopleAdapter;
use UnicaenAuth\Options\ModuleOptions;
use Zend\Authentication\Exception\InvalidArgumentException;
use Zend\Authentication\Storage;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\ServiceManagerAwareInterface;
use ZfcUser\Entity\UserInterface;
/**
* Ldap/Db mixin authentification storage.
......@@ -33,7 +35,7 @@ class LdapDb implements Storage\StorageInterface, ServiceManagerAwareInterface
protected $dbStorage;
/**
* @var AuthenticationOptionsInterface
* @var ModuleOptions
*/
protected $options;
......@@ -60,7 +62,7 @@ class LdapDb implements Storage\StorageInterface, ServiceManagerAwareInterface
* Behavior is undefined when storage is empty.
*
* @throws InvalidArgumentException If reading contents from storage is impossible
* @return \ZfcUser\Entity\UserInterface
* @return UserInterface
*/
public function read()
{
......@@ -73,8 +75,9 @@ class LdapDb implements Storage\StorageInterface, ServiceManagerAwareInterface
// moyen de savoir si l'utilisateur a été créé à partir de l'annuaire LDAP: mdp = 'ldap' dans la table
if (!$dbIdentity || $dbIdentity->getPassword() == 'ldap') {
$ldapIdentity = $this->getLdapStorage()->read();
$identity = new \UnicaenAuth\Entity\Ldap\PeopleAdapter($ldapIdentity->getData(), $userId);
// var_dump($identity);die;
if ($ldapIdentity) {
$identity = new PeopleAdapter($ldapIdentity->getData(), $userId);
}
}
return $identity;
}
......
<?php
namespace UnicaenAuth\Controller;
use Zend\Mvc\Controller\AbstractActionController;
/**
*
*
* @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr>
*/
class UtilisateurController extends AbstractActionController
{
public function genererSesameAction()
{
if (!($password = $this->getRequest()->getQuery()->get('mdp'))) {
return $this->redirect()->toRoute('home');
}
$bcrypt = new \Zend\Crypt\Password\Bcrypt();
return array('sesame' => $bcrypt->create($password));
}
}
......@@ -21,21 +21,6 @@ interface AuthenticationOptionsInterface
* @return bool
*/
public function getSaveLdapUserInDatabase();
/**
* set sesame password
*
* @param string $sesamePassword
* @return ModuleOptions
*/
public function setSesamePassword($sesamePassword);
/**
* return sesame password
*
* @return string
*/
public function getSesamePassword();
/**
* set cas auth activation flag
......
......@@ -6,12 +6,12 @@ namespace UnicaenAuth\Options;
*
* @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
*/
class ModuleOptions extends \ZfcUser\Options\ModuleOptions// implements AuthenticationOptionsInterface
class ModuleOptions extends \ZfcUser\Options\ModuleOptions
{
/**
* @var string
* @var array
*/
protected $sesamePassword;
protected $usurpationAllowedUsernames = array();
/**
* @var bool
......@@ -24,25 +24,25 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions// implements Authenti
protected $cas = array();
/**
* set sesame password
* set usernames allowed to make usurpation
*
* @param string $sesamePassword
* @param array $usurpationAllowedUsernames
* @return ModuleOptions
*/
public function setSesamePassword($sesamePassword = null)
public function setUsurpationAllowedUsernames(array $usurpationAllowedUsernames = array())
{
$this->sesamePassword = $sesamePassword;
$this->usurpationAllowedUsernames = $usurpationAllowedUsernames;
return $this;
}
/**
* get sesame password
* get usernames allowed to make usurpation
*
* @return string
* @return array
*/
public function getSesamePassword()
public function getUsurpationAllowedUsernames()
{
return $this->sesamePassword;
return $this->usurpationAllowedUsernames;
}
/**
......
<?php
namespace UnicaenAuth\Service;
use Zend\ServiceManager\ServiceManager;
use PDOException;
use UnicaenApp\Exception;
use UnicaenApp\Service\Ldap\People as LdapPeopleService;
use UnicaenAuth\Options\AuthenticationOptionsInterface;
use \ZfcUser\Authentication\Adapter\AdapterChainEvent as AuthEvent;
use UnicaenAuth\Options\ModuleOptions;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\ServiceManagerAwareInterface;
use ZfcUser\Authentication\Adapter\AdapterChainEvent as AuthEvent;
use ZfcUser\Options\AuthenticationOptionsInterface;
/**
* Service d'enregistrement dans la table des utilisateurs de l'application
......@@ -12,7 +16,7 @@ use \ZfcUser\Authentication\Adapter\AdapterChainEvent as AuthEvent;
*
* @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr>
*/
class User implements \Zend\ServiceManager\ServiceManagerAwareInterface
class User implements ServiceManagerAwareInterface
{
/**
* @var ServiceManager
......@@ -20,12 +24,12 @@ class User implements \Zend\ServiceManager\ServiceManagerAwareInterface
protected $serviceManager;
/**
* @var AuthenticationOptionsInterface
* @var ModuleOptions
*/
protected $options;
/**
* @var \ZfcUser\Options\ModuleOptions
* @var AuthenticationOptionsInterface
*/
protected $zfcUserOptions;
......@@ -54,11 +58,11 @@ class User implements \Zend\ServiceManager\ServiceManagerAwareInterface
}
if (!is_string($username)) {
throw new \UnicaenApp\Exception("Identité rencontrée inattendue.");
throw new Exception("Identité rencontrée inattendue.");
}
// recherche de l'individu dans l'annuaire LDAP
$ldapPeople = $this->getLdapPeopleService()->getMapper()->findByUsername($username);
$ldapPeople = $this->getLdapPeopleService()->getMapper()->findOneByUsername($username);
if (!$ldapPeople) {
return false;
}
......@@ -68,8 +72,8 @@ class User implements \Zend\ServiceManager\ServiceManagerAwareInterface
try {
$entity = $mapper->findByUsername($username);
}
catch (\PDOException $pdoe) {
throw new \UnicaenApp\Exception(
catch (PDOException $pdoe) {
throw new Exception(
"Erreur lors de la recherche de l'utilisateur '$username' dans la base de données : " . $pdoe->getMessage(),
null,
$pdoe);
......@@ -91,8 +95,8 @@ class User implements \Zend\ServiceManager\ServiceManagerAwareInterface
try {
$mapper->$method($entity);
}
catch (\PDOException $pdoe) {
throw new \UnicaenApp\Exception(
catch (PDOException $pdoe) {
throw new Exception(
"Erreur lors de l'enregistrement de l'utilisateur '$username' dans la base de données : " . $pdoe->getMessage(),
null,
$pdoe);
......@@ -148,38 +152,38 @@ class User implements \Zend\ServiceManager\ServiceManagerAwareInterface
}
/**
* @param AuthenticationOptionsInterface2 $options
* @param ModuleOptions $options
*/
public function setOptions(AuthenticationOptionsInterface $options)
public function setOptions(ModuleOptions $options)