Overview

Packages

  • Ldap

Classes

  • Horde_Ldap
  • Horde_Ldap_Entry
  • Horde_Ldap_Exception
  • Horde_Ldap_Filter
  • Horde_Ldap_Ldif
  • Horde_Ldap_RootDse
  • Horde_Ldap_Schema
  • Horde_Ldap_Search
  • Horde_Ldap_Util
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Load an LDAP Schema and provide information
  4:  *
  5:  * This class takes a Subschema entry, parses this information
  6:  * and makes it available in an array. Most of the code has been
  7:  * inspired by perl-ldap( http://perl-ldap.sourceforge.net).
  8:  * You will find portions of their implementation in here.
  9:  *
 10:  * Copyright 2009 Jan Wagner, Benedikt Hallinger
 11:  * Copyright 2010-2012 Horde LLC (http://www.horde.org/)
 12:  *
 13:  * @category  Horde
 14:  * @package   Ldap
 15:  * @author    Jan Wagner <wagner@netsols.de>
 16:  * @author    Benedikt Hallinger <beni@php.net>
 17:  * @author    Jan Schneider <jan@horde.org>
 18:  * @license   http://www.gnu.org/licenses/lgpl-3.0.html LGPL-3.0
 19:  */
 20: class Horde_Ldap_Schema
 21: {
 22:     /**
 23:      * Syntax definitions.
 24:      *
 25:      * Please don't forget to add binary attributes to isBinary() below to
 26:      * support proper value fetching from Horde_Ldap_Entry.
 27:      */
 28:     const SYNTAX_BOOLEAN =            '1.3.6.1.4.1.1466.115.121.1.7';
 29:     const SYNTAX_DIRECTORY_STRING =   '1.3.6.1.4.1.1466.115.121.1.15';
 30:     const SYNTAX_DISTINGUISHED_NAME = '1.3.6.1.4.1.1466.115.121.1.12';
 31:     const SYNTAX_INTEGER =            '1.3.6.1.4.1.1466.115.121.1.27';
 32:     const SYNTAX_JPEG =               '1.3.6.1.4.1.1466.115.121.1.28';
 33:     const SYNTAX_NUMERIC_STRING =     '1.3.6.1.4.1.1466.115.121.1.36';
 34:     const SYNTAX_OID =                '1.3.6.1.4.1.1466.115.121.1.38';
 35:     const SYNTAX_OCTET_STRING =       '1.3.6.1.4.1.1466.115.121.1.40';
 36: 
 37:     /**
 38:      * Map of entry types to LDAP attributes of subschema entry.
 39:      *
 40:      * @var array
 41:      */
 42:     public $types = array(
 43:         'attribute'        => 'attributeTypes',
 44:         'ditcontentrule'   => 'dITContentRules',
 45:         'ditstructurerule' => 'dITStructureRules',
 46:         'matchingrule'     => 'matchingRules',
 47:         'matchingruleuse'  => 'matchingRuleUse',
 48:         'nameform'         => 'nameForms',
 49:         'objectclass'      => 'objectClasses',
 50:         'syntax'           => 'ldapSyntaxes' );
 51: 
 52:     /**
 53:      * Array of entries belonging to this type
 54:      *
 55:      * @var array
 56:      */
 57:     protected $_attributeTypes    = array();
 58:     protected $_matchingRules     = array();
 59:     protected $_matchingRuleUse   = array();
 60:     protected $_ldapSyntaxes      = array();
 61:     protected $_objectClasses     = array();
 62:     protected $_dITContentRules   = array();
 63:     protected $_dITStructureRules = array();
 64:     protected $_nameForms         = array();
 65: 
 66: 
 67:     /**
 68:      * Hash of all fetched OIDs.
 69:      *
 70:      * @var array
 71:      */
 72:     protected $_oids = array();
 73: 
 74:     /**
 75:      * Whether the schema is initialized.
 76:      *
 77:      * @see parse(), get()
 78:      * @var boolean
 79:      */
 80:     protected $_initialized = false;
 81: 
 82:     /**
 83:      * Constructor.
 84:      *
 85:      * Fetches the Schema from an LDAP connection.
 86:      *
 87:      * @param Horde_Ldap $ldap LDAP connection.
 88:      * @param string     $dn   Subschema entry DN.
 89:      *
 90:      * @throws Horde_Ldap_Exception
 91:      */
 92:     public function __construct(Horde_Ldap $ldap, $dn = null)
 93:     {
 94:         if (is_null($dn)) {
 95:             // Get the subschema entry via rootDSE.
 96:             $dse = $ldap->rootDSE(array('subschemaSubentry'));
 97:             $base = $dse->getValue('subschemaSubentry', 'single');
 98:             $dn = $base;
 99:         }
100: 
101:         // Support for buggy LDAP servers (e.g. Siemens DirX 6.x) that
102:         // incorrectly call this entry subSchemaSubentry instead of
103:         // subschemaSubentry. Note the correct case/spelling as per RFC 2251.
104:         if (is_null($dn)) {
105:             // Get the subschema entry via rootDSE.
106:             $dse = $ldap->rootDSE(array('subSchemaSubentry'));
107:             $base = $dse->getValue('subSchemaSubentry', 'single');
108:             $dn = $base;
109:         }
110: 
111:         // Final fallback in case there is no subschemaSubentry attribute in
112:         // the root DSE (this is a bug for an LDAPv3 server so report this to
113:         // your LDAP vendor if you get this far).
114:         if (is_null($dn)) {
115:             $dn = 'cn=Subschema';
116:         }
117: 
118:         // Fetch the subschema entry.
119:         $result = $ldap->search($dn, '(objectClass=*)',
120:                                 array('attributes' => array_values($this->types),
121:                                       'scope' => 'base'));
122:         $entry = $result->shiftEntry();
123:         if (!($entry instanceof Horde_Ldap_Entry)) {
124:             throw new Horde_Ldap_Exception('Could not fetch Subschema entry');
125:         }
126: 
127:         $this->parse($entry);
128:     }
129: 
130:     /**
131:      * Returns a hash of entries for the given type.
132:      *
133:      * Types may be: objectclasses, attributes, ditcontentrules,
134:      * ditstructurerules, matchingrules, matchingruleuses, nameforms, syntaxes.
135:      *
136:      * @param string $type Type to fetch.
137:      *
138:      * @return array
139:      * @throws Horde_Ldap_Exception
140:      */
141:     public function getAll($type)
142:     {
143:         $map = array('objectclasses'     => $this->_objectClasses,
144:                      'attributes'        => $this->_attributeTypes,
145:                      'ditcontentrules'   => $this->_dITContentRules,
146:                      'ditstructurerules' => $this->_dITStructureRules,
147:                      'matchingrules'     => $this->_matchingRules,
148:                      'matchingruleuses'  => $this->_matchingRuleUse,
149:                      'nameforms'         => $this->_nameForms,
150:                      'syntaxes'          => $this->_ldapSyntaxes);
151: 
152:         $key = Horde_String::lower($type);
153:         if (!isset($map[$key])) {
154:             throw new Horde_Ldap_Exception("Unknown type $type");
155:         }
156: 
157:         return $map[$key];
158:     }
159: 
160:     /**
161:      * Returns a specific entry.
162:      *
163:      * @param string $type Type of name.
164:      * @param string $name Name or OID to fetch.
165:      *
166:      * @return mixed
167:      * @throws Horde_Ldap_Exception
168:      */
169:     public function get($type, $name)
170:     {
171:         if (!$this->_initialized) {
172:             return null;
173:         }
174: 
175:         $type = Horde_String::lower($type);
176:         if (!isset($this->types[$type])) {
177:             throw new Horde_Ldap_Exception("No such type $type");
178:         }
179: 
180:         $name     = Horde_String::lower($name);
181:         $type_var = $this->{'_' . $this->types[$type]};
182: 
183:         if (isset($type_var[$name])) {
184:             return $type_var[$name];
185:         }
186:         if (isset($this->_oids[$name]) &&
187:             $this->_oids[$name]['type'] == $type) {
188:             return $this->_oids[$name];
189:         }
190:         throw new Horde_Ldap_Exception("Could not find $type $name");
191:     }
192: 
193: 
194:     /**
195:      * Fetches attributes that MAY be present in the given objectclass.
196:      *
197:      * @param string $oc         Name or OID of objectclass.
198:      * @param boolean $checksup  Check all superiour objectclasses too?
199:      *
200:      * @return array Array with attributes.
201:      */
202:     public function may($oc, $checksup = false)
203:     {
204:         try {
205:             $attributes = $this->_getAttr($oc, 'may');
206:         } catch (Horde_Ldap_Exception $e) {
207:             $attributes = array();
208:         }
209:         if ($checksup) {
210:             try {
211:                 foreach ($this->superclass($oc) as $sup) {
212:                     $attributes = array_merge($attributes, $this->may($sup, true));
213:                 }
214:             } catch (Horde_Ldap_Exception $e) {
215:             }
216:             $attributes = array_values(array_unique($attributes));
217:         }
218:         return $attributes;
219:     }
220: 
221:     /**
222:      * Fetches attributes that MUST be present in the given objectclass.
223:      *
224:      * @param string $oc         Name or OID of objectclass.
225:      * @param boolean $checksup  Check all superiour objectclasses too?
226:      *
227:      * @return array Array with attributes.
228:      */
229:     public function must($oc, $checksup = false)
230:     {
231:         try {
232:             $attributes = $this->_getAttr($oc, 'must');
233:         } catch (Horde_Ldap_Exception $e) {
234:             $attributes = array();
235:         }
236:         if ($checksup) {
237:             try {
238:                 foreach ($this->superclass($oc) as $sup) {
239:                     $attributes = array_merge($attributes, $this->must($sup, true));
240:                 }
241:             } catch (Horde_Ldap_Exception $e) {
242:             }
243:             $attributes = array_values(array_unique($attributes));
244:         }
245:         return $attributes;
246:     }
247: 
248:     /**
249:      * Fetches the given attribute from the given objectclass.
250:      *
251:      * @param string $oc   Name or OID of objectclass.
252:      * @param string $attr Name of attribute to fetch.
253:      *
254:      * @return array The attribute.
255:      * @throws Horde_Ldap_Exception
256:      */
257:     protected function _getAttr($oc, $attr)
258:     {
259:         $oc = Horde_String::lower($oc);
260:         if (isset($this->_objectClasses[$oc]) &&
261:             isset($this->_objectClasses[$oc][$attr])) {
262:             return $this->_objectClasses[$oc][$attr];
263:         }
264:         if (isset($this->_oids[$oc]) &&
265:             $this->_oids[$oc]['type'] == 'objectclass' &&
266:             isset($this->_oids[$oc][$attr])) {
267:             return $this->_oids[$oc][$attr];
268:         }
269:         throw new Horde_Ldap_Exception("Could not find $attr attributes for $oc ");
270:     }
271: 
272:     /**
273:      * Returns the name(s) of the immediate superclass(es).
274:      *
275:      * @param string $oc Name or OID of objectclass.
276:      *
277:      * @return array
278:      * @throws Horde_Ldap_Exception
279:      */
280:     public function superclass($oc)
281:     {
282:         $o = $this->get('objectclass', $oc);
283:         return isset($o['sup']) ? $o['sup'] : array();
284:     }
285: 
286:     /**
287:      * Parses the schema of the given subschema entry.
288:      *
289:      * @param Horde_Ldap_Entry $entry Subschema entry.
290:      */
291:     public function parse($entry)
292:     {
293:         foreach ($this->types as $type => $attr) {
294:             // Initialize map type to entry.
295:             $type_var          = '_' . $attr;
296:             $this->{$type_var} = array();
297: 
298:             if (!$entry->exists($attr)) {
299:                 continue;
300:             }
301: 
302:             // Get values for this type.
303:             $values = $entry->getValue($attr);
304:             if (!is_array($values)) {
305:                 continue;
306:             }
307:             foreach ($values as $value) {
308:                 // Get the schema entry.
309:                 $schema_entry = $this->_parse_entry($value);
310:                 // Set the type.
311:                 $schema_entry['type'] = $type;
312:                 // Save a ref in $_oids.
313:                 $this->_oids[$schema_entry['oid']] = $schema_entry;
314:                 // Save refs for all names in type map.
315:                 $names = $schema_entry['aliases'];
316:                 array_push($names, $schema_entry['name']);
317:                 foreach ($names as $name) {
318:                     $this->{$type_var}[Horde_String::lower($name)] = $schema_entry;
319:                 }
320:             }
321:         }
322:         $this->_initialized = true;
323:     }
324: 
325:     /**
326:      * Parses an attribute value into a schema entry.
327:      *
328:      * @param string $value Attribute value.
329:      *
330:      * @return array Schema entry array.
331:      */
332:     protected function _parse_entry($value)
333:     {
334:         // Tokens that have no value associated.
335:         $noValue = array('single-value',
336:                          'obsolete',
337:                          'collective',
338:                          'no-user-modification',
339:                          'abstract',
340:                          'structural',
341:                          'auxiliary');
342: 
343:         // Tokens that can have multiple values.
344:         $multiValue = array('must', 'may', 'sup');
345: 
346:         // Get an array of tokens.
347:         $tokens = $this->_tokenize($value);
348: 
349:         // Remove surrounding brackets.
350:         if ($tokens[0] == '(') {
351:             array_shift($tokens);
352:         }
353:         if ($tokens[count($tokens) - 1] == ')') {
354:             array_pop($tokens);
355:         }
356: 
357:         // First token is the oid.
358:         $schema_entry = array('aliases' => array(),
359:                               'oid' => array_shift($tokens));
360: 
361:         // Cycle over the tokens until none are left.
362:         while (count($tokens) > 0) {
363:             $token = Horde_String::lower(array_shift($tokens));
364:             if (in_array($token, $noValue)) {
365:                 // Single value token.
366:                 $schema_entry[$token] = 1;
367:             } else {
368:                 // Follow a string or a list if it is multivalued.
369:                 if (($schema_entry[$token] = array_shift($tokens)) == '(') {
370:                     // Create the list of values and cycles through the tokens
371:                     // until the end of the list is reached ')'.
372:                     $schema_entry[$token] = array();
373:                     while ($tmp = array_shift($tokens)) {
374:                         if ($tmp == ')') {
375:                             break;
376:                         }
377:                         if ($tmp != '$') {
378:                             array_push($schema_entry[$token], $tmp);
379:                         }
380:                     }
381:                 }
382:                 // Create an array if the value should be multivalued but was
383:                 // not.
384:                 if (in_array($token, $multiValue) &&
385:                     !is_array($schema_entry[$token])) {
386:                     $schema_entry[$token] = array($schema_entry[$token]);
387:                 }
388:             }
389:         }
390: 
391:         // Get max length from syntax.
392:         if (isset($schema_entry['syntax'])) {
393:             if (preg_match('/{(\d+)}/', $schema_entry['syntax'], $matches)) {
394:                 $schema_entry['max_length'] = $matches[1];
395:             }
396:         }
397: 
398:         // Force a name.
399:         if (empty($schema_entry['name'])) {
400:             $schema_entry['name'] = $schema_entry['oid'];
401:         }
402: 
403:         // Make one name the default and put the other ones into aliases.
404:         if (is_array($schema_entry['name'])) {
405:             $aliases                 = $schema_entry['name'];
406:             $schema_entry['name']    = array_shift($aliases);
407:             $schema_entry['aliases'] = $aliases;
408:         }
409: 
410:         return $schema_entry;
411:     }
412: 
413:     /**
414:      * Tokenizes the given value into an array of tokens.
415:      *
416:      * @param string $value String to parse.
417:      *
418:      * @return array Array of tokens.
419:      */
420:     protected function _tokenize($value)
421:     {
422:         /* Match one big pattern where only one of the three subpatterns
423:          * matches.  We are interested in the subpatterns that matched. If it
424:          * matched its value will be non-empty and so it is a token. Tokens may
425:          * be round brackets, a string, or a string enclosed by ''. */
426:         preg_match_all("/\s* (?:([()]) | ([^'\s()]+) | '((?:[^']+|'[^\s)])*)') \s*/x", $value, $matches);
427: 
428:         $tokens  = array();
429:         // Number of tokens (full pattern match).
430:         for ($i = 0, $c = count($matches[0]); $i < $c; $i++) {
431:             // Each subpattern.
432:             for ($j = 1; $j < 4; $j++) {
433:                 // Pattern match in this subpattern.
434:                 if (null != trim($matches[$j][$i])) {
435:                     // This is the token.
436:                     $tokens[$i] = trim($matches[$j][$i]);
437:                 }
438:             }
439:         }
440: 
441:         return $tokens;
442:     }
443: 
444:     /**
445:      * Returns wether a attribute syntax is binary or not.
446:      *
447:      * This method is used by Horde_Ldap_Entry to decide which PHP function
448:      * needs to be used to fetch the value in the proper format (e.g. binary or
449:      * string).
450:      *
451:      * @param string $attribute The name of the attribute (eg.: 'sn').
452:      *
453:      * @return boolean  True if the attribute is a binary type.
454:      */
455:     public function isBinary($attribute)
456:     {
457:         // All syntax that should be treaten as containing binary values.
458:         $syntax_binary = array(self::SYNTAX_OCTET_STRING, self::SYNTAX_JPEG);
459: 
460:         // Check Syntax.
461:         try {
462:             $attr_s = $this->get('attribute', $attribute);
463:         } catch (Horde_Ldap_Exception $e) {
464:             // Attribute not found in schema, consider attr not binary.
465:             return false;
466:         }
467: 
468:         if (isset($attr_s['syntax']) &&
469:             in_array($attr_s['syntax'], $syntax_binary)) {
470:             // Syntax is defined as binary in schema
471:             return true;
472:         }
473: 
474:         // Syntax not defined as binary, or not found if attribute is a
475:         // subtype, check superior attribute syntaxes.
476:         if (isset($attr_s['sup'])) {
477:             foreach ($attr_s['sup'] as $superattr) {
478:                 if ($this->isBinary($superattr)) {
479:                     // Stop checking parents since we are binary.
480:                     return true;
481:                 }
482:             }
483:         }
484: 
485:         return false;
486:     }
487: }
488: 
API documentation generated by ApiGen