summaryrefslogtreecommitdiff
path: root/extlib/Net/LDAP2/Schema.php
diff options
context:
space:
mode:
authorCraig Andrews <candrews@integralblue.com>2009-11-04 13:39:56 -0500
committerCraig Andrews <candrews@integralblue.com>2009-11-04 13:39:56 -0500
commitc403f7fa442d7f8eacb4e2288429c271450d57d2 (patch)
tree750fc924305db82ff73f202f20b256c989918a19 /extlib/Net/LDAP2/Schema.php
parenta82df5fae8b5d71d4545f27e7aa6cc561160922a (diff)
Added Net_LDAP2 to extlib, and add a skeleton LDAP plugin
Diffstat (limited to 'extlib/Net/LDAP2/Schema.php')
-rw-r--r--extlib/Net/LDAP2/Schema.php516
1 files changed, 516 insertions, 0 deletions
diff --git a/extlib/Net/LDAP2/Schema.php b/extlib/Net/LDAP2/Schema.php
new file mode 100644
index 000000000..b590eabc5
--- /dev/null
+++ b/extlib/Net/LDAP2/Schema.php
@@ -0,0 +1,516 @@
+<?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 286718 2009-08-03 07:30:49Z 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 $result;
+ }
+
+ $entry = $result->shiftEntry();
+ if (!$entry instanceof Net_LDAP2_Entry) {
+ return PEAR::raiseError('Could not fetch Subschema entry');
+ }
+
+ $schema_o->parse($entry);
+ return $schema_o;
+ }
+
+ /**
+ * Return a hash of entries for the given type
+ *
+ * Returns a hash of entry for th 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 type
+ if ($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 brackets
+ if ($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 left
+ while (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 multivalued
+ if (($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 not
+ if (in_array($token, $multiValue) && !is_array($schema_entry[$token])) {
+ $schema_entry[$token] = array($schema_entry[$token]);
+ }
+ }
+ }
+ // get max length from syntax
+ if (key_exists('syntax', $schema_entry)) {
+ if (preg_match('/{(\d+)}/', $schema_entry['syntax'], $matches)) {
+ $schema_entry['max_length'] = $matches[1];
+ }
+ }
+ // force a name
+ if (empty($schema_entry['name'])) {
+ $schema_entry['name'] = $schema_entry['oid'];
+ }
+ // make one name the default and put the other ones into aliases
+ if (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 subpattern
+ if (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 syntaxes
+ if (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;
+ }
+
+ // [TODO] add method that allows us to see to which objectclasses a certain attribute belongs to
+ // it should return the result structured, e.g. sorted in "may" and "must". Optionally it should
+ // be able to return it just "flat", e.g. array_merge()d.
+ // We could use get_all() to achieve this easily, i think
+}
+?>