Ldap.php 9.61 KB
Newer Older
Bertrand Gauthier's avatar
Bertrand Gauthier committed
1
<?php
2

Bertrand Gauthier's avatar
Bertrand Gauthier committed
3 4
namespace UnicaenAuth\Authentication\Adapter;

5 6
use UnicaenApp\Exception\RuntimeException;
use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
Bertrand Gauthier's avatar
Bertrand Gauthier committed
7
use UnicaenAuth\Options\ModuleOptions;
8
use UnicaenAuth\Service\User;
Bertrand Gauthier's avatar
Bertrand Gauthier committed
9
use Zend\Authentication\Adapter\Ldap as LdapAuthAdapter;
10 11
use Zend\Authentication\Exception\ExceptionInterface;
use Zend\Authentication\Result as AuthenticationResult;
Laurent Lécluse's avatar
Laurent Lécluse committed
12
use Zend\EventManager\Event;
13
use Zend\EventManager\EventInterface;
Bertrand Gauthier's avatar
Bertrand Gauthier committed
14 15 16 17 18
use Zend\EventManager\EventManager;
use Zend\EventManager\EventManagerAwareInterface;
use Zend\EventManager\EventManagerInterface;
use ZfcUser\Authentication\Adapter\AbstractAdapter;
use ZfcUser\Authentication\Adapter\ChainableAdapter;
19
use Zend\Authentication\Storage\Session;
Bertrand Gauthier's avatar
Bertrand Gauthier committed
20 21 22 23 24 25

/**
 * LDAP authentication adpater
 *
 * @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
 */
26
class Ldap extends AbstractAdapter implements EventManagerAwareInterface
Bertrand Gauthier's avatar
Bertrand Gauthier committed
27
{
28
    const USURPATION_USERNAMES_SEP = '=';
Bertrand Gauthier's avatar
Bertrand Gauthier committed
29 30

    /**
Bertrand Gauthier's avatar
Bertrand Gauthier committed
31
     * @var EventManager
Bertrand Gauthier's avatar
Bertrand Gauthier committed
32 33
     */
    protected $eventManager;
34

Bertrand Gauthier's avatar
Bertrand Gauthier committed
35
    /**
Bertrand Gauthier's avatar
Bertrand Gauthier committed
36
     * @var LdapAuthAdapter
Bertrand Gauthier's avatar
Bertrand Gauthier committed
37
     */
Bertrand Gauthier's avatar
Bertrand Gauthier committed
38
    protected $ldapAuthAdapter;
Bertrand Gauthier's avatar
Bertrand Gauthier committed
39

40 41 42 43 44
    /**
     * @var LdapPeopleMapper
     */
    protected $ldapPeopleMapper;

Bertrand Gauthier's avatar
Bertrand Gauthier committed
45
    /**
Bertrand Gauthier's avatar
Bertrand Gauthier committed
46
     * @var ModuleOptions
Bertrand Gauthier's avatar
Bertrand Gauthier committed
47
     */
Bertrand Gauthier's avatar
Bertrand Gauthier committed
48
    protected $options;
49

50
    /**
51
     * @var string
52 53
     */
    protected $usernameUsurpe;
54

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
    /**
     * @var User
     */
    private $userService;

    /**
     * @param User $userService
     */
    public function setUserService(User $userService)
    {
        $this->userService = $userService;
    }

    /**
     * @var \UnicaenApp\Options\ModuleOptions
     */
    private $appModuleOptions;

    /**
     * @param \UnicaenApp\Options\ModuleOptions $appModuleOptions
     */
    public function setAppModuleOptions(\UnicaenApp\Options\ModuleOptions $appModuleOptions)
    {
        $this->appModuleOptions = $appModuleOptions;
    }

81 82 83 84 85 86 87 88 89
    public function getStorage()
    {
        if (null === $this->storage) {
            $this->setStorage(new Session());
        }

        return $this->storage;
    }
    
Bertrand Gauthier's avatar
Bertrand Gauthier committed
90
    /**
91
     *
92
     * @param EventInterface $e
Bertrand Gauthier's avatar
Bertrand Gauthier committed
93
     * @return boolean
94 95
     * @throws \Zend\Authentication\Adapter\Exception\ExceptionInterface
     * @throws \Zend\Ldap\Exception\LdapException
Bertrand Gauthier's avatar
Bertrand Gauthier committed
96 97
     * @see ChainableAdapter
     */
98
    public function authenticate(EventInterface $e)
Bertrand Gauthier's avatar
Bertrand Gauthier committed
99
    {
100 101 102 103 104
        // 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().
        $e = $e->getTarget();

Bertrand Gauthier's avatar
Bertrand Gauthier committed
105
        if ($this->isSatisfied()) {
106 107 108 109 110
            try {
                $storage = $this->getStorage()->read();
            } catch (ExceptionInterface $e) {
                throw new RuntimeException("Erreur de lecture du storage");
            }
Bertrand Gauthier's avatar
Bertrand Gauthier committed
111
            $e->setIdentity($storage['identity'])
112 113
                ->setCode(AuthenticationResult::SUCCESS)
                ->setMessages(['Authentication successful.']);
Bertrand Gauthier's avatar
Bertrand Gauthier committed
114 115 116
            return;
        }

Bertrand Gauthier's avatar
Bertrand Gauthier committed
117
        $username   = $e->getRequest()->getPost()->get('identity');
Bertrand Gauthier's avatar
Bertrand Gauthier committed
118 119
        $credential = $e->getRequest()->getPost()->get('credential');

120 121 122 123 124 125
        if (function_exists('mb_strtolower')) {
            $username = mb_strtolower($username, 'UTF-8');
        } else {
            $username = strtolower($username);
        }

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

Bertrand Gauthier's avatar
Bertrand Gauthier committed
128
        // Failure!
129
        if (! $success) {
Bertrand Gauthier's avatar
Bertrand Gauthier committed
130
            $e->setCode(AuthenticationResult::FAILURE)
131 132 133 134 135 136
                ->setMessages(['LDAP bind failed.']);
            $this->setSatisfied(false);
            return false;
        }

        // recherche de l'individu dans l'annuaire LDAP
137
        $ldapPeople = $this->getLdapPeopleMapper()->findOneByUsername($this->usernameUsurpe ?: $username);
138 139 140 141
        if (!$ldapPeople) {
            $e
                ->setCode(AuthenticationResult::FAILURE)
                ->setMessages(['Authentication failed.']);
Bertrand Gauthier's avatar
Bertrand Gauthier committed
142 143 144
            $this->setSatisfied(false);
            return false;
        }
145

146
        $e->setIdentity($this->usernameUsurpe ?: $username);
Bertrand Gauthier's avatar
Bertrand Gauthier committed
147
        $this->setSatisfied(true);
148 149 150 151 152 153 154
        try {
            $storage = $this->getStorage()->read();
            $storage['identity'] = $e->getIdentity();
            $this->getStorage()->write($storage);
        } catch (ExceptionInterface $e) {
            throw new RuntimeException("Erreur de concernant le storage");
        }
Bertrand Gauthier's avatar
Bertrand Gauthier committed
155
        $e->setCode(AuthenticationResult::SUCCESS)
156
            ->setMessages(['Authentication successful.']);
157

158
        /* @var $userService User */
159
        $this->userService->userAuthenticated($ldapPeople);
Bertrand Gauthier's avatar
Bertrand Gauthier committed
160 161
    }

162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
    /**
     * 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
            ];
        }
181

182
        return [];
Bertrand Gauthier's avatar
Bertrand Gauthier committed
183 184
    }

185 186
    /**
     * Authentifie l'identifiant et le mot de passe spécifiés.
187
     *
188
     * @param string $username   Identifiant de connexion
189 190
     * @param string $credential Mot de passe
     * @return boolean
191 192
     * @throws \Zend\Authentication\Adapter\Exception\ExceptionInterface
     * @throws \Zend\Ldap\Exception\LdapException
193 194 195 196 197 198 199
     */
    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;
200 201 202
        $usernames = self::extractUsernamesUsurpation($username);
        if (count($usernames) === 2) {
            list ($username, $this->usernameUsurpe) = $usernames;
203 204 205 206 207 208 209
            if (!in_array($username, $this->getOptions()->getUsurpationAllowedUsernames())) {
                $this->usernameUsurpe = null;
            }
        }

        // LDAP auth
        $result  = $this->getLdapAuthAdapter()->setUsername($username)->setPassword($credential)->authenticate();
210 211 212 213 214

        if ($result && count($result->getMessages())) {
            // Obtenir le message LDAP
            // $msg = preg_replace('/\[0x\d* \((.*)\):/','$1', $event->getParam('result')->getMessages()[1]);
            $eventClasse = new Event('authentication.ldap.error');
215 216
            $eventClasse->setTarget($this);
            $eventClasse->setParams([
217 218
                'result' => $result
            ]);
219
            $this->getEventManager()->triggerEvent($eventClasse);
220 221
        }

222
        $success = $result->isValid();
223

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

233 234
        return $success;
    }
235

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
    /**
     * get ldap people mapper
     *
     * @return LdapPeopleMapper
     */
    public function getLdapPeopleMapper()
    {
        return $this->ldapPeopleMapper;
    }

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

Bertrand Gauthier's avatar
Bertrand Gauthier committed
258
    /**
Bertrand Gauthier's avatar
Bertrand Gauthier committed
259
     * @param ModuleOptions $options
Bertrand Gauthier's avatar
Bertrand Gauthier committed
260
     */
Bertrand Gauthier's avatar
Bertrand Gauthier committed
261
    public function setOptions(ModuleOptions $options)
Bertrand Gauthier's avatar
Bertrand Gauthier committed
262
    {
Bertrand Gauthier's avatar
Bertrand Gauthier committed
263
        $this->options = $options;
Bertrand Gauthier's avatar
Bertrand Gauthier committed
264 265 266
    }

    /**
Bertrand Gauthier's avatar
Bertrand Gauthier committed
267
     * @return ModuleOptions
Bertrand Gauthier's avatar
Bertrand Gauthier committed
268
     */
Bertrand Gauthier's avatar
Bertrand Gauthier committed
269
    public function getOptions()
Bertrand Gauthier's avatar
Bertrand Gauthier committed
270
    {
Bertrand Gauthier's avatar
Bertrand Gauthier committed
271
        return $this->options;
Bertrand Gauthier's avatar
Bertrand Gauthier committed
272
    }
Bertrand Gauthier's avatar
Bertrand Gauthier committed
273

Bertrand Gauthier's avatar
Bertrand Gauthier committed
274
    /**
Bertrand Gauthier's avatar
Bertrand Gauthier committed
275
     * @return \UnicaenApp\Options\ModuleOptions
Bertrand Gauthier's avatar
Bertrand Gauthier committed
276
     */
Bertrand Gauthier's avatar
Bertrand Gauthier committed
277
    public function getAppModuleOptions()
Bertrand Gauthier's avatar
Bertrand Gauthier committed
278
    {
279
        return $this->appModuleOptions;
Bertrand Gauthier's avatar
Bertrand Gauthier committed
280 281 282 283
    }

    /**
     * get ldap connection adapter
284
     *
Bertrand Gauthier's avatar
Bertrand Gauthier committed
285
     * @return LdapAuthAdapter
Bertrand Gauthier's avatar
Bertrand Gauthier committed
286
     */
Bertrand Gauthier's avatar
Bertrand Gauthier committed
287
    public function getLdapAuthAdapter()
Bertrand Gauthier's avatar
Bertrand Gauthier committed
288
    {
Bertrand Gauthier's avatar
Bertrand Gauthier committed
289
        if (null === $this->ldapAuthAdapter) {
290
            $options = [];
Bertrand Gauthier's avatar
Bertrand Gauthier committed
291 292 293 294 295 296
            if (($config = $this->getAppModuleOptions()->getLdap())) {
                foreach ($config['connection'] as $name => $connection) {
                    $options[$name] = $connection['params'];
                }
            }
            $this->ldapAuthAdapter = new LdapAuthAdapter($options); // NB: array(array)
Bertrand Gauthier's avatar
Bertrand Gauthier committed
297
        }
Bertrand Gauthier's avatar
Bertrand Gauthier committed
298
        return $this->ldapAuthAdapter;
Bertrand Gauthier's avatar
Bertrand Gauthier committed
299 300 301 302
    }

    /**
     * set ldap connection adapter
303
     *
Bertrand Gauthier's avatar
Bertrand Gauthier committed
304
     * @param LdapAuthAdapter $authAdapter
Bertrand Gauthier's avatar
Bertrand Gauthier committed
305 306
     * @return Ldap
     */
Bertrand Gauthier's avatar
Bertrand Gauthier committed
307
    public function setLdapAuthAdapter(LdapAuthAdapter $authAdapter)
Bertrand Gauthier's avatar
Bertrand Gauthier committed
308
    {
Bertrand Gauthier's avatar
Bertrand Gauthier committed
309
        $this->ldapAuthAdapter = $authAdapter;
Bertrand Gauthier's avatar
Bertrand Gauthier committed
310 311 312 313
        return $this;
    }

    /**
Bertrand Gauthier's avatar
Bertrand Gauthier committed
314 315 316
     * Retrieve EventManager instance
     *
     * @return EventManagerInterface
Bertrand Gauthier's avatar
Bertrand Gauthier committed
317
     */
Bertrand Gauthier's avatar
Bertrand Gauthier committed
318
    public function getEventManager()
Bertrand Gauthier's avatar
Bertrand Gauthier committed
319
    {
Bertrand Gauthier's avatar
Bertrand Gauthier committed
320
        return $this->eventManager;
Bertrand Gauthier's avatar
Bertrand Gauthier committed
321
    }
322

Bertrand Gauthier's avatar
Bertrand Gauthier committed
323
    /**
324
     * {@inheritdoc}
Bertrand Gauthier's avatar
Bertrand Gauthier committed
325
     */
Bertrand Gauthier's avatar
Bertrand Gauthier committed
326
    public function setEventManager(EventManagerInterface $eventManager)
Bertrand Gauthier's avatar
Bertrand Gauthier committed
327
    {
328 329 330 331
        $eventManager->setIdentifiers([
            __NAMESPACE__,
            __CLASS__,
        ]);
Bertrand Gauthier's avatar
Bertrand Gauthier committed
332 333
        $this->eventManager = $eventManager;
        return $this;
Bertrand Gauthier's avatar
Bertrand Gauthier committed
334 335
    }
}