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

Dernièrs aménagements pour l'auth Shibboleth

parent a4b4a4f7
Loading
Loading
Loading
Loading
Loading
+6 −9
Changes for config/autoload/unicaen-auth.global.php: 6 added lines, 9 removed lines.
Original line number Diff line number Diff line
<?php
/**
 * UnicaenAuth Global Configuration
 *
 * If you have a ./config/autoload/ directory set up for your project, you can
 * drop this config file in it and change the values as you wish.
 */

use Application\Authentication\Storage\AppStorage;
@@ -67,12 +64,12 @@ $config = [
             * NB: si la connexion à la base échoue, ce n'est pas bloquant!
             */
            'UnicaenAuth\Provider\Role\DbRole'   => [],
            /**
             * Fournit le rôle correspondant à l'identifiant de connexion de l'utilisateur.
             * Cela est utile lorsque l'on veut gérer les habilitations d'un utilisateur unique
             * sur des ressources.
             */
            'UnicaenAuth\Provider\Role\Username' => [],
//            /**
//             * Fournit le rôle correspondant à l'identifiant de connexion de l'utilisateur.
//             * Cela est utile lorsque l'on veut gérer les habilitations d'un utilisateur unique
//             * sur des ressources.
//             */
//            'UnicaenAuth\Provider\Role\Username' => [],
        ],
    ],
    'zfcuser'      => [
+1 −5
Changes for module/Application/config/module.config.php: 1 added line, 5 removed lines.
Original line number Diff line number Diff line
<?php

use Application\Authentication\Adapter\AbstractFactory;
use Application\Cache\MemcachedFactory;
use Application\Entity\Db\Repository\DefaultEntityRepository;
use Application\Event\UserAuthenticatedEventListenerFactory;
@@ -15,6 +14,7 @@ use Application\Service\Role\RoleService;
use Application\Service\ServiceAwareInitializer;
use Application\Service\UserContextServiceAwareInitializer;
use Application\View\Helper\EscapeTextHelper;
use Application\View\Helper\QueryParamsHelperFactory;
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain;
use Doctrine\ORM\Mapping\Driver\XmlDriver;
use UnicaenApp\Service\EntityManagerAwareInitializer;
@@ -174,9 +174,6 @@ return array(
            'NotificationService'            => NotificationServiceFactory::class,
            'Sygal\Memcached'                => MemcachedFactory::class,
        ),
        'abstract_factories' => [
            AbstractFactory::class,
        ],
        'initializers' => [
            ServiceAwareInitializer::class,
            AuthorizeServiceAwareInitializer::class,
@@ -221,7 +218,6 @@ return array(
    ),
    'view_helpers' => array(
        'invokables' => array(
            'queryParams' => 'Application\View\Helper\QueryParams',
            'sortable'    => 'Application\View\Helper\Sortable',
            'Uploader'    => 'Application\View\Helper\Uploader\UploaderHelper',
            'filterPanel' => 'Application\View\Helper\FilterPanel\FilterPanelHelper',
+0 −64
Changes for module/Application/src/Application/Authentication/Adapter/AbstractFactory.php: 0 added lines, 64 removed lines.
Original line number Diff line number Diff line
<?php

namespace Application\Authentication\Adapter;

use UnicaenApp\Exception;
use Zend\EventManager\EventManager;
use Zend\EventManager\EventManagerAwareInterface;
use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

/**
 * Description of AbstractFactory
 *
 * @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr>
 */
class AbstractFactory implements AbstractFactoryInterface
{
    /**
     * Determine if we can create a service with name
     *
     * @param ServiceLocatorInterface $serviceLocator
     * @param $name
     * @param $requestedName
     * @return bool
     */
    public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
    {
        return strpos($requestedName, __NAMESPACE__) === 0 && class_exists($requestedName);
    }

    /**
     * Create service with name
     *
     * @param ServiceLocatorInterface $serviceLocator
     * @param $name
     * @param $requestedName
     * @return mixed
     */
    public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
    {
        switch ($requestedName) {
            case __NAMESPACE__ . '\Shib':
                $adapter = new Shib();
                break;
            case __NAMESPACE__ . '\Ldap':
                $adapter = new Ldap();
                break;
            default:
                throw new Exception\RuntimeException("Service demandé inattendu : '$requestedName'!");
                break;
        }

//        if ($adapter instanceof EventManagerAwareInterface) {
//            /** @var EventManager $eventManager */
//            $eventManager = $serviceLocator->get('event_manager');
//            $adapter->setEventManager($eventManager);
//            /* @var $userService \UnicaenAuth\Service\User */
//            $userService = $serviceLocator->get('unicaen-auth_user_service');
//            $eventManager->attach('userAuthenticated', [$userService, 'userAuthenticated'], 100);
//        }

        return $adapter;
    }
}
 No newline at end of file
+0 −314
Changes for module/Application/src/Application/Authentication/Adapter/Ldap.php: 0 added lines, 314 removed lines.
Original line number Diff line number Diff line
<?php

namespace Application\Authentication\Adapter;

use UnicaenApp\Exception\RuntimeException;
use UnicaenAuth\Options\ModuleOptions;
use Zend\Authentication\Exception\UnexpectedValueException;
use Zend\Authentication\Result as AuthenticationResult;
use Zend\Authentication\Adapter\Ldap as LdapAuthAdapter;
use Zend\EventManager\EventManager;
use Zend\EventManager\EventManagerAwareInterface;
use Zend\EventManager\EventManagerInterface;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\ServiceManagerAwareInterface;
use ZfcUser\Authentication\Adapter\AbstractAdapter;
use ZfcUser\Authentication\Adapter\AdapterChainEvent as AuthEvent;
use ZfcUser\Authentication\Adapter\ChainableAdapter;
use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
use Zend\Authentication\Exception\ExceptionInterface;

/**
 * LDAP authentication adpater
 *
 * @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
 */
class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, EventManagerAwareInterface
{
    const USURPATION_USERNAMES_SEP = '=';

    /**
     * @var ServiceManager
     */
    protected $serviceManager;

    /**
     * @var EventManager
     */
    protected $eventManager;

    /**
     * @var LdapAuthAdapter
     */
    protected $ldapAuthAdapter;

    /**
     * @var LdapPeopleMapper
     */
    protected $ldapPeopleMapper;

    /**
     * @var ModuleOptions
     */
    protected $options;

    /**
     * @var string
     */
    protected $usernameUsurpe;

    /**
     *
     * @param AuthEvent $e
     * @return boolean
     * @throws UnexpectedValueException
     * @see ChainableAdapter
     */
    public function authenticate(AuthEvent $e)
    {
        if ($this->isSatisfied()) {
            try {
                $storage = $this->getStorage()->read();
            } catch (ExceptionInterface $e) {
                throw new RuntimeException("Erreur de lecture du storage");
            }
            $e->setIdentity($storage['identity'])
                    ->setCode(AuthenticationResult::SUCCESS)
                    ->setMessages(['Authentication successful.']);
            return;
        }

        $username   = $e->getRequest()->getPost()->get('identity');
        $credential = $e->getRequest()->getPost()->get('credential');

        $success = $this->authenticateUsername($username, $credential);

        // Failure!
        if (! $success) {
            $e->setCode(AuthenticationResult::FAILURE)
              ->setMessages(['LDAP bind failed.']);
            $this->setSatisfied(false);
            return false;
        }

        // recherche de l'individu dans l'annuaire LDAP
        $ldapPeople = $this->getLdapPeopleMapper()->findOneByUsername($username);
        if (!$ldapPeople) {
            $e
                ->setCode(AuthenticationResult::FAILURE)
                ->setMessages(['Authentication failed.']);
            $this->setSatisfied(false);
            return false;
        }

        $e->setIdentity($this->usernameUsurpe ?: $username);
        $this->setSatisfied(true);
        try {
            $storage = $this->getStorage()->read();
            $storage['identity'] = $e->getIdentity();
            $this->getStorage()->write($storage);
        } catch (ExceptionInterface $e) {
            throw new RuntimeException("Erreur de concernant le storage");
        }
        $e->setCode(AuthenticationResult::SUCCESS)
          ->setMessages(['Authentication successful.']);

        /* @var $userService \Application\Service\User */
        $userService = $this->getServiceManager()->get('unicaen-auth_user_service');
        $userService->userAuthenticated($ldapPeople);
    }

    /**
     * Extrait le loginUsurpateur et le loginUsurpé si l'identifiant spécifé est de la forme
     * "loginUsurpateur=loginUsurpé".
     *
     * @param string $identifiant Identifiant, éventuellement de la forme "loginUsurpateur=loginUsurpé"
     * @return array
     * [loginUsurpateur, loginUsurpé] si l'identifiant est de la forme "loginUsurpateur=loginUsurpé" ;
     * [] sinon.
     */
    static public function extractUsernamesUsurpation($identifiant)
    {
        if (strpos($identifiant, self::USURPATION_USERNAMES_SEP) > 0) {
            list($identifiant, $usernameUsurpe) = explode(self::USURPATION_USERNAMES_SEP, $identifiant, 2);

            return [
                $identifiant,
                $usernameUsurpe
            ];
        }

        return [];
    }

    /**
     * Authentifie l'identifiant et le mot de passe spécifiés.
     *
     * @param string $username Identifiant de connexion
     * @param string $credential Mot de passe
     * @return boolean
     */
    public function authenticateUsername($username, $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)
        $this->usernameUsurpe = null;
        $usernames = self::extractUsernamesUsurpation($username);
        if (count($usernames) === 2) {
            list ($username, $this->usernameUsurpe) = $usernames;
            if (!in_array($username, $this->getOptions()->getUsurpationAllowedUsernames())) {
                $this->usernameUsurpe = null;
            }
        }

        // LDAP auth
        $result  = $this->getLdapAuthAdapter()->setUsername($username)->setPassword($credential)->authenticate();
        $success = $result->isValid();

        // verif existence du login usurpé
        if ($this->usernameUsurpe) {
            // s'il nexiste pas, échec de l'authentification
            if (!$this->getLdapAuthAdapter()->getLdap()->searchEntries("(supannAliasLogin=$this->usernameUsurpe)")) {
                $this->usernameUsurpe = null;
                $success              = false;
            }
        }

        return $success;
    }

    /**
     * get ldap people mapper
     *
     * @return LdapPeopleMapper
     */
    public function getLdapPeopleMapper()
    {
        if (null === $this->ldapPeopleMapper) {
            $this->ldapPeopleMapper = $this->getServiceManager()->get('ldap_people_mapper');
        }
        return $this->ldapPeopleMapper;
    }

    /**
     * set ldap people mapper
     *
     * @param LdapPeopleMapper $mapper
     * @return self
     */
    public function setLdapPeopleMapper(LdapPeopleMapper $mapper)
    {
        $this->ldapPeopleMapper = $mapper;
        return $this;
    }

    /**
     * @param ModuleOptions $options
     */
    public function setOptions(ModuleOptions $options)
    {
        $this->options = $options;
    }

    /**
     * @return ModuleOptions
     */
    public function getOptions()
    {
        if (!$this->options instanceof ModuleOptions) {
            $options = array_merge(
                    $this->getServiceManager()->get('zfcuser_module_options')->toArray(),
                    $this->getServiceManager()->get('unicaen-auth_module_options')->toArray());
            $this->setOptions(new ModuleOptions($options));
        }
        return $this->options;
    }

    /**
     * @return \UnicaenApp\Options\ModuleOptions
     */
    public function getAppModuleOptions()
    {
        return $this->getServiceManager()->get('unicaen-app_module_options');
    }

    /**
     * get ldap connection adapter
     *
     * @return LdapAuthAdapter
     */
    public function getLdapAuthAdapter()
    {
        if (null === $this->ldapAuthAdapter) {
            $options = [];
            if (($config = $this->getAppModuleOptions()->getLdap())) {
                foreach ($config['connection'] as $name => $connection) {
                    $options[$name] = $connection['params'];
                }
            }
            $this->ldapAuthAdapter = new LdapAuthAdapter($options); // NB: array(array)
        }
        return $this->ldapAuthAdapter;
    }

    /**
     * set ldap connection adapter
     *
     * @param LdapAuthAdapter $authAdapter
     * @return Ldap
     */
    public function setLdapAuthAdapter(LdapAuthAdapter $authAdapter)
    {
        $this->ldapAuthAdapter = $authAdapter;
        return $this;
    }

    /**
     * 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;
    }

    /**
     * Retrieve EventManager instance
     *
     * @return EventManagerInterface
     */
    public function getEventManager()
    {
        return $this->eventManager;
    }

    /**
     * Inject an EventManager instance
     *
     * @param  EventManagerInterface $eventManager
     * @return Ldap
     */
    public function setEventManager(EventManagerInterface $eventManager)
    {
        $eventManager->setIdentifiers([
            __NAMESPACE__,
            __CLASS__,
        ]);
        $this->eventManager = $eventManager;
        return $this;
    }
}
 No newline at end of file
+0 −159
Changes for module/Application/src/Application/Authentication/Adapter/Shib.php: 0 added lines, 159 removed lines.
Original line number Diff line number Diff line
<?php

namespace Application\Authentication\Adapter;

use UnicaenApp\Exception\RuntimeException;
use UnicaenAuth\Options\ModuleOptions;
use Zend\EventManager\EventManager;
use Zend\EventManager\EventManagerAwareInterface;
use Zend\EventManager\EventManagerInterface;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\ServiceManagerAwareInterface;

/**
 * Shibboleth authentication adpater
 *
 * @author Unicaen
 */
class Shib implements ServiceManagerAwareInterface, EventManagerAwareInterface
{
    /**
     * @var ServiceManager
     */
    protected $serviceManager;

    /**
     * @var EventManager
     */
    protected $eventManager;

    /**
     * @var ModuleOptions
     */
    protected $options;

    /**
     * @var array
     */
    protected $shibOptions;

    /**
     * @var ShibUser
     */
    protected $authenticatedUser;

    /**
     * @return ShibUser|null
     */
    public function getAuthenticatedUser()
    {
        if ($this->authenticatedUser === null) {
            if (empty($_SERVER['REMOTE_USER'])) {
                return null;
            }
            $this->authenticatedUser = $this->createShibUser();
        }

        return $this->authenticatedUser;
    }

    /**
     * @return ShibUser
     */
    private function createShibUser()
    {
        $eppn = $_SERVER['REMOTE_USER'];

        if (isset($_SERVER['supannEtuId'])) {
            $id = $_SERVER['supannEtuId'];
        } elseif (isset($_SERVER['supannEmpId'])) {
            $id = $_SERVER['supannEmpId'];
        } else {
            throw new RuntimeException('Un au moins des attributs suivants doivent exister dans $_SERVER : supannEtuId, supannEmpId.');
        }

        $mail = null;
        if (isset($_SERVER['mail'])) {
            $mail = $_SERVER['mail'];
        }

        $displayName = null;
        if (isset($_SERVER['displayName'])) {
            $displayName = $_SERVER['displayName'];
        }

        $shibUser = new ShibUser();
        $shibUser->setId($id);
        $shibUser->setUsername($eppn);
        $shibUser->setDisplayName($displayName);
        $shibUser->setEmail($mail);

        return $shibUser;
    }

    /**
     * @param ModuleOptions $options
     */
    public function setOptions(ModuleOptions $options)
    {
        $this->options = $options;
    }

    /**
     * @return ModuleOptions
     */
    public function getOptions()
    {
        if (!$this->options instanceof ModuleOptions) {
            $options = array_merge(
                    $this->getServiceManager()->get('zfcuser_module_options')->toArray(),
                    $this->getServiceManager()->get('unicaen-auth_module_options')->toArray());
            $this->setOptions(new ModuleOptions($options));
        }
        return $this->options;
    }

    /**
     * Get service manager
     *
     * @return ServiceManager
     */
    public function getServiceManager()
    {
        return $this->serviceManager;
    }

    /**
     * Set service manager
     *
     * @param ServiceManager $serviceManager
     * @return self
     */
    public function setServiceManager(ServiceManager $serviceManager)
    {
        $this->serviceManager = $serviceManager;
        return $this;
    }

    /**
     * Retrieve EventManager instance
     *
     * @return EventManagerInterface
     */
    public function getEventManager()
    {
        return $this->eventManager;
    }

    /**
     * Inject an EventManager instance
     *
     * @param  EventManagerInterface $eventManager
     * @return self
     */
    public function setEventManager(EventManagerInterface $eventManager)
    {
        $this->eventManager = $eventManager;
        return $this;
    }
}
 No newline at end of file
Loading