Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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
?>