Skip to content
Snippets Groups Projects
Select Git revision
  • a2e1f94c4d32fdec4ea211525dfcdc9ad20ba34c
  • master default protected
  • main
  • update_github_actions
  • 144_rocky8_support
  • 195-update-pdk-to-300
  • 144-rocky8
  • add_test_github_test_workflow
  • pdk_2.4.0
  • fix_unclosed_let_block_in_defines_client_spec
  • validation_fixes
  • freeradius_3_0_21_config_updates
  • data_types
  • PrepareBuster
  • travis
  • 4.0.1
  • 4.0.0
  • 3.9.2
  • 3.9.1
  • 3.9.0
  • 3.8.2
  • 3.8.1
  • 3.8.0
  • 3.7.0
  • 3.6.0
  • 3.5.0
  • 3.4.3
  • 3.4.2
  • 3.4.1
  • 3.4.0
  • 3.3.0
  • 3.2.0
  • 3.1.0
  • 3.0.0
  • 2.3.1
35 results

dictionary.header

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    AbstractService.php 15.65 KiB
    <?php
    
    namespace UnicaenLdap\Service;
    
    use UnicaenLdap\Ldap;
    use UnicaenLdap\Entity\Entity;
    use UnicaenLdap\Exception;
    use UnicaenLdap\Collection;
    use UnicaenLdap\Filter\Filter;
    use Zend\ServiceManager\ServiceLocatorAwareTrait;
    use Zend\ServiceManager\ServiceManager;
    use Zend\ServiceManager\ServiceManagerAwareInterface;
    use Zend\Ldap\Filter\AbstractFilter;
    use Zend\Ldap\Dn;
    use Zend\Stdlib\ErrorHandler;
    
    /**
     * Classe mère des services d'accès à l'annuaire LDAP.
     *
     * @author Laurent Lécluse <laurent.lecluse at unicaen.fr>
     */
    abstract class AbstractService
    {
        use ServiceLocatorAwareTrait;
    
        /**
         * Limite de recherche par défaut
         */
        const DEFAULT_LIMIT = 10;
    
        /**
         * Offset par défaut
         */
        const DEFAULT_OFFSET = 0;
    
        /**
         * @var Ldap
         */
        protected $ldap;
    
        /**
         *
         * @var string
         */
        protected $type;
    
        /**
         * Organizational Units
         * 
         * @var string[]
         */
        protected $ou = array();
    
        /**
         *
         * @var integer
         */
        protected $count;
    
        /**
         * Retourne le type du service
         * 
         * @return string
         */
        public function getType()
        {
            return $this->type;
        }
    
        /**
         * Retourne l'objet d'accès à l'annuaire LDAP.
         *
         * @return Ldap
         */
        public function getLdap()
        {
            if (empty($this->ldap)){
                $this->ldap = $this->getServiceLocator()->get('ldap');
            }
            return $this->ldap;
        }
    
        /**
         * Spécifie l'objet d'accès à l'annuaire LDAP.
         *
         * @param Ldap $ldap
         * @return AbstractService
         */
        public function setLdap(Ldap $ldap = null)
        {
            $this->ldap = $ldap;
            return $this;
        }
    
        /**
         * Retourne la liste des organizational units
         *
         * @return string[]
         */
        public function getOu()
        {
            return $this->ou;
        }
    
        /**
         * Redéfinie la liste des organizational units
         *
         * @param string[] $ou
         * @return AbstractService
         */
        public function setOu(array $ou)
        {
            $this->ou = $ou;
            return $this;
        }
    
        /**
         * Retourne la liste de toutes les entités correspondantes
         * 
         * @param string $orderBy Propriété de référence pour le tri
         * @return Collection
         */
        public function getList( $orderBy=null, $limit=self::DEFAULT_LIMIT, $offset=self::DEFAULT_OFFSET )
        {
            list($key) = Entity::getNodeParams($this->type);
            return $this->search( "($key=*)", $orderBy, $limit, $offset );
        }
    
        /**
         * Recherche une liste d'entités correspondantes
         *
         * @param string|AbstractFilter $filter  Valeur de recherche
         * @param string                $orderBy Attribut de référence pour le tri
         * @param integer               $limit   Nombre maximum d'occurences renvoyées (-1 = infini)
         * @param integer               $offset  Renvoi les entités à partir de $offset uniquement
         * @return Collection
         * @throws Exception
         */
        public function search( $filter, $orderBy=null, $limit=self::DEFAULT_LIMIT, $offset=self::DEFAULT_OFFSET )
        {
            list($key) = Entity::getNodeParams($this->type);
            if ($limit < 0) $limit = 3999999999; // Limite maximum à 4 milliard...
            if ($offset < 0) $offset = 0; // Moins de zéro = impossible
    
            list( $resource, $search ) = $this->__searchBegin($filter, $this->ou, array($key,$orderBy));
    
            ErrorHandler::start(E_WARNING);
            $this->count = ldap_count_entries($resource, $search);
            ErrorHandler::stop();
    
            if ($this->count > 0){
    
                if ($orderBy !== null && is_string($orderBy)) {
                    ErrorHandler::start(E_WARNING);
                    $isSorted = ldap_sort($resource, $search, $orderBy);
                    ErrorHandler::stop();
                    if ($isSorted === false) {
                        throw new Exception($this, 'sorting: ' . $orderBy);
                    }
                }
    
                $result = array();
                $i = 0;
                ErrorHandler::start(E_WARNING);
                for ($entry=ldap_first_entry($resource,$search); $entry; $entry=ldap_next_entry($resource,$entry)) {
                    list($value) = ldap_get_values_len($resource,$entry,$key);
                    if (null !== $value){
                        $result[] = $value;
                        $i++;
                    }
                    if ($i > $limit + $offset - 1) break; // Pas besoin d'aller plus loin...
                }
                ErrorHandler::stop();
            }else{
                $result = array();
            }
            $this->__searchEnd( $search );
            $this->count = count($result);
            return new Collection( $this, array_slice( $result, $offset, $limit ) );
        }
    
        /**
         * Retourne le nombre d'entités correspondant au filtre transmis
         *
         * @param string|AbstractFilter $filter
         * @return integer
         * @throws Exception
         */
        public function searchCount( $filter )
        {
            list( $resource, $search ) = $this->__searchBegin($filter, $this->ou);
    
            if ($search === false) {
                throw new Exception('searching: ' . $filter);
            }
            $this->count = ldap_count_entries($resource, $search);
            $this->__searchEnd($search);
            return $this->count;
        }
    
        /**
         * Recherche une liste d'entités correspondantes
         *
         * @param string|AbstractFilter $filter     Valeur de recherche
         * @param array                 $attributes Liste des attributs à retourner
         * @param string                $orderBy    Champ de référence pour le tri
         * @param integer               $limit      Nombre maximum d'occurences renvoyées (-1 = infini)
         * @param integer               $offset     Renvoi les entités à partir de $offset uniquement
         * @return Collection
         * @throws Exception
         */
        public function searchAttributes( $filter, array $attributes, $orderBy=null, $limit=self::DEFAULT_LIMIT, $offset=self::DEFAULT_OFFSET )
        {
            list($key) = Entity::getNodeParams($this->type);
            if ($limit < 0) $limit = 3999999999; // Limite maximum à 4 milliard...
            if ($offset < 0) $offset = 0; // Moins de zéro = impossible
    
            $searchAttributes = $attributes;
            if (! in_array($key, $searchAttributes)) $searchAttributes[] = $key;
            if (null !== $orderBy && ! in_array($orderBy, $searchAttributes)) $searchAttributes[] = $orderBy;
            list( $resource, $search ) = $this->__searchBegin($filter, $this->ou, $searchAttributes);
    
            ErrorHandler::start(E_WARNING);
            $this->count = ldap_count_entries($resource, $search);
            ErrorHandler::stop();
            if ($this->count > 0){
                if ($orderBy !== null && is_string($orderBy)) {
                    ErrorHandler::start(E_WARNING);
                    $isSorted = ldap_sort($resource, $search, $orderBy);
                    ErrorHandler::stop();
                    if ($isSorted === false) {
                        throw new Exception($this, 'sorting: ' . $orderBy);
                    }
                }
    
                $result = array();
                $i = 0;
                ErrorHandler::start(E_WARNING);
                for ($entry=ldap_first_entry($resource,$search); $entry; $entry=ldap_next_entry($resource,$entry)) {
                    list($id) = ldap_get_values_len($resource,$entry,$key);
                    $data = array();
                    foreach( $attributes as $attribute ){
                        $attrValue = ldap_get_values_len($resource,$entry,$attribute);
                        if (1 == $attrValue['count']) $attrValue = $attrValue[0];
                        $data[$attribute] = $attrValue;
                    }
                    $result[$id] = $data;
                    $i++;
                    if ($i > $limit + $offset - 1) break; // Pas besoin d'aller plus loin...
                }
                ErrorHandler::stop();
            }else{
                $result = array();
            }
            $this->__searchEnd( $search );
            return array_slice( $result, $offset, $limit );
        }
    
        /**
         *
         * @param string|AbstractFilter $filter     Filtre Ldap à appliquer
         * @param string[]              $ou         Liste des organisations dans lesquelles rechercher
         * @param string[]              $attributes Liste des attributs à retourner
         * @return array
         * @throws Exception
         */
        private function __searchBegin( $filter, array $ou=null, array $attributes=null )
        {
            /* Initialisation $basedn et $filter */
            if ($filter instanceof AbstractFilter) {
                $filter = $filter->toString();
            }
    
            if (is_string($ou)) $ou = array($ou);
            elseif (null === $ou) $ou = $this->ou;
    
            if (1 == count($ou)){
                $basedn = "ou=".$ou[0].",".$this->getLdap()->getBaseDn();
            }else{
                $basedn = $this->getLdap()->getBaseDn();
                $ouFilter = '(&(|';
                foreach( $ou as $ouItem ){
                    $ouFilter .= "(ou:dn:=$ouItem)";
                }
                $filter = $ouFilter.")$filter)";
            }
            $resource = $this->getLdap()->getResource();
            if (null === $attributes) $search = ldap_search($resource, $basedn, $filter);
            else $search = ldap_search($resource, $basedn, $filter, $attributes);
            if ($search === false) {
                throw new Exception('searching: ' . $filter);
            }
            return array( $resource, $search );
        }
    
        /**
         * Libère la mémoire du résultat de recherche
         *
         * @param resource $search
         */
        private function __searchEnd( $search )
        {
            ldap_free_result($search);
        }
    
        /**
         * Retourne un tableau d'attributs
         *
         * @param resource $resource
         * @param resource $entry
         * @return null|array
         */
        private function __getEntryAttributes( $resource, $entry )
        {
            if (!is_resource($resource)) {
                return null;
            }
    
            $berIdentifier = null;
    
            $name = ldap_first_attribute(
                $resource, $entry, $berIdentifier
            );
    
            $attributes         = array();
    
            while ($name) {
                ErrorHandler::start(E_WARNING);
                $data = ldap_get_values_len($resource, $entry, $name);
                ErrorHandler::stop();
    
                if (!$data) {
                    $data = array();
                }
    
                if (isset($data['count'])) {
                    unset($data['count']);
                }
    
                $attrName = strtolower($name);
                $attributes[$attrName] = $data;
    
                $name = ldap_next_attribute(
                    $resource, $entry,
                    $berIdentifier
                );
            }
            ksort($attributes, SORT_LOCALE_STRING);
            return $attributes;
        }
    
        /**
         * Retourne une entité du type correspondant au service courant
         *
         * 
         * @param string $id
         * @return Entity
         */
        public function get( $id )
        {
            list($key) = Entity::getNodeParams($this->type);
            return $this->getBy( $id, $key );
        }
    
        /**
         * Retourne une entité en fonction de la valeur d'un champ donné.
         * Attention : une seule entrée doit correspondre dans l'annuaire, faute de quoi une exception sera levée.
         *
         * @param mixed $value
         * @param string $by
         * @return Entity
         * @throws Exception
         */
        public function getBy( $value, $by )
        {
            if ('dn' == $by){
                $value = Dn::factory($value)->get(0);
                $by = key($value);
                $value = current($value);
            }
    
            list($tmp, $classname) = Entity::getNodeParams($this->type);
            list( $resource, $search ) = $this->__searchBegin( Filter::equals($by, $value), $this->ou, array('*','+') );
            $count = ldap_count_entries($resource, $search);
            switch( $count ){
                case 0:
                    $this->__searchEnd($search);
                    throw new Exception( 'Entité de type "'.$this->type.'" ayant "'.$by.'"="'.$value.'" non trouvée');
                case 1:
                    $entry = ldap_first_entry($resource,$search);
                    $attributes = $this->__getEntryAttributes($resource, $entry);
                    $dn = ldap_get_dn( $resource, $entry );
                    $attributes['dn'] = $dn;
                    $this->__searchEnd($search);
                    return new $classname( $this, \UnicaenLdap\Node::fromEntry($dn, $this->getLdap(), $attributes) );
                default:
                    $this->__searchEnd($search);
                    throw new Exception( 'Plusieurs entités de type "'.$this->type.'" ayant "'.$by.'"="'.$value.'" ont été trouvées');
            }
        }
    
        /**
         * Retourne un tableau d'entités de format array[ID] = Entite
         * 
         * @param string[] $ids   Tableau d'identifiants
         * @param string $orderBy Nom d'attribut à trier
         * @return Entity[]
         */
        public function getAll( $ids, $orderBy=null )
        {
            list($key) = Entity::getNodeParams($this->type);
            return $this->getAllBy($ids, $key, $orderBy);
        }
    
        /**
         * Retourne un tableau d'entités de format array[ID] = Entite
         *
         * @param array     $values     Tableau des valeurs à rechercher
         * @param string    $by         Nom de champ à rechercher
         * @param string    $orderBy    Nom d'attribut à trier
         * @return Entity[]
         */
        public function getAllBy( $values, $by, $orderBy=null )
        {
            if (! is_array($values)) $values = array($values);
            
            $data = array();
            $sortedData = array();
            foreach( $values as $val ){
                $valRes = $this->getBy( $val, $by );
                if (! empty($orderBy)){
                    $sortedData[$valRes->getId()] = $valRes->get($orderBy);
                }
                $data[$valRes->getId()] = $valRes;
            }
            if (! empty($orderBy)){
                asort($sortedData);
                foreach( $sortedData as $id => $val ){
                    $sortedData[$id] = $data[$id];
                }
                return $sortedData;
            }else{
                return $data;
            }
        }
    
        /**
         * Détermine si une entité existe ou non
         *
         * @param string $id Identifiant de l'entité
         * @return boolean
         */
        public function exists( $id )
        {
            list($key) = Entity::getNodeParams($this->type);
            return $this->existsBy($id, $key);
        }
    
        /**
         * Détermine si une entité existe ou non en fonction d'un champ déterminé
         * 
         * @param mixed $value Valeur de champ à rechercher
         * @param string $by Nom du champ à tester
         * @return boolean
         */
        public function existsBy( $value, $by )
        {
            if ('dn' == $by){
                $value = Dn::factory($value)->get(0);
                $by = key($value);
                $value = current($value);
            }
    
            return 1 == $this->searchCount( Filter::equals($by, $value) );
        }
    
        /**
         * Crée une nouvelle entité à partir de son futur identifiant et (si nécessaire) de son organizational unit (OU)
         * Attention, l'entité créée ne sera pas ajoutée au Ldap.
         * Elle doit d'abord être peuplée.
         * Une fois cela fait, il conviendra d'appeler la méthode attach() de la nouvelle entité.
         *
         * @param string $id Identifiant
         * @param string $ou Organizational Unit
         * @return Entity
         * @throws Exception
         */
        public function create( $id, $ou=null )
        {
            list($key, $classname) = Entity::getNodeParams($this->type);
            if (empty($ou)){
                if (count($this->ou) > 1){
                    throw new Exception('$ou non renseigné alors que le service couvre plusieurs Organizational Units (OU).');
                }
                $ou = $this->ou[0];
            }
            $dn = $key.'='.$id.',ou='.$ou.','.$this->getLdap()->getBaseDn();
            $classname = 'UnicaenLdap\\Entity\\'.$this->type;
            return new $classname( $this, $dn);
        }
    
        /**
         * Retourne le nombre d'occurences retournées par la dernière recherche
         *
         * @return integer
         */
        public function getLastCount()
        {
            return $this->count;
        }
    }