Skip to content
Snippets Groups Projects
Select Git revision
  • 3d7e506fc2613ba497fdf541b5060076809584aa
  • main default protected
  • renovate/lock-file-maintenance
  • demo protected
  • person-select-custom
  • dbp-translation-component
  • icon-set-mapping
  • port-i18next-parser
  • remove-sentry
  • favorites-and-recent-files
  • revert-6c632dc6
  • lit2
  • advertisement
  • wc-part
  • automagic
  • publish
  • wip-cleanup
  • demo-file-handling
18 results

README.md

Blame
  • LDAPApi.php 11.28 KiB
    <?php
    
    declare(strict_types=1);
    /**
     * LDAP wrapper service.
     *
     * @see https://github.com/Adldap2/Adldap2
     */
    
    namespace Dbp\Relay\BasePersonConnectorLdapBundle\Service;
    
    use Adldap\Adldap;
    use Adldap\Connections\Provider;
    use Adldap\Connections\ProviderInterface;
    use Adldap\Models\User;
    use Adldap\Query\Builder;
    use Dbp\Relay\BasePersonBundle\Entity\Person;
    use Dbp\Relay\BasePersonConnectorLdapBundle\Event\PersonFromUserItemPostEvent;
    use Dbp\Relay\BasePersonConnectorLdapBundle\Event\PersonFromUserItemPreEvent;
    use Dbp\Relay\BasePersonConnectorLdapBundle\Event\PersonUserItemPreEvent;
    use Dbp\Relay\CoreBundle\API\UserSessionInterface;
    use Dbp\Relay\CoreBundle\Exception\ApiError;
    use Dbp\Relay\CoreBundle\Helpers\Tools as CoreTools;
    use Psr\Cache\CacheItemPoolInterface;
    use Psr\Container\ContainerInterface;
    use Psr\Log\LoggerAwareInterface;
    use Psr\Log\LoggerAwareTrait;
    use Symfony\Component\Cache\Psr16Cache;
    use Symfony\Component\EventDispatcher\EventDispatcherInterface;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
    use Symfony\Contracts\Service\ServiceSubscriberInterface;
    
    class LDAPApi implements LoggerAwareInterface, ServiceSubscriberInterface
    {
        use LoggerAwareTrait;
    
        private $PAGESIZE = 50;
    
        /**
         * @var Adldap
         */
        private $ad;
    
        private $cachePool;
    
        private $personCache;
    
        private $cacheTTL;
    
        /**
         * @var Person|null
         */
        private $currentPerson;
    
        private $providerConfig;
    
        private $deploymentEnv;
    
        private $locator;
    
        private $identifierAttributeName;
    
        private $givenNameAttributeName;
    
        private $familyNameAttributeName;
    
        private $emailAttributeName;
    
        private $birthdayAttributeName;
    
        /** @var EventDispatcherInterface */
        private $dispatcher;
    
        public function __construct(ContainerInterface $locator, EventDispatcherInterface $dispatcher)
        {
            $this->ad = new Adldap();
            $this->cacheTTL = 0;
            $this->currentPerson = null;
            $this->locator = $locator;
            $this->deploymentEnv = 'production';
            $this->dispatcher = $dispatcher;
        }
    
        public function setConfig(array $config)
        {
            $this->identifierAttributeName = $config['ldap']['attributes']['identifier'] ?? 'cn';
            $this->givenNameAttributeName = $config['ldap']['attributes']['given_name'] ?? 'givenName';
            $this->familyNameAttributeName = $config['ldap']['attributes']['family_name'] ?? 'sn';
            $this->emailAttributeName = $config['ldap']['attributes']['email'] ?? '';
            $this->birthdayAttributeName = $config['ldap']['attributes']['birthday'] ?? '';
    
            $this->providerConfig = [
                'hosts' => [$config['ldap']['host'] ?? ''],
                'base_dn' => $config['ldap']['base_dn'] ?? '',
                'username' => $config['ldap']['username'] ?? '',
                'password' => $config['ldap']['password'] ?? '',
            ];
    
            $encryption = $config['ldap']['encryption'];
            assert(in_array($encryption, ['start_tls', 'simple_tls'], true));
            $this->providerConfig['use_tls'] = ($encryption === 'start_tls');
            $this->providerConfig['use_ssl'] = ($encryption === 'simple_tls');
            $this->providerConfig['port'] = ($encryption === 'start_tls') ? 389 : 636;
        }
    
        public function checkConnection()
        {
            $provider = $this->getProvider();
            $builder = $this->getCachedBuilder($provider);
            $builder->first();
        }
    
        public function setDeploymentEnvironment(string $env)
        {
            $this->deploymentEnv = $env;
        }
    
        public function setLDAPCache(?CacheItemPoolInterface $cachePool, int $ttl)
        {
            $this->cachePool = $cachePool;
            $this->cacheTTL = $ttl;
        }
    
        public function setPersonCache(?CacheItemPoolInterface $cachePool)
        {
            $this->personCache = $cachePool;
        }
    
        private function getProvider(): ProviderInterface
        {
            if ($this->logger !== null) {
                Adldap::setLogger($this->logger);
            }
            $ad = new Adldap();
            $ad->addProvider($this->providerConfig);
            $provider = $ad->connect();
            assert($provider instanceof Provider);
            if ($this->cachePool !== null) {
                $provider->setCache(new Psr16Cache($this->cachePool));
            }
    
            return $provider;
        }
    
        private function getCachedBuilder(ProviderInterface $provider): Builder
        {
            // FIXME: https://github.com/Adldap2/Adldap2/issues/786
            // return $provider->search()->cache($until=$this->cacheTTL);
            // We depend on the default TTL of the cache for now...
    
            /**
             * @var Builder $builder
             */
            $builder = $provider->search()->cache();
    
            return $builder;
        }
    
        private function getPeopleUserItems(array $filters): array
        {
            try {
                $provider = $this->getProvider();
                $builder = $this->getCachedBuilder($provider);
    
                $search = $builder
                    ->where('objectClass', '=', $provider->getSchema()->person());
    
                if (isset($filters['search'])) {
                    $items = explode(' ', $filters['search']);
    
                    // search for all substrings
                    foreach ($items as $item) {
                        $search->whereContains('fullName', $item);
                    }
                }
    
                return $search->sortBy($this->familyNameAttributeName, 'asc')->paginate($this->PAGESIZE)->getResults();
            } catch (\Adldap\Auth\BindException $e) {
                // There was an issue binding / connecting to the server.
                throw new ApiError(Response::HTTP_BAD_GATEWAY, sprintf('People could not be loaded! Message: %s', CoreTools::filterErrorMessage($e->getMessage())));
            }
        }
    
        public function getPersons(array $filters): array
        {
            $persons = [];
            $items = $this->getPeopleUserItems($filters);
            foreach ($items as $item) {
                $person = $this->personFromUserItem($item, false);
                $persons[] = $person;
            }
    
            return $persons;
        }
    
        public function getPersonUserItem(string $identifier): ?User
        {
            $preEvent = new PersonUserItemPreEvent($identifier);
            $this->dispatcher->dispatch($preEvent, PersonUserItemPreEvent::NAME);
            $identifier = $preEvent->getIdentifier();
    
            try {
                $provider = $this->getProvider();
                $builder = $this->getCachedBuilder($provider);
    
                /** @var User $user */
                $user = $builder
                    ->where('objectClass', '=', $provider->getSchema()->person())
                    ->whereEquals($this->identifierAttributeName, $identifier)
                    ->first();
    
                if ($user === null) {
                    throw new NotFoundHttpException(sprintf("Person with id '%s' could not be found!", $identifier));
                }
    
                /* @var User $user */
                return $user;
            } catch (\Adldap\Auth\BindException $e) {
                // There was an issue binding / connecting to the server.
                throw new ApiError(Response::HTTP_BAD_GATEWAY, sprintf("Person with id '%s' could not be loaded! Message: %s", $identifier, CoreTools::filterErrorMessage($e->getMessage())));
            }
        }
    
        public function personFromUserItem(User $user, bool $full): Person
        {
    //        $preEvent = new PersonFromUserItemPreEvent($user, $full);
    //        $this->dispatcher->dispatch($preEvent, PersonFromUserItemPreEvent::NAME);
    //        $user = $preEvent->getUser();
    
            $identifier = $user->getFirstAttribute($this->identifierAttributeName);
    
            $person = new Person();
            $person->setIdentifier($identifier);
            $person->setGivenName($user->getFirstAttribute($this->givenNameAttributeName));
            $person->setFamilyName($user->getFirstAttribute($this->familyNameAttributeName));
    
            if ($this->emailAttributeName !== '') {
                $person->setEmail($user->getFirstAttribute($this->emailAttributeName) ?? '');
            }
    
            $birthDateString = $this->birthdayAttributeName !== '' ?
                trim($user->getFirstAttribute($this->birthdayAttributeName) ?? '') : '';
    
            if ($birthDateString !== '') {
                $matches = [];
    
                if (preg_match('/^(\d{4})-(\d{2})-(\d{2})/', $birthDateString, $matches)) {
                    $person->setBirthDate("{$matches[1]}-{$matches[2]}-{$matches[3]}");
                }
            }
    
            // Remove all value with numeric keys
            $attributes = [];
            foreach ($user->getAttributes() as $key => $value) {
                if (!is_numeric($key)) {
                    $attributes[$key] = $value;
                }
            }
    
            $postEvent = new PersonFromUserItemPostEvent($attributes, $person, $full);
            $this->dispatcher->dispatch($postEvent, PersonFromUserItemPostEvent::NAME);
    
            return $postEvent->getPerson();
        }
    
        public function getPerson(string $id): Person
        {
            $id = str_replace('/people/', '', $id);
    
            $session = $this->getUserSession();
            $currentIdentifier = $session->getUserIdentifier();
    
            if ($currentIdentifier !== null && $currentIdentifier === $id) {
                // fast path: getCurrentPerson() does some caching
                $person = $this->getCurrentPerson();
                assert($person !== null);
            } else {
                $user = $this->getPersonUserItem($id);
                $person = $this->personFromUserItem($user, true);
            }
    
            return $person;
        }
    
        private function getUserSession(): UserSessionInterface
        {
            return $this->locator->get(UserSessionInterface::class);
        }
    
        public function getCurrentPersonCached(): Person
        {
            $session = $this->getUserSession();
            $currentIdentifier = $session->getUserIdentifier();
            assert($currentIdentifier !== null);
    
            $cache = $this->personCache;
            $cacheKey = $session->getSessionCacheKey().'-'.$currentIdentifier;
            // make sure the cache is longer than the session, so just double it.
            $cacheTTL = $session->getSessionTTL() * 2;
    
            $item = $cache->getItem($cacheKey);
    
            if ($item->isHit()) {
                $person = $item->get();
                if ($person === null) {
                    throw new NotFoundHttpException();
                }
    
                return $person;
            } else {
                try {
                    $user = $this->getPersonUserItem($currentIdentifier);
                    $person = $this->personFromUserItem($user, true);
                } catch (NotFoundHttpException $e) {
                    $person = null;
                }
                $item->set($person);
                $item->expiresAfter($cacheTTL);
                $cache->save($item);
                if ($person === null) {
                    throw new NotFoundHttpException();
                }
    
                return $person;
            }
        }
    
        public function getCurrentPerson(): ?Person
        {
            $session = $this->getUserSession();
            $currentIdentifier = $session->getUserIdentifier();
            if ($currentIdentifier === null) {
                return null;
            }
    
            if ($this->currentPerson !== null && $this->currentPerson->getIdentifier() !== $currentIdentifier) {
                $this->currentPerson = null;
            }
    
            if ($this->currentPerson === null) {
                $this->currentPerson = $this->getCurrentPersonCached();
            }
    
            return $this->currentPerson;
        }
    
        public static function getSubscribedServices(): array
        {
            return [
                UserSessionInterface::class,
            ];
        }
    }