Allow a sequence of filters in LSobjects profile configurations
authorBenjamin Dauvergne <bdauvergne@entrouvert.com>
Sat, 13 Dec 2014 11:15:37 +0000 (12:15 +0100)
committerBenjamin Renard <brenard@easter-eggs.com>
Mon, 9 Feb 2015 10:16:20 +0000 (11:16 +0100)
It's now possible for example to define a profile on an LSobject whose
attribute would contain the DN of a group the user is member of instead
of directly the dn of the user. A possible configuation using this new feature:

  'LSprofile' => array(
    'admin' => array(
      'LSobjects' => array(
        'LSsupannGroupAdminByGroup' => array(
          'filters' => array(
            array(
              'basedn' => $basedn,
              'attr' => 'member',
              'attr_value' => '%{dn}',
              'LSobject' => 'LSsupannGroup',
            ),
            array(
              'basedn' => $basedn,
              'attr' => 'supannGroupeAdminDN',
              'attr_value' => '%{dn}',
              'LSobject' => 'LSsupannGroup',
            )
          ),
        ),
      ),
    ),
  )

Signed-off-by: Benjamin Renard <brenard@easter-eggs.com>
doc/conf/LSprofile.docbook
public_html/includes/class/class.LSsession.php

index 152f907..97683ee 100644 (file)
         'basedn' => [basedn de recherche],
         'params' => [configuration de la recherche]
       ),
+      [nom quelconque] => array (
+        'filters' => array(
+          array(
+            'LSobject' => [nom du LSobject],
+            'attr' => [nom de l'attribut clé],
+            'attr_value' => [format de la valeur de l'attribut clé],
+            // ou
+            'filter' => [format du filtre de recherche],
+
+            'basedn' => [basedn de recherche],
+            'params' => [configuration de la recherche]
+          ),
+        ),
+      ),
       ...
     )
   ),
@@ -151,6 +165,20 @@ objets pour lesquels l'utilisateur appartiendra au
         'basedn' => [basedn de recherche],
         'params' => [configuration de la recherche]
       ),
+      array (
+        'filters' => array(
+          array(
+            'LSobject' => [nom du LSobject],
+            'attr' => [nom de l'attribut clé],
+            'attr_value' => [format de la valeur de l'attribut clé],
+            // ou
+            'filter' => [format du filtre de recherche],
+
+            'basedn' => [basedn de recherche],
+            'params' => [configuration de la recherche]
+          ),
+        ),
+      ),
       ...
     )
   ),
@@ -159,19 +187,44 @@ objets pour lesquels l'utilisateur appartiendra au
 ...
 </programlisting> 
 <para>Explications&nbsp;: Dans la configuration d'un <emphasis>LSprofile</emphasis>,
-la valeur clé <emphasis>LSprofile</emphasis> signifie qu'on est dans un cas de la 
-délégation de droits sur des types d'LSobject. Dans ce tableau associatif, il 
+la valeur clé <emphasis>LSobjects</emphasis> signifie qu'on est dans un cas de la
+délégation de droits sur des types d'LSobject. Dans ce tableau associatif, il
 est possible de définir un ou plusieurs types de LSobject pour lesquels on délègue
-des droits. Dans ce tableau la valeur clé est le nom du LSobject et la valeur 
-associée est un tableau contenant la configuration permettant de dire quels sont
-les LSobjets de ce type concernés par la délégation.</para>
-<para>Cette configuration contient les paramètres d'une recherche dans l'annuaire
+des droits via des recherches simples ou enchaînées. Le fonctionnement simple
+consiste à partir de l'objet de l'utilisateur et à générer un filtre de
+recherche sur un type de LSobject. Le fonctionnement enchainée consiste à faire
+un première recherche à partir de l'objet de l'utilisateur puis à recommencer à
+partir des objets trouvés en construisant une liste de filtres de recherche
+pour chaque objet qui seront combinés via l'opérateur booléen
+<emphasis>ou</emphasis>.</para>
+
+<para>Pour configurer une délégation de type simple on mettra le nom du
+LSobject dans la clé du tableau et dans la valeur un tableau définissant la
+recherche. Il est possible de ne pas utiliser la clé du tableau comme nom du
+LSobject grâce à la clé de configuration
+<emphasis>LSobject</emphasis>.</para>
+
+<para>Pour configurer une délégation de type enchaîné on pourra utiliser
+n'importe quelle valeur unique pour la clé du tableau et pour la valeur un
+tableau contenant une unique clé <emphasis>filters</emphasis>. La valeur
+associée à cette clé est celle d'une délégation de type simple où la clé
+<emphasis>LSobject</emphasis> est devenue obligatoire.</para>
+
+<para>Cette configuration contient les paramètres d'une ou plusieurs recherches dans l'annuaire
 en considérant que l'utilisateur connecté aura les droits du LSprofile sur les
 objets retournés. Les paramètres de la recherche sont&nbsp;:
 
 <variablelist>
 
 <varlistentry>
+  <term>LSobject</term>
+  <listitem>
+    <simpara>C'est le nom du LSobject recherché.
+    <emphasis>(Paramètre facultatif pour une délégation de type simple)</emphasis></simpara>
+  </listitem>
+</varlistentry>
+
+<varlistentry>
   <term>attr</term>
   <listitem>
     <simpara>Nom de l'attribut des LSobjets contenant une valeur clé qui 
index adab3f1..4703a2d 100644 (file)
@@ -1485,6 +1485,111 @@ class LSsession {
   }
   
   /**
+   * Prend un tableau de LSobject et le réduit en utilisant un filtre de
+   * recherche sur un autre type de LSobject.
+   *
+   * Si une erreur est présente dans le tableau de définition du filtre, un
+   * tableau vide est renvoyé.
+   *
+   * @param[in] string $LSobject le type LSobject par défaut
+   * @param[in] array $set tableau de LSobject
+   * @param[in] array $filter_def définition du filtre de recherche pour la réduction
+   * @param[in] string $basend basedn pour la recherche, null par défaut
+   *
+   * @retval array le nouveau tableau de LSobject
+   */
+  private static function reduceLdapSet($LSobject, $set, $filter_def, $basedn=null) {
+    if (empty($set)) {
+      return array();
+    }
+
+    if (! isset($filter_def['filter']) &&
+          (! isset($filter_def['attr']) ||
+           ! isset($filter_def['attr_value']))) {
+      LSdebug("Filtre de profil LSobject invalide " . var_export($filter_def, true));
+      return array();
+    }
+
+    LSdebug('LSsession :: reducing set of');
+    foreach ($set as $object) {
+      LSdebug('LSsession :: -> ' . $object -> getDn());
+    }
+
+    $LSobject = isset($filter_def['LSObject']) ? $filter_def['LSobject'] : $LSobject;
+    LSdebug('LSobject :: ' . $LSobject);
+    $filters = array();
+    foreach ($set as $object) {
+      if (isset($filter_def['filter'])) {
+        $filters[] = $object -> getFData($filter_def['filter']);
+      }
+      else {
+        $value = $object -> getFData($filter_def['attr_value']);
+        $filters[] = Net_LDAP2_Filter::create($filter_def['attr'], 'equals', $value);
+      }
+    }
+    $filter = LSldap::combineFilters('or', $filters);
+    $params = array(
+      'basedn' => isset($filter_def['basedn']) ? $filter_def['basedn'] : $basedn,
+      'filter' => $filter,
+    );
+    if (isset($filter_def['params']) && is_array($filter_def['params'])) {
+      $params = array_merge($filter_def['params'],$params);
+    }
+    $LSsearch = new LSsearch($LSobject,'LSsession :: loadLSprofiles',$params,true);
+    $LSsearch -> run(false);
+
+    $set = $LSsearch -> listObjects();
+    LSdebug('LSsession :: reduced set to');
+    foreach ($set as $object) {
+      LSdebug('LSsession :: -> ' . $object -> getDn());
+    }
+    return $set;
+  }
+
+  /**
+   * Charge les droits LS de l'utilisateur : uniquement du type LSobjects
+   *
+   * @param[in] string $
+   *
+   * @retval void
+   */
+  private static function loadLSprofilesLSobjects($profile, $LSobject, $listInfos) {
+    if (! self :: loadLSclass('LSsearch')) {
+      LSdebug('Impossible de charger la classe LSsearch');
+      return;
+    }
+    # we are gonna grow a set of objects progressively, we start from the user
+    $set = array(self :: getLSuserObject());
+    $basedn = isset($listInfos['basedn']) ? $listInfos['basedn'] : null;
+    $LSobject = isset($listInfos['LSobject']) ? $listInfos['LSobject'] : $LSobject;
+
+    if (isset($listInfos['filters']) && is_array($listInfos['filters'])) {
+      foreach ($listInfos['filters'] as $filter_def) {
+        $set = self :: reduceLdapSet($LSobject, $set, $filter_def, $basedn);
+      }
+    }
+    if (isset($listInfos['filter']) || (isset($listInfos['attr']) && isset($listInfos['attr_value']))) {
+      # support legacy profile definition
+      $set = self :: reduceLdapSet($LSobject, $set, $listInfos, $basedn);
+    }
+
+    $DNs = [];
+    foreach ($set as $object) {
+      $DNs[] = $object -> getDn();
+    }
+    if (!is_array(self :: $LSprofiles[$profile])) {
+      self :: $LSprofiles[$profile]=$DNs;
+    }
+    else {
+      foreach($DNs as $dn) {
+        if (!in_array($dn,self :: $LSprofiles[$profile])) {
+          self :: $LSprofiles[$profile][] = $dn;
+        }
+      }
+    }
+  }
+
+  /**
    * Charge les droits LS de l'utilisateur
    * 
    * @retval boolean True si le chargement Ã  réussi, false sinon.
@@ -1501,38 +1606,8 @@ class LSsession {
             if ($topDn == 'LSobjects') {
               if (is_array($rightsInfos)) {
                 foreach ($rightsInfos as $LSobject => $listInfos) {
-                  if (self :: loadLSclass('LSsearch')) {
-                    if (isset($listInfos['filter'])) {
-                      $filter = self :: getLSuserObject() -> getFData($listInfos['filter']);
-                    }
-                    else {
-                      $filter = '('.$listInfos['attr'].'='.self :: getLSuserObject() -> getFData($listInfos['attr_value']).')';
-                    }
-                    
-                    $params = array (
-                      'basedn' => (isset($listInfos['basedn'])?$listInfos['basedn']:null),
-                      'filter' => $filter
-                    );
-                    
-                    if (isset($listInfos['params']) && is_array($listInfos['params'])) {
-                      $params = array_merge($listInfos['params'],$params);
-                    }
-                    
-                    $LSsearch = new LSsearch($LSobject,'LSsession :: loadLSprofiles',$params,true);
-                    $LSsearch -> run(false);
-                    
-                    $DNs = $LSsearch -> listObjectsDn();
-                    if (!is_array(self :: $LSprofiles[$profile])) {
-                      self :: $LSprofiles[$profile]=$DNs;
-                    }
-                    else {
-                      foreach($DNs as $dn) {
-                        if (!in_array($dn,self :: $LSprofiles[$profile])) {
-                          self :: $LSprofiles[$profile][] = $dn;
-                        }
-                      }
-                    }
-                  }
+                  LSdebug('loading LSprofile ' . $profile . ' for LSobject ' . $LSobject . ' with params ' . var_export($listInfos, true));
+                  self :: loadLSprofilesLSobjects($profile, $LSobject, $listInfos);
                 }
               }
               else {