]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/LdapCommon/extlib/Net/LDAP2/Schema.php
Introduced common_location_shared() to check if location sharing is always,
[quix0rs-gnu-social.git] / plugins / LdapCommon / extlib / Net / LDAP2 / Schema.php
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4: */
3 /**
4 * File containing the Net_LDAP2_Schema interface class.
5 *
6 * PHP version 5
7 *
8 * @category  Net
9 * @package   Net_LDAP2
10 * @author    Jan Wagner <wagner@netsols.de>
11 * @author    Benedikt Hallinger <beni@php.net>
12 * @copyright 2009 Jan Wagner, Benedikt Hallinger
13 * @license   http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
14 * @version   SVN: $Id: Schema.php 296515 2010-03-22 14:46:41Z beni $
15 * @link      http://pear.php.net/package/Net_LDAP2/
16 * @todo see the comment at the end of the file
17 */
18
19 /**
20 * Includes
21 */
22 require_once 'PEAR.php';
23
24 /**
25 * Syntax definitions
26 *
27 * Please don't forget to add binary attributes to isBinary() below
28 * to support proper value fetching from Net_LDAP2_Entry
29 */
30 define('NET_LDAP2_SYNTAX_BOOLEAN',            '1.3.6.1.4.1.1466.115.121.1.7');
31 define('NET_LDAP2_SYNTAX_DIRECTORY_STRING',   '1.3.6.1.4.1.1466.115.121.1.15');
32 define('NET_LDAP2_SYNTAX_DISTINGUISHED_NAME', '1.3.6.1.4.1.1466.115.121.1.12');
33 define('NET_LDAP2_SYNTAX_INTEGER',            '1.3.6.1.4.1.1466.115.121.1.27');
34 define('NET_LDAP2_SYNTAX_JPEG',               '1.3.6.1.4.1.1466.115.121.1.28');
35 define('NET_LDAP2_SYNTAX_NUMERIC_STRING',     '1.3.6.1.4.1.1466.115.121.1.36');
36 define('NET_LDAP2_SYNTAX_OID',                '1.3.6.1.4.1.1466.115.121.1.38');
37 define('NET_LDAP2_SYNTAX_OCTET_STRING',       '1.3.6.1.4.1.1466.115.121.1.40');
38
39 /**
40 * Load an LDAP Schema and provide information
41 *
42 * This class takes a Subschema entry, parses this information
43 * and makes it available in an array. Most of the code has been
44 * inspired by perl-ldap( http://perl-ldap.sourceforge.net).
45 * You will find portions of their implementation in here.
46 *
47 * @category Net
48 * @package  Net_LDAP2
49 * @author   Jan Wagner <wagner@netsols.de>
50 * @author   Benedikt Hallinger <beni@php.net>
51 * @license  http://www.gnu.org/copyleft/lesser.html LGPL
52 * @link     http://pear.php.net/package/Net_LDAP22/
53 */
54 class Net_LDAP2_Schema extends PEAR
55 {
56     /**
57     * Map of entry types to ldap attributes of subschema entry
58     *
59     * @access public
60     * @var array
61     */
62     public $types = array(
63             'attribute'        => 'attributeTypes',
64             'ditcontentrule'   => 'dITContentRules',
65             'ditstructurerule' => 'dITStructureRules',
66             'matchingrule'     => 'matchingRules',
67             'matchingruleuse'  => 'matchingRuleUse',
68             'nameform'         => 'nameForms',
69             'objectclass'      => 'objectClasses',
70             'syntax'           => 'ldapSyntaxes'
71         );
72
73     /**
74     * Array of entries belonging to this type
75     *
76     * @access protected
77     * @var array
78     */
79     protected $_attributeTypes    = array();
80     protected $_matchingRules     = array();
81     protected $_matchingRuleUse   = array();
82     protected $_ldapSyntaxes      = array();
83     protected $_objectClasses     = array();
84     protected $_dITContentRules   = array();
85     protected $_dITStructureRules = array();
86     protected $_nameForms         = array();
87
88
89     /**
90     * hash of all fetched oids
91     *
92     * @access protected
93     * @var array
94     */
95     protected $_oids = array();
96
97     /**
98     * Tells if the schema is initialized
99     *
100     * @access protected
101     * @var boolean
102     * @see parse(), get()
103     */
104     protected $_initialized = false;
105
106
107     /**
108     * Constructor of the class
109     *
110     * @access protected
111     */
112     protected function __construct()
113     {
114         $this->PEAR('Net_LDAP2_Error'); // default error class
115     }
116
117     /**
118     * Fetch the Schema from an LDAP connection
119     *
120     * @param Net_LDAP2 $ldap LDAP connection
121     * @param string    $dn   (optional) Subschema entry dn
122     *
123     * @access public
124     * @return Net_LDAP2_Schema|NET_LDAP2_Error
125     */
126     public function fetch($ldap, $dn = null)
127     {
128         if (!$ldap instanceof Net_LDAP2) {
129             return PEAR::raiseError("Unable to fetch Schema: Parameter \$ldap must be a Net_LDAP2 object!");
130         }
131
132         $schema_o = new Net_LDAP2_Schema();
133
134         if (is_null($dn)) {
135             // get the subschema entry via root dse
136             $dse = $ldap->rootDSE(array('subschemaSubentry'));
137             if (false == Net_LDAP2::isError($dse)) {
138                 $base = $dse->getValue('subschemaSubentry', 'single');
139                 if (!Net_LDAP2::isError($base)) {
140                     $dn = $base;
141                 }
142             }
143         }
144
145         // Support for buggy LDAP servers (e.g. Siemens DirX 6.x) that incorrectly
146         // call this entry subSchemaSubentry instead of subschemaSubentry.
147         // Note the correct case/spelling as per RFC 2251.
148         if (is_null($dn)) {
149             // get the subschema entry via root dse
150             $dse = $ldap->rootDSE(array('subSchemaSubentry'));
151             if (false == Net_LDAP2::isError($dse)) {
152                 $base = $dse->getValue('subSchemaSubentry', 'single');
153                 if (!Net_LDAP2::isError($base)) {
154                     $dn = $base;
155                 }
156             }
157         }
158
159         // Final fallback case where there is no subschemaSubentry attribute
160         // in the root DSE (this is a bug for an LDAP v3 server so report this
161         // to your LDAP vendor if you get this far).
162         if (is_null($dn)) {
163             $dn = 'cn=Subschema';
164         }
165
166         // fetch the subschema entry
167         $result = $ldap->search($dn, '(objectClass=*)',
168                                 array('attributes' => array_values($schema_o->types),
169                                         'scope' => 'base'));
170         if (Net_LDAP2::isError($result)) {
171             return PEAR::raiseError('Could not fetch Subschema entry: '.$result->getMessage());
172         }
173
174         $entry = $result->shiftEntry();
175         if (!$entry instanceof Net_LDAP2_Entry) {
176             if ($entry instanceof Net_LDAP2_Error) {
177                 return PEAR::raiseError('Could not fetch Subschema entry: '.$entry->getMessage());
178             } else {
179                 return PEAR::raiseError('Could not fetch Subschema entry (search returned '.$result->count().' entries. Check parameter \'basedn\')');
180             }
181         }
182
183         $schema_o->parse($entry);
184         return $schema_o;
185     }
186
187     /**
188     * Return a hash of entries for the given type
189     *
190     * Returns a hash of entry for the givene type. Types may be:
191     * objectclasses, attributes, ditcontentrules, ditstructurerules, matchingrules,
192     * matchingruleuses, nameforms, syntaxes
193     *
194     * @param string $type Type to fetch
195     *
196     * @access public
197     * @return array|Net_LDAP2_Error Array or Net_LDAP2_Error
198     */
199     public function &getAll($type)
200     {
201         $map = array('objectclasses'     => &$this->_objectClasses,
202                      'attributes'        => &$this->_attributeTypes,
203                      'ditcontentrules'   => &$this->_dITContentRules,
204                      'ditstructurerules' => &$this->_dITStructureRules,
205                      'matchingrules'     => &$this->_matchingRules,
206                      'matchingruleuses'  => &$this->_matchingRuleUse,
207                      'nameforms'         => &$this->_nameForms,
208                      'syntaxes'          => &$this->_ldapSyntaxes );
209
210         $key = strtolower($type);
211         $ret = ((key_exists($key, $map)) ? $map[$key] : PEAR::raiseError("Unknown type $type"));
212         return $ret;
213     }
214
215     /**
216     * Return a specific entry
217     *
218     * @param string $type Type of name
219     * @param string $name Name or OID to fetch
220     *
221     * @access public
222     * @return mixed Entry or Net_LDAP2_Error
223     */
224     public function &get($type, $name)
225     {
226         if ($this->_initialized) {
227             $type = strtolower($type);
228             if (false == key_exists($type, $this->types)) {
229                 return PEAR::raiseError("No such type $type");
230             }
231
232             $name     = strtolower($name);
233             $type_var = &$this->{'_' . $this->types[$type]};
234
235             if (key_exists($name, $type_var)) {
236                 return $type_var[$name];
237             } elseif (key_exists($name, $this->_oids) && $this->_oids[$name]['type'] == $type) {
238                 return $this->_oids[$name];
239             } else {
240                 return PEAR::raiseError("Could not find $type $name");
241             }
242         } else {
243             $return = null;
244             return $return;
245         }
246     }
247
248
249     /**
250     * Fetches attributes that MAY be present in the given objectclass
251     *
252     * @param string $oc Name or OID of objectclass
253     *
254     * @access public
255     * @return array|Net_LDAP2_Error Array with attributes or Net_LDAP2_Error
256     */
257     public function may($oc)
258     {
259         return $this->_getAttr($oc, 'may');
260     }
261
262     /**
263     * Fetches attributes that MUST be present in the given objectclass
264     *
265     * @param string $oc Name or OID of objectclass
266     *
267     * @access public
268     * @return array|Net_LDAP2_Error Array with attributes or Net_LDAP2_Error
269     */
270     public function must($oc)
271     {
272         return $this->_getAttr($oc, 'must');
273     }
274
275     /**
276     * Fetches the given attribute from the given objectclass
277     *
278     * @param string $oc   Name or OID of objectclass
279     * @param string $attr Name of attribute to fetch
280     *
281     * @access protected
282     * @return array|Net_LDAP2_Error The attribute or Net_LDAP2_Error
283     */
284     protected function _getAttr($oc, $attr)
285     {
286         $oc = strtolower($oc);
287         if (key_exists($oc, $this->_objectClasses) && key_exists($attr, $this->_objectClasses[$oc])) {
288             return $this->_objectClasses[$oc][$attr];
289         } elseif (key_exists($oc, $this->_oids) &&
290                 $this->_oids[$oc]['type'] == 'objectclass' &&
291                 key_exists($attr, $this->_oids[$oc])) {
292             return $this->_oids[$oc][$attr];
293         } else {
294             return PEAR::raiseError("Could not find $attr attributes for $oc ");
295         }
296     }
297
298     /**
299     * Returns the name(s) of the immediate superclass(es)
300     *
301     * @param string $oc Name or OID of objectclass
302     *
303     * @access public
304     * @return array|Net_LDAP2_Error  Array of names or Net_LDAP2_Error
305     */
306     public function superclass($oc)
307     {
308         $o = $this->get('objectclass', $oc);
309         if (Net_LDAP2::isError($o)) {
310             return $o;
311         }
312         return (key_exists('sup', $o) ? $o['sup'] : array());
313     }
314
315     /**
316     * Parses the schema of the given Subschema entry
317     *
318     * @param Net_LDAP2_Entry &$entry Subschema entry
319     *
320     * @access public
321     * @return void
322     */
323     public function parse(&$entry)
324     {
325         foreach ($this->types as $type => $attr) {
326             // initialize map type to entry
327             $type_var          = '_' . $attr;
328             $this->{$type_var} = array();
329
330             // get values for this type
331             if ($entry->exists($attr)) {
332                 $values = $entry->getValue($attr);
333                 if (is_array($values)) {
334                     foreach ($values as $value) {
335
336                         unset($schema_entry); // this was a real mess without it
337
338                         // get the schema entry
339                         $schema_entry = $this->_parse_entry($value);
340
341                         // set the type
342                         $schema_entry['type'] = $type;
343
344                         // save a ref in $_oids
345                         $this->_oids[$schema_entry['oid']] = &$schema_entry;
346
347                         // save refs for all names in type map
348                         $names = $schema_entry['aliases'];
349                         array_push($names, $schema_entry['name']);
350                         foreach ($names as $name) {
351                             $this->{$type_var}[strtolower($name)] = &$schema_entry;
352                         }
353                     }
354                 }
355             }
356         }
357         $this->_initialized = true;
358     }
359
360     /**
361     * Parses an attribute value into a schema entry
362     *
363     * @param string $value Attribute value
364     *
365     * @access protected
366     * @return array|false Schema entry array or false
367     */
368     protected function &_parse_entry($value)
369     {
370         // tokens that have no value associated
371         $noValue = array('single-value',
372                          'obsolete',
373                          'collective',
374                          'no-user-modification',
375                          'abstract',
376                          'structural',
377                          'auxiliary');
378
379         // tokens that can have multiple values
380         $multiValue = array('must', 'may', 'sup');
381
382         $schema_entry = array('aliases' => array()); // initilization
383
384         $tokens = $this->_tokenize($value); // get an array of tokens
385
386         // remove surrounding brackets
387         if ($tokens[0] == '(') array_shift($tokens);
388         if ($tokens[count($tokens) - 1] == ')') array_pop($tokens); // -1 doesnt work on arrays :-(
389
390         $schema_entry['oid'] = array_shift($tokens); // first token is the oid
391
392         // cycle over the tokens until none are left
393         while (count($tokens) > 0) {
394             $token = strtolower(array_shift($tokens));
395             if (in_array($token, $noValue)) {
396                 $schema_entry[$token] = 1; // single value token
397             } else {
398                 // this one follows a string or a list if it is multivalued
399                 if (($schema_entry[$token] = array_shift($tokens)) == '(') {
400                     // this creates the list of values and cycles through the tokens
401                     // until the end of the list is reached ')'
402                     $schema_entry[$token] = array();
403                     while ($tmp = array_shift($tokens)) {
404                         if ($tmp == ')') break;
405                         if ($tmp != '$') array_push($schema_entry[$token], $tmp);
406                     }
407                 }
408                 // create a array if the value should be multivalued but was not
409                 if (in_array($token, $multiValue) && !is_array($schema_entry[$token])) {
410                     $schema_entry[$token] = array($schema_entry[$token]);
411                 }
412             }
413         }
414         // get max length from syntax
415         if (key_exists('syntax', $schema_entry)) {
416             if (preg_match('/{(\d+)}/', $schema_entry['syntax'], $matches)) {
417                 $schema_entry['max_length'] = $matches[1];
418             }
419         }
420         // force a name
421         if (empty($schema_entry['name'])) {
422             $schema_entry['name'] = $schema_entry['oid'];
423         }
424         // make one name the default and put the other ones into aliases
425         if (is_array($schema_entry['name'])) {
426             $aliases                 = $schema_entry['name'];
427             $schema_entry['name']    = array_shift($aliases);
428             $schema_entry['aliases'] = $aliases;
429         }
430         return $schema_entry;
431     }
432
433     /**
434     * Tokenizes the given value into an array of tokens
435     *
436     * @param string $value String to parse
437     *
438     * @access protected
439     * @return array Array of tokens
440     */
441     protected function _tokenize($value)
442     {
443         $tokens  = array();       // array of tokens
444         $matches = array();       // matches[0] full pattern match, [1,2,3] subpatterns
445
446         // this one is taken from perl-ldap, modified for php
447         $pattern = "/\s* (?:([()]) | ([^'\s()]+) | '((?:[^']+|'[^\s)])*)') \s*/x";
448
449         /**
450          * This one matches one big pattern wherin only one of the three subpatterns matched
451          * We are interested in the subpatterns that matched. If it matched its value will be
452          * non-empty and so it is a token. Tokens may be round brackets, a string, or a string
453          * enclosed by '
454          */
455         preg_match_all($pattern, $value, $matches);
456
457         for ($i = 0; $i < count($matches[0]); $i++) {     // number of tokens (full pattern match)
458             for ($j = 1; $j < 4; $j++) {                  // each subpattern
459                 if (null != trim($matches[$j][$i])) {     // pattern match in this subpattern
460                     $tokens[$i] = trim($matches[$j][$i]); // this is the token
461                 }
462             }
463         }
464         return $tokens;
465     }
466
467     /**
468     * Returns wether a attribute syntax is binary or not
469     *
470     * This method gets used by Net_LDAP2_Entry to decide which
471     * PHP function needs to be used to fetch the value in the
472     * proper format (e.g. binary or string)
473     *
474     * @param string $attribute The name of the attribute (eg.: 'sn')
475     *
476     * @access public
477     * @return boolean
478     */
479     public function isBinary($attribute)
480     {
481         $return = false; // default to false
482
483         // This list contains all syntax that should be treaten as
484         // containing binary values
485         // The Syntax Definitons go into constants at the top of this page
486         $syntax_binary = array(
487                            NET_LDAP2_SYNTAX_OCTET_STRING,
488                            NET_LDAP2_SYNTAX_JPEG
489                          );
490
491         // Check Syntax
492         $attr_s = $this->get('attribute', $attribute);
493         if (Net_LDAP2::isError($attr_s)) {
494             // Attribute not found in schema
495             $return = false; // consider attr not binary
496         } elseif (isset($attr_s['syntax']) && in_array($attr_s['syntax'], $syntax_binary)) {
497             // Syntax is defined as binary in schema
498             $return = true;
499         } else {
500             // Syntax not defined as binary, or not found
501             // if attribute is a subtype, check superior attribute syntaxes
502             if (isset($attr_s['sup'])) {
503                 foreach ($attr_s['sup'] as $superattr) {
504                     $return = $this->isBinary($superattr);
505                     if ($return) {
506                         break; // stop checking parents since we are binary
507                     }
508                 }
509             }
510         }
511
512         return $return;
513     }
514
515     /**
516     * See if an schema element exists
517     *
518     * @param string $type Type of name, see get()
519     * @param string $name Name or OID
520     *
521     * @return boolean
522     */
523     public function exists($type, $name)
524     {
525         $entry = $this->get($type, $name);
526         if ($entry instanceof Net_LDAP2_ERROR) {
527                 return false;
528         } else {
529             return true;
530         }
531     }
532
533     /**
534     * See if an attribute is defined in the schema
535     *
536     * @param string $attribute Name or OID of the attribute
537     * @return boolean
538     */
539     public function attributeExists($attribute)
540     {
541         return $this->exists('attribute', $attribute);
542     }
543
544     /**
545     * See if an objectClass is defined in the schema
546     *
547     * @param string $ocl Name or OID of the objectClass
548     * @return boolean
549     */
550     public function objectClassExists($ocl)
551     {
552         return $this->exists('objectclass', $ocl);
553     }
554
555
556     /**
557     * See to which ObjectClasses an attribute is assigned
558     *
559     * The objectclasses are sorted into the keys 'may' and 'must'.
560     *
561     * @param string $attribute Name or OID of the attribute
562     *
563     * @return array|Net_LDAP2_Error Associative array with OCL names or Error
564     */
565     public function getAssignedOCLs($attribute)
566     {
567         $may  = array();
568         $must = array();
569
570         // Test if the attribute type is defined in the schema,
571         // if so, retrieve real name for lookups
572         $attr_entry = $this->get('attribute', $attribute);
573         if ($attr_entry instanceof Net_LDAP2_ERROR) {
574             return PEAR::raiseError("Attribute $attribute not defined in schema: ".$attr_entry->getMessage());
575         } else {
576             $attribute = $attr_entry['name'];
577         }
578
579
580         // We need to get all defined OCLs for this.
581         $ocls = $this->getAll('objectclasses');
582         foreach ($ocls as $ocl => $ocl_data) {
583             // Fetch the may and must attrs and see if our searched attr is contained.
584             // If so, record it in the corresponding array.
585             $ocl_may_attrs  = $this->may($ocl);
586             $ocl_must_attrs = $this->must($ocl);
587             if (is_array($ocl_may_attrs) && in_array($attribute, $ocl_may_attrs)) {
588                 array_push($may, $ocl_data['name']);
589             }
590             if (is_array($ocl_must_attrs) && in_array($attribute, $ocl_must_attrs)) {
591                 array_push($must, $ocl_data['name']);
592             }
593         }
594
595         return array('may' => $may, 'must' => $must);
596     }
597
598     /**
599     * See if an attribute is available in a set of objectClasses
600     *
601     * @param string $attribute Attribute name or OID
602     * @param array $ocls       Names of OCLs to check for
603     *
604     * @return boolean TRUE, if the attribute is defined for at least one of the OCLs
605     */
606     public function checkAttribute($attribute, $ocls)
607     {
608         foreach ($ocls as $ocl) {
609             $ocl_entry = $this->get('objectclass', $ocl);
610             $ocl_may_attrs  = $this->may($ocl);
611             $ocl_must_attrs = $this->must($ocl);
612             if (is_array($ocl_may_attrs) && in_array($attribute, $ocl_may_attrs)) {
613                 return true;
614             }
615             if (is_array($ocl_must_attrs) && in_array($attribute, $ocl_must_attrs)) {
616                 return true;
617             }
618         }
619         return false; // no ocl for the ocls found.
620     }
621 }
622 ?>