Commit 0d70b318 authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

Work in progress

parent c88975f1
Loading
Loading
Loading
Loading
+4 −0
Changes for module/Application/config/module.config.php: 4 added lines, 0 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;
@@ -173,6 +174,9 @@ return array(
            'NotificationService'            => NotificationServiceFactory::class,
            'Sygal\Memcached'                => MemcachedFactory::class,
        ),
        'abstract_factories' => [
            AbstractFactory::class,
        ],
        'initializers' => [
            ServiceAwareInitializer::class,
            AuthorizeServiceAwareInitializer::class,
+61 −0
Changes for module/Application/src/Application/Authentication/Adapter/AbstractFactory.php: 61 added lines, 0 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;
            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
+314 −0
Changes for module/Application/src/Application/Authentication/Adapter/Ldap.php: 314 added lines, 0 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($e->getIdentity(), $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
+183 −0
Changes for module/Application/src/Application/Authentication/Adapter/Shib.php: 183 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Application\Authentication\Adapter;

use phpCAS;
use UnicaenApp\Exception;
use UnicaenAuth\Options\ModuleOptions;
use Zend\Authentication\Exception\ExceptionInterface;
use Zend\Authentication\Exception\UnexpectedValueException;
use Zend\Authentication\Result as AuthenticationResult;
use Zend\EventManager\EventManager;
use Zend\EventManager\EventManagerAwareInterface;
use Zend\EventManager\EventManagerInterface;
use Zend\Http\Headers;
use Zend\Http\Request;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\ServiceManagerAwareInterface;
use Zend\Http\Response;
use ZfcUser\Authentication\Adapter\AbstractAdapter;
use ZfcUser\Authentication\Adapter\AdapterChainEvent as AuthEvent;
use ZfcUser\Authentication\Adapter\ChainableAdapter;

/**
 * CAS authentication adpater
 *
 * @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
 */
class Shib extends AbstractAdapter implements ServiceManagerAwareInterface, EventManagerAwareInterface
{
    /**
     * @var ServiceManager
     */
    protected $serviceManager;

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

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

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

    /**
     * @var phpCAS
     */
    protected $casClient;

    /**
     * Réalise l'authentification.
     *
     * @param AuthEvent $e
     * @return Response|null
     * @see ChainableAdapter
     */
    public function authenticate(AuthEvent $e)
    {
	    if ($this->isSatisfied()) {
            try {
                $storage = $this->getStorage()->read();
            } catch (ExceptionInterface $e) {
                throw new Exception\RuntimeException("Erreur de lecture du storage");
            }
            $e
                ->setIdentity($storage['identity'])
                ->setCode(AuthenticationResult::SUCCESS)
                ->setMessages(['Authentication successful.']);
            return null;
        }

        if (empty($_SERVER['REMOTE_USER'])) {

            /** @var Request $request */
            $request = $e->getRequest();
            $returnUrl = $request->getQuery('redirect', false);

            $response = new Response();
            $response->setStatusCode(Response::STATUS_CODE_302);
            $response->getHeaders()->addHeaders([
                'Location' => "/secure?redirect=" . urlencode($returnUrl),
            ]);

            return $response;
        }

        $eppn = $_SERVER['REMOTE_USER'];

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

        $userData = new ShibUser();
        $userData->setId($eppn);
        $userData->setUsername($eppn);
        $userData->setDisplayName($eppn);
        $userData->setEmail($eppn);

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

    /**
     * @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
+157 −0
Changes for module/Application/src/Application/Authentication/Adapter/ShibUser.php: 157 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Application\Authentication\Adapter;

use ZfcUser\Entity\UserInterface;

class ShibUser implements UserInterface
{
    protected $id;

    protected $username;

    protected $email;

    protected $displayName;

    protected $state = 1;

    /**
     * Get id.
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set id.
     *
     * @param int $id
     *
     * @return void
     */
    public function setId($id)
    {
        $this->id = (int) $id;
    }

    /**
     * Get username.
     *
     * @return string
     */
    public function getUsername()
    {
        return $this->username;
    }

    /**
     * Set username.
     *
     * @param string $username
     *
     * @return void
     */
    public function setUsername($username)
    {
        $this->username = $username;
    }

    /**
     * Get email.
     *
     * @return string
     */
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * Set email.
     *
     * @param string $email
     *
     * @return void
     */
    public function setEmail($email)
    {
        $this->email = $email;
    }

    /**
     * Get displayName.
     *
     * @return string
     */
    public function getDisplayName()
    {
        return $this->displayName;
    }

    /**
     * Set displayName.
     *
     * @param string $displayName
     *
     * @return void
     */
    public function setDisplayName($displayName)
    {
        $this->displayName = $displayName;
    }

    /**
     * Get password.
     *
     * @return string
     */
    public function getPassword()
    {
        return 'shib';
    }

    /**
     * Set password.
     *
     * @param string $password
     */
    public function setPassword($password)
    {

    }

    /**
     * Get state.
     *
     * @return int
     */
    public function getState()
    {
        return $this->state;
    }

    /**
     * Set state.
     *
     * @param int $state
     *
     * @return void
     */
    public function setState($state)
    {
        $this->state = $state;
    }

    /**
     *
     * @return string
     */
    public function __toString()
    {
        return (string) $this->getDisplayName();
    }
}
 No newline at end of file
Loading