Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php/* vim: set expandtab tabstop=4 shiftwidth=4: *//*** File containing the Net_LDAP2_Schema interface class.** PHP version 5** @category Net* @package Net_LDAP2* @author Jan Wagner <wagner@netsols.de>* @author Benedikt Hallinger <beni@php.net>* @copyright 2009 Jan Wagner, Benedikt Hallinger* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3* @version SVN: $Id: Schema.php 296515 2010-03-22 14:46:41Z beni $* @link http://pear.php.net/package/Net_LDAP2/* @todo see the comment at the end of the file*//*** Includes*/require_once 'PEAR.php';/*** Syntax definitions** Please don't forget to add binary attributes to isBinary() below* to support proper value fetching from Net_LDAP2_Entry*/define('NET_LDAP2_SYNTAX_BOOLEAN', '1.3.6.1.4.1.1466.115.121.1.7');define('NET_LDAP2_SYNTAX_DIRECTORY_STRING', '1.3.6.1.4.1.1466.115.121.1.15');define('NET_LDAP2_SYNTAX_DISTINGUISHED_NAME', '1.3.6.1.4.1.1466.115.121.1.12');define('NET_LDAP2_SYNTAX_INTEGER', '1.3.6.1.4.1.1466.115.121.1.27');define('NET_LDAP2_SYNTAX_JPEG', '1.3.6.1.4.1.1466.115.121.1.28');define('NET_LDAP2_SYNTAX_NUMERIC_STRING', '1.3.6.1.4.1.1466.115.121.1.36');define('NET_LDAP2_SYNTAX_OID', '1.3.6.1.4.1.1466.115.121.1.38');define('NET_LDAP2_SYNTAX_OCTET_STRING', '1.3.6.1.4.1.1466.115.121.1.40');/*** Load an LDAP Schema and provide information** This class takes a Subschema entry, parses this information* and makes it available in an array. Most of the code has been* inspired by perl-ldap( http://perl-ldap.sourceforge.net).* You will find portions of their implementation in here.** @category Net* @package Net_LDAP2* @author Jan Wagner <wagner@netsols.de>* @author Benedikt Hallinger <beni@php.net>* @license http://www.gnu.org/copyleft/lesser.html LGPL* @link http://pear.php.net/package/Net_LDAP22/*/class Net_LDAP2_Schema extends PEAR{/*** Map of entry types to ldap attributes of subschema entry** @access public* @var array*/public $types = array('attribute' => 'attributeTypes','ditcontentrule' => 'dITContentRules','ditstructurerule' => 'dITStructureRules','matchingrule' => 'matchingRules','matchingruleuse' => 'matchingRuleUse','nameform' => 'nameForms','objectclass' => 'objectClasses','syntax' => 'ldapSyntaxes');/*** Array of entries belonging to this type** @access protected* @var array*/protected $_attributeTypes = array();protected $_matchingRules = array();protected $_matchingRuleUse = array();protected $_ldapSyntaxes = array();protected $_objectClasses = array();protected $_dITContentRules = array();protected $_dITStructureRules = array();protected $_nameForms = array();/*** hash of all fetched oids** @access protected* @var array*/protected $_oids = array();/*** Tells if the schema is initialized** @access protected* @var boolean* @see parse(), get()*/protected $_initialized = false;/*** Constructor of the class** @access protected*/protected function __construct(){$this->PEAR('Net_LDAP2_Error'); // default error class}/*** Fetch the Schema from an LDAP connection** @param Net_LDAP2 $ldap LDAP connection* @param string $dn (optional) Subschema entry dn** @access public* @return Net_LDAP2_Schema|NET_LDAP2_Error*/public function fetch($ldap, $dn = null){if (!$ldap instanceof Net_LDAP2) {return PEAR::raiseError("Unable to fetch Schema: Parameter \$ldap must be a Net_LDAP2 object!");}$schema_o = new Net_LDAP2_Schema();if (is_null($dn)) {// get the subschema entry via root dse$dse = $ldap->rootDSE(array('subschemaSubentry'));if (false == Net_LDAP2::isError($dse)) {$base = $dse->getValue('subschemaSubentry', 'single');if (!Net_LDAP2::isError($base)) {$dn = $base;}}}// Support for buggy LDAP servers (e.g. Siemens DirX 6.x) that incorrectly// call this entry subSchemaSubentry instead of subschemaSubentry.// Note the correct case/spelling as per RFC 2251.if (is_null($dn)) {// get the subschema entry via root dse$dse = $ldap->rootDSE(array('subSchemaSubentry'));if (false == Net_LDAP2::isError($dse)) {$base = $dse->getValue('subSchemaSubentry', 'single');if (!Net_LDAP2::isError($base)) {$dn = $base;}}}// Final fallback case where there is no subschemaSubentry attribute// in the root DSE (this is a bug for an LDAP v3 server so report this// to your LDAP vendor if you get this far).if (is_null($dn)) {$dn = 'cn=Subschema';}// fetch the subschema entry$result = $ldap->search($dn, '(objectClass=*)',array('attributes' => array_values($schema_o->types),'scope' => 'base'));if (Net_LDAP2::isError($result)) {return PEAR::raiseError('Could not fetch Subschema entry: '.$result->getMessage());}$entry = $result->shiftEntry();if (!$entry instanceof Net_LDAP2_Entry) {if ($entry instanceof Net_LDAP2_Error) {return PEAR::raiseError('Could not fetch Subschema entry: '.$entry->getMessage());} else {return PEAR::raiseError('Could not fetch Subschema entry (search returned '.$result->count().' entries. Check parameter \'basedn\')');}}$schema_o->parse($entry);return $schema_o;}/*** Return a hash of entries for the given type** Returns a hash of entry for the givene type. Types may be:* objectclasses, attributes, ditcontentrules, ditstructurerules, matchingrules,* matchingruleuses, nameforms, syntaxes** @param string $type Type to fetch** @access public* @return array|Net_LDAP2_Error Array or Net_LDAP2_Error*/public function &getAll($type){$map = array('objectclasses' => &$this->_objectClasses,'attributes' => &$this->_attributeTypes,'ditcontentrules' => &$this->_dITContentRules,'ditstructurerules' => &$this->_dITStructureRules,'matchingrules' => &$this->_matchingRules,'matchingruleuses' => &$this->_matchingRuleUse,'nameforms' => &$this->_nameForms,'syntaxes' => &$this->_ldapSyntaxes );$key = strtolower($type);$ret = ((key_exists($key, $map)) ? $map[$key] : PEAR::raiseError("Unknown type $type"));return $ret;}/*** Return a specific entry** @param string $type Type of name* @param string $name Name or OID to fetch** @access public* @return mixed Entry or Net_LDAP2_Error*/public function &get($type, $name){if ($this->_initialized) {$type = strtolower($type);if (false == key_exists($type, $this->types)) {return PEAR::raiseError("No such type $type");}$name = strtolower($name);$type_var = &$this->{'_' . $this->types[$type]};if (key_exists($name, $type_var)) {return $type_var[$name];} elseif (key_exists($name, $this->_oids) && $this->_oids[$name]['type'] == $type) {return $this->_oids[$name];} else {return PEAR::raiseError("Could not find $type $name");}} else {$return = null;return $return;}}/*** Fetches attributes that MAY be present in the given objectclass** @param string $oc Name or OID of objectclass** @access public* @return array|Net_LDAP2_Error Array with attributes or Net_LDAP2_Error*/public function may($oc){return $this->_getAttr($oc, 'may');}/*** Fetches attributes that MUST be present in the given objectclass** @param string $oc Name or OID of objectclass** @access public* @return array|Net_LDAP2_Error Array with attributes or Net_LDAP2_Error*/public function must($oc){return $this->_getAttr($oc, 'must');}/*** Fetches the given attribute from the given objectclass** @param string $oc Name or OID of objectclass* @param string $attr Name of attribute to fetch** @access protected* @return array|Net_LDAP2_Error The attribute or Net_LDAP2_Error*/protected function _getAttr($oc, $attr){$oc = strtolower($oc);if (key_exists($oc, $this->_objectClasses) && key_exists($attr, $this->_objectClasses[$oc])) {return $this->_objectClasses[$oc][$attr];} elseif (key_exists($oc, $this->_oids) &&$this->_oids[$oc]['type'] == 'objectclass' &&key_exists($attr, $this->_oids[$oc])) {return $this->_oids[$oc][$attr];} else {return PEAR::raiseError("Could not find $attr attributes for $oc ");}}/*** Returns the name(s) of the immediate superclass(es)** @param string $oc Name or OID of objectclass** @access public* @return array|Net_LDAP2_Error Array of names or Net_LDAP2_Error*/public function superclass($oc){$o = $this->get('objectclass', $oc);if (Net_LDAP2::isError($o)) {return $o;}return (key_exists('sup', $o) ? $o['sup'] : array());}/*** Parses the schema of the given Subschema entry** @param Net_LDAP2_Entry &$entry Subschema entry** @access public* @return void*/public function parse(&$entry){foreach ($this->types as $type => $attr) {// initialize map type to entry$type_var = '_' . $attr;$this->{$type_var} = array();// get values for this typeif ($entry->exists($attr)) {$values = $entry->getValue($attr);if (is_array($values)) {foreach ($values as $value) {unset($schema_entry); // this was a real mess without it// get the schema entry$schema_entry = $this->_parse_entry($value);// set the type$schema_entry['type'] = $type;// save a ref in $_oids$this->_oids[$schema_entry['oid']] = &$schema_entry;// save refs for all names in type map$names = $schema_entry['aliases'];array_push($names, $schema_entry['name']);foreach ($names as $name) {$this->{$type_var}[strtolower($name)] = &$schema_entry;}}}}}$this->_initialized = true;}/*** Parses an attribute value into a schema entry** @param string $value Attribute value** @access protected* @return array|false Schema entry array or false*/protected function &_parse_entry($value){// tokens that have no value associated$noValue = array('single-value','obsolete','collective','no-user-modification','abstract','structural','auxiliary');// tokens that can have multiple values$multiValue = array('must', 'may', 'sup');$schema_entry = array('aliases' => array()); // initilization$tokens = $this->_tokenize($value); // get an array of tokens// remove surrounding bracketsif ($tokens[0] == '(') array_shift($tokens);if ($tokens[count($tokens) - 1] == ')') array_pop($tokens); // -1 doesnt work on arrays :-($schema_entry['oid'] = array_shift($tokens); // first token is the oid// cycle over the tokens until none are leftwhile (count($tokens) > 0) {$token = strtolower(array_shift($tokens));if (in_array($token, $noValue)) {$schema_entry[$token] = 1; // single value token} else {// this one follows a string or a list if it is multivaluedif (($schema_entry[$token] = array_shift($tokens)) == '(') {// this creates the list of values and cycles through the tokens// until the end of the list is reached ')'$schema_entry[$token] = array();while ($tmp = array_shift($tokens)) {if ($tmp == ')') break;if ($tmp != '$') array_push($schema_entry[$token], $tmp);}}// create a array if the value should be multivalued but was notif (in_array($token, $multiValue) && !is_array($schema_entry[$token])) {$schema_entry[$token] = array($schema_entry[$token]);}}}// get max length from syntaxif (key_exists('syntax', $schema_entry)) {if (preg_match('/{(\d+)}/', $schema_entry['syntax'], $matches)) {$schema_entry['max_length'] = $matches[1];}}// force a nameif (empty($schema_entry['name'])) {$schema_entry['name'] = $schema_entry['oid'];}// make one name the default and put the other ones into aliasesif (is_array($schema_entry['name'])) {$aliases = $schema_entry['name'];$schema_entry['name'] = array_shift($aliases);$schema_entry['aliases'] = $aliases;}return $schema_entry;}/*** Tokenizes the given value into an array of tokens** @param string $value String to parse** @access protected* @return array Array of tokens*/protected function _tokenize($value){$tokens = array(); // array of tokens$matches = array(); // matches[0] full pattern match, [1,2,3] subpatterns// this one is taken from perl-ldap, modified for php$pattern = "/\s* (?:([()]) | ([^'\s()]+) | '((?:[^']+|'[^\s)])*)') \s*/x";/*** This one matches one big pattern wherin only one of the three subpatterns matched* We are interested in the subpatterns that matched. If it matched its value will be* non-empty and so it is a token. Tokens may be round brackets, a string, or a string* enclosed by '*/preg_match_all($pattern, $value, $matches);for ($i = 0; $i < count($matches[0]); $i++) { // number of tokens (full pattern match)for ($j = 1; $j < 4; $j++) { // each subpatternif (null != trim($matches[$j][$i])) { // pattern match in this subpattern$tokens[$i] = trim($matches[$j][$i]); // this is the token}}}return $tokens;}/*** Returns wether a attribute syntax is binary or not** This method gets used by Net_LDAP2_Entry to decide which* PHP function needs to be used to fetch the value in the* proper format (e.g. binary or string)** @param string $attribute The name of the attribute (eg.: 'sn')** @access public* @return boolean*/public function isBinary($attribute){$return = false; // default to false// This list contains all syntax that should be treaten as// containing binary values// The Syntax Definitons go into constants at the top of this page$syntax_binary = array(NET_LDAP2_SYNTAX_OCTET_STRING,NET_LDAP2_SYNTAX_JPEG);// Check Syntax$attr_s = $this->get('attribute', $attribute);if (Net_LDAP2::isError($attr_s)) {// Attribute not found in schema$return = false; // consider attr not binary} elseif (isset($attr_s['syntax']) && in_array($attr_s['syntax'], $syntax_binary)) {// Syntax is defined as binary in schema$return = true;} else {// Syntax not defined as binary, or not found// if attribute is a subtype, check superior attribute syntaxesif (isset($attr_s['sup'])) {foreach ($attr_s['sup'] as $superattr) {$return = $this->isBinary($superattr);if ($return) {break; // stop checking parents since we are binary}}}}return $return;}/*** See if an schema element exists** @param string $type Type of name, see get()* @param string $name Name or OID** @return boolean*/public function exists($type, $name){$entry = $this->get($type, $name);if ($entry instanceof Net_LDAP2_ERROR) {return false;} else {return true;}}/*** See if an attribute is defined in the schema** @param string $attribute Name or OID of the attribute* @return boolean*/public function attributeExists($attribute){return $this->exists('attribute', $attribute);}/*** See if an objectClass is defined in the schema** @param string $ocl Name or OID of the objectClass* @return boolean*/public function objectClassExists($ocl){return $this->exists('objectclass', $ocl);}/*** See to which ObjectClasses an attribute is assigned** The objectclasses are sorted into the keys 'may' and 'must'.** @param string $attribute Name or OID of the attribute** @return array|Net_LDAP2_Error Associative array with OCL names or Error*/public function getAssignedOCLs($attribute){$may = array();$must = array();// Test if the attribute type is defined in the schema,// if so, retrieve real name for lookups$attr_entry = $this->get('attribute', $attribute);if ($attr_entry instanceof Net_LDAP2_ERROR) {return PEAR::raiseError("Attribute $attribute not defined in schema: ".$attr_entry->getMessage());} else {$attribute = $attr_entry['name'];}// We need to get all defined OCLs for this.$ocls = $this->getAll('objectclasses');foreach ($ocls as $ocl => $ocl_data) {// Fetch the may and must attrs and see if our searched attr is contained.// If so, record it in the corresponding array.$ocl_may_attrs = $this->may($ocl);$ocl_must_attrs = $this->must($ocl);if (is_array($ocl_may_attrs) && in_array($attribute, $ocl_may_attrs)) {array_push($may, $ocl_data['name']);}if (is_array($ocl_must_attrs) && in_array($attribute, $ocl_must_attrs)) {array_push($must, $ocl_data['name']);}}return array('may' => $may, 'must' => $must);}/*** See if an attribute is available in a set of objectClasses** @param string $attribute Attribute name or OID* @param array $ocls Names of OCLs to check for** @return boolean TRUE, if the attribute is defined for at least one of the OCLs*/public function checkAttribute($attribute, $ocls){foreach ($ocls as $ocl) {$ocl_entry = $this->get('objectclass', $ocl);$ocl_may_attrs = $this->may($ocl);$ocl_must_attrs = $this->must($ocl);if (is_array($ocl_may_attrs) && in_array($attribute, $ocl_may_attrs)) {return true;}if (is_array($ocl_must_attrs) && in_array($attribute, $ocl_must_attrs)) {return true;}}return false; // no ocl for the ocls found.}}?>