PageRenderTime 41ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/common/libraries/plugin/pear/Net/LDAP2/Schema.php

https://bitbucket.org/chamilo/chamilo/
PHP | 516 lines | 259 code | 49 blank | 208 comment | 57 complexity | ec9613c9456199392dfb393bdc28ca40 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, LGPL-2.1, LGPL-3.0, GPL-3.0, MIT
  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 286718 2009-08-03 07:30:49Z beni $
  15. * @link http://pear.php.net/package/Net_LDAP2/
  16. * @todo see the comment at the end of the file
  17. */
  18. /**
  19. * Includes
  20. */
  21. require_once 'PEAR.php';
  22. /**
  23. * Syntax definitions
  24. *
  25. * Please don't forget to add binary attributes to isBinary() below
  26. * to support proper value fetching from Net_LDAP2_Entry
  27. */
  28. define('NET_LDAP2_SYNTAX_BOOLEAN', '1.3.6.1.4.1.1466.115.121.1.7');
  29. define('NET_LDAP2_SYNTAX_DIRECTORY_STRING', '1.3.6.1.4.1.1466.115.121.1.15');
  30. define('NET_LDAP2_SYNTAX_DISTINGUISHED_NAME', '1.3.6.1.4.1.1466.115.121.1.12');
  31. define('NET_LDAP2_SYNTAX_INTEGER', '1.3.6.1.4.1.1466.115.121.1.27');
  32. define('NET_LDAP2_SYNTAX_JPEG', '1.3.6.1.4.1.1466.115.121.1.28');
  33. define('NET_LDAP2_SYNTAX_NUMERIC_STRING', '1.3.6.1.4.1.1466.115.121.1.36');
  34. define('NET_LDAP2_SYNTAX_OID', '1.3.6.1.4.1.1466.115.121.1.38');
  35. define('NET_LDAP2_SYNTAX_OCTET_STRING', '1.3.6.1.4.1.1466.115.121.1.40');
  36. /**
  37. * Load an LDAP Schema and provide information
  38. *
  39. * This class takes a Subschema entry, parses this information
  40. * and makes it available in an array. Most of the code has been
  41. * inspired by perl-ldap( http://perl-ldap.sourceforge.net).
  42. * You will find portions of their implementation in here.
  43. *
  44. * @category Net
  45. * @package Net_LDAP2
  46. * @author Jan Wagner <wagner@netsols.de>
  47. * @author Benedikt Hallinger <beni@php.net>
  48. * @license http://www.gnu.org/copyleft/lesser.html LGPL
  49. * @link http://pear.php.net/package/Net_LDAP22/
  50. */
  51. class Net_LDAP2_Schema extends PEAR
  52. {
  53. /**
  54. * Map of entry types to ldap attributes of subschema entry
  55. *
  56. * @access public
  57. * @var array
  58. */
  59. public $types = array(
  60. 'attribute' => 'attributeTypes',
  61. 'ditcontentrule' => 'dITContentRules',
  62. 'ditstructurerule' => 'dITStructureRules',
  63. 'matchingrule' => 'matchingRules',
  64. 'matchingruleuse' => 'matchingRuleUse',
  65. 'nameform' => 'nameForms',
  66. 'objectclass' => 'objectClasses',
  67. 'syntax' => 'ldapSyntaxes'
  68. );
  69. /**
  70. * Array of entries belonging to this type
  71. *
  72. * @access protected
  73. * @var array
  74. */
  75. protected $_attributeTypes = array();
  76. protected $_matchingRules = array();
  77. protected $_matchingRuleUse = array();
  78. protected $_ldapSyntaxes = array();
  79. protected $_objectClasses = array();
  80. protected $_dITContentRules = array();
  81. protected $_dITStructureRules = array();
  82. protected $_nameForms = array();
  83. /**
  84. * hash of all fetched oids
  85. *
  86. * @access protected
  87. * @var array
  88. */
  89. protected $_oids = array();
  90. /**
  91. * Tells if the schema is initialized
  92. *
  93. * @access protected
  94. * @var boolean
  95. * @see parse(), get()
  96. */
  97. protected $_initialized = false;
  98. /**
  99. * Constructor of the class
  100. *
  101. * @access protected
  102. */
  103. public function __construct()
  104. {
  105. parent :: __construct('Net_LDAP2_Error'); // default error class
  106. }
  107. /**
  108. * Fetch the Schema from an LDAP connection
  109. *
  110. * @param Net_LDAP2 $ldap LDAP connection
  111. * @param string $dn (optional) Subschema entry dn
  112. *
  113. * @access public
  114. * @return Net_LDAP2_Schema|NET_LDAP2_Error
  115. */
  116. public function fetch($ldap, $dn = null)
  117. {
  118. if (!$ldap instanceof Net_LDAP2) {
  119. return PEAR::raiseError("Unable to fetch Schema: Parameter \$ldap must be a Net_LDAP2 object!");
  120. }
  121. $schema_o = new Net_LDAP2_Schema();
  122. if (is_null($dn)) {
  123. // get the subschema entry via root dse
  124. $dse = $ldap->rootDSE(array('subschemaSubentry'));
  125. if (false == Net_LDAP2::isError($dse)) {
  126. $base = $dse->getValue('subschemaSubentry', 'single');
  127. if (!Net_LDAP2::isError($base)) {
  128. $dn = $base;
  129. }
  130. }
  131. }
  132. // Support for buggy LDAP servers (e.g. Siemens DirX 6.x) that incorrectly
  133. // call this entry subSchemaSubentry instead of subschemaSubentry.
  134. // Note the correct case/spelling as per RFC 2251.
  135. if (is_null($dn)) {
  136. // get the subschema entry via root dse
  137. $dse = $ldap->rootDSE(array('subSchemaSubentry'));
  138. if (false == Net_LDAP2::isError($dse)) {
  139. $base = $dse->getValue('subSchemaSubentry', 'single');
  140. if (!Net_LDAP2::isError($base)) {
  141. $dn = $base;
  142. }
  143. }
  144. }
  145. // Final fallback case where there is no subschemaSubentry attribute
  146. // in the root DSE (this is a bug for an LDAP v3 server so report this
  147. // to your LDAP vendor if you get this far).
  148. if (is_null($dn)) {
  149. $dn = 'cn=Subschema';
  150. }
  151. // fetch the subschema entry
  152. $result = $ldap->search($dn, '(objectClass=*)',
  153. array('attributes' => array_values($schema_o->types),
  154. 'scope' => 'base'));
  155. if (Net_LDAP2::isError($result)) {
  156. return $result;
  157. }
  158. $entry = $result->shiftEntry();
  159. if (!$entry instanceof Net_LDAP2_Entry) {
  160. return PEAR::raiseError('Could not fetch Subschema entry');
  161. }
  162. $schema_o->parse($entry);
  163. return $schema_o;
  164. }
  165. /**
  166. * Return a hash of entries for the given type
  167. *
  168. * Returns a hash of entry for th givene type. Types may be:
  169. * objectclasses, attributes, ditcontentrules, ditstructurerules, matchingrules,
  170. * matchingruleuses, nameforms, syntaxes
  171. *
  172. * @param string $type Type to fetch
  173. *
  174. * @access public
  175. * @return array|Net_LDAP2_Error Array or Net_LDAP2_Error
  176. */
  177. public function &getAll($type)
  178. {
  179. $map = array('objectclasses' => &$this->_objectClasses,
  180. 'attributes' => &$this->_attributeTypes,
  181. 'ditcontentrules' => &$this->_dITContentRules,
  182. 'ditstructurerules' => &$this->_dITStructureRules,
  183. 'matchingrules' => &$this->_matchingRules,
  184. 'matchingruleuses' => &$this->_matchingRuleUse,
  185. 'nameforms' => &$this->_nameForms,
  186. 'syntaxes' => &$this->_ldapSyntaxes );
  187. $key = strtolower($type);
  188. $ret = ((key_exists($key, $map)) ? $map[$key] : PEAR::raiseError("Unknown type $type"));
  189. return $ret;
  190. }
  191. /**
  192. * Return a specific entry
  193. *
  194. * @param string $type Type of name
  195. * @param string $name Name or OID to fetch
  196. *
  197. * @access public
  198. * @return mixed Entry or Net_LDAP2_Error
  199. */
  200. public function &get($type, $name)
  201. {
  202. if ($this->_initialized) {
  203. $type = strtolower($type);
  204. if (false == key_exists($type, $this->types)) {
  205. return PEAR::raiseError("No such type $type");
  206. }
  207. $name = strtolower($name);
  208. $type_var = &$this->{'_' . $this->types[$type]};
  209. if (key_exists($name, $type_var)) {
  210. return $type_var[$name];
  211. } elseif (key_exists($name, $this->_oids) && $this->_oids[$name]['type'] == $type) {
  212. return $this->_oids[$name];
  213. } else {
  214. return PEAR::raiseError("Could not find $type $name");
  215. }
  216. } else {
  217. $return = null;
  218. return $return;
  219. }
  220. }
  221. /**
  222. * Fetches attributes that MAY be present in the given objectclass
  223. *
  224. * @param string $oc Name or OID of objectclass
  225. *
  226. * @access public
  227. * @return array|Net_LDAP2_Error Array with attributes or Net_LDAP2_Error
  228. */
  229. public function may($oc)
  230. {
  231. return $this->_getAttr($oc, 'may');
  232. }
  233. /**
  234. * Fetches attributes that MUST be present in the given objectclass
  235. *
  236. * @param string $oc Name or OID of objectclass
  237. *
  238. * @access public
  239. * @return array|Net_LDAP2_Error Array with attributes or Net_LDAP2_Error
  240. */
  241. public function must($oc)
  242. {
  243. return $this->_getAttr($oc, 'must');
  244. }
  245. /**
  246. * Fetches the given attribute from the given objectclass
  247. *
  248. * @param string $oc Name or OID of objectclass
  249. * @param string $attr Name of attribute to fetch
  250. *
  251. * @access protected
  252. * @return array|Net_LDAP2_Error The attribute or Net_LDAP2_Error
  253. */
  254. protected function _getAttr($oc, $attr)
  255. {
  256. $oc = strtolower($oc);
  257. if (key_exists($oc, $this->_objectClasses) && key_exists($attr, $this->_objectClasses[$oc])) {
  258. return $this->_objectClasses[$oc][$attr];
  259. } elseif (key_exists($oc, $this->_oids) &&
  260. $this->_oids[$oc]['type'] == 'objectclass' &&
  261. key_exists($attr, $this->_oids[$oc])) {
  262. return $this->_oids[$oc][$attr];
  263. } else {
  264. return PEAR::raiseError("Could not find $attr attributes for $oc ");
  265. }
  266. }
  267. /**
  268. * Returns the name(s) of the immediate superclass(es)
  269. *
  270. * @param string $oc Name or OID of objectclass
  271. *
  272. * @access public
  273. * @return array|Net_LDAP2_Error Array of names or Net_LDAP2_Error
  274. */
  275. public function superclass($oc)
  276. {
  277. $o = $this->get('objectclass', $oc);
  278. if (Net_LDAP2::isError($o)) {
  279. return $o;
  280. }
  281. return (key_exists('sup', $o) ? $o['sup'] : array());
  282. }
  283. /**
  284. * Parses the schema of the given Subschema entry
  285. *
  286. * @param Net_LDAP2_Entry &$entry Subschema entry
  287. *
  288. * @access public
  289. * @return void
  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. // get values for this type
  298. if ($entry->exists($attr)) {
  299. $values = $entry->getValue($attr);
  300. if (is_array($values)) {
  301. foreach ($values as $value) {
  302. unset($schema_entry); // this was a real mess without it
  303. // get the schema entry
  304. $schema_entry = $this->_parse_entry($value);
  305. // set the type
  306. $schema_entry['type'] = $type;
  307. // save a ref in $_oids
  308. $this->_oids[$schema_entry['oid']] = &$schema_entry;
  309. // save refs for all names in type map
  310. $names = $schema_entry['aliases'];
  311. array_push($names, $schema_entry['name']);
  312. foreach ($names as $name) {
  313. $this->{$type_var}[strtolower($name)] = &$schema_entry;
  314. }
  315. }
  316. }
  317. }
  318. }
  319. $this->_initialized = true;
  320. }
  321. /**
  322. * Parses an attribute value into a schema entry
  323. *
  324. * @param string $value Attribute value
  325. *
  326. * @access protected
  327. * @return array|false Schema entry array or false
  328. */
  329. protected function &_parse_entry($value)
  330. {
  331. // tokens that have no value associated
  332. $noValue = array('single-value',
  333. 'obsolete',
  334. 'collective',
  335. 'no-user-modification',
  336. 'abstract',
  337. 'structural',
  338. 'auxiliary');
  339. // tokens that can have multiple values
  340. $multiValue = array('must', 'may', 'sup');
  341. $schema_entry = array('aliases' => array()); // initilization
  342. $tokens = $this->_tokenize($value); // get an array of tokens
  343. // remove surrounding brackets
  344. if ($tokens[0] == '(') array_shift($tokens);
  345. if ($tokens[count($tokens) - 1] == ')') array_pop($tokens); // -1 doesnt work on arrays :-(
  346. $schema_entry['oid'] = array_shift($tokens); // first token is the oid
  347. // cycle over the tokens until none are left
  348. while (count($tokens) > 0) {
  349. $token = strtolower(array_shift($tokens));
  350. if (in_array($token, $noValue)) {
  351. $schema_entry[$token] = 1; // single value token
  352. } else {
  353. // this one follows a string or a list if it is multivalued
  354. if (($schema_entry[$token] = array_shift($tokens)) == '(') {
  355. // this creates the list of values and cycles through the tokens
  356. // until the end of the list is reached ')'
  357. $schema_entry[$token] = array();
  358. while ($tmp = array_shift($tokens)) {
  359. if ($tmp == ')') break;
  360. if ($tmp != '$') array_push($schema_entry[$token], $tmp);
  361. }
  362. }
  363. // create a array if the value should be multivalued but was not
  364. if (in_array($token, $multiValue) && !is_array($schema_entry[$token])) {
  365. $schema_entry[$token] = array($schema_entry[$token]);
  366. }
  367. }
  368. }
  369. // get max length from syntax
  370. if (key_exists('syntax', $schema_entry)) {
  371. if (preg_match('/{(\d+)}/', $schema_entry['syntax'], $matches)) {
  372. $schema_entry['max_length'] = $matches[1];
  373. }
  374. }
  375. // force a name
  376. if (empty($schema_entry['name'])) {
  377. $schema_entry['name'] = $schema_entry['oid'];
  378. }
  379. // make one name the default and put the other ones into aliases
  380. if (is_array($schema_entry['name'])) {
  381. $aliases = $schema_entry['name'];
  382. $schema_entry['name'] = array_shift($aliases);
  383. $schema_entry['aliases'] = $aliases;
  384. }
  385. return $schema_entry;
  386. }
  387. /**
  388. * Tokenizes the given value into an array of tokens
  389. *
  390. * @param string $value String to parse
  391. *
  392. * @access protected
  393. * @return array Array of tokens
  394. */
  395. protected function _tokenize($value)
  396. {
  397. $tokens = array(); // array of tokens
  398. $matches = array(); // matches[0] full pattern match, [1,2,3] subpatterns
  399. // this one is taken from perl-ldap, modified for php
  400. $pattern = "/\s* (?:([()]) | ([^'\s()]+) | '((?:[^']+|'[^\s)])*)') \s*/x";
  401. /**
  402. * This one matches one big pattern wherin only one of the three subpatterns matched
  403. * We are interested in the subpatterns that matched. If it matched its value will be
  404. * non-empty and so it is a token. Tokens may be round brackets, a string, or a string
  405. * enclosed by '
  406. */
  407. preg_match_all($pattern, $value, $matches);
  408. for ($i = 0; $i < count($matches[0]); $i++) { // number of tokens (full pattern match)
  409. for ($j = 1; $j < 4; $j++) { // each subpattern
  410. if (null != trim($matches[$j][$i])) { // pattern match in this subpattern
  411. $tokens[$i] = trim($matches[$j][$i]); // this is the token
  412. }
  413. }
  414. }
  415. return $tokens;
  416. }
  417. /**
  418. * Returns wether a attribute syntax is binary or not
  419. *
  420. * This method gets used by Net_LDAP2_Entry to decide which
  421. * PHP function needs to be used to fetch the value in the
  422. * proper format (e.g. binary or string)
  423. *
  424. * @param string $attribute The name of the attribute (eg.: 'sn')
  425. *
  426. * @access public
  427. * @return boolean
  428. */
  429. public function isBinary($attribute)
  430. {
  431. $return = false; // default to false
  432. // This list contains all syntax that should be treaten as
  433. // containing binary values
  434. // The Syntax Definitons go into constants at the top of this page
  435. $syntax_binary = array(
  436. NET_LDAP2_SYNTAX_OCTET_STRING,
  437. NET_LDAP2_SYNTAX_JPEG
  438. );
  439. // Check Syntax
  440. $attr_s = $this->get('attribute', $attribute);
  441. if (Net_LDAP2::isError($attr_s)) {
  442. // Attribute not found in schema
  443. $return = false; // consider attr not binary
  444. } elseif (isset($attr_s['syntax']) && in_array($attr_s['syntax'], $syntax_binary)) {
  445. // Syntax is defined as binary in schema
  446. $return = true;
  447. } else {
  448. // Syntax not defined as binary, or not found
  449. // if attribute is a subtype, check superior attribute syntaxes
  450. if (isset($attr_s['sup'])) {
  451. foreach ($attr_s['sup'] as $superattr) {
  452. $return = $this->isBinary($superattr);
  453. if ($return) {
  454. break; // stop checking parents since we are binary
  455. }
  456. }
  457. }
  458. }
  459. return $return;
  460. }
  461. // [TODO] add method that allows us to see to which objectclasses a certain attribute belongs to
  462. // it should return the result structured, e.g. sorted in "may" and "must". Optionally it should
  463. // be able to return it just "flat", e.g. array_merge()d.
  464. // We could use get_all() to achieve this easily, i think
  465. }
  466. ?>