PageRenderTime 62ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/ldapOrm.php

https://github.com/4l3x4ndr3/admin
PHP | 729 lines | 397 code | 80 blank | 252 comment | 100 complexity | d2d96e7ff27cd3903729999f96cdaba9 MD5 | raw file
Possible License(s): AGPL-3.0
  1. <?php
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  4. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. /**
  6. * LDAPORM - an LDAP micro ORM.
  7. *
  8. * For more informations: {@link http://github.com/kloadut/ldaporm}
  9. *
  10. * @author Alexis Gavoty <alexis@gavoty.fr>
  11. * @license MPLv2 http://www.mozilla.org/MPL/2.0/
  12. * @package yunohost
  13. */
  14. # ============================================================================ #
  15. # LDAP CONNECTION FUNCTIONS COLLECTION #
  16. # ============================================================================ #
  17. class LdapConnection
  18. {
  19. /**
  20. * Variables with default values
  21. *
  22. * @access public
  23. */
  24. var $connected = false;
  25. protected $connection = null;
  26. protected $baseDn;
  27. protected $baseDomain;
  28. protected $protocolVersion = 3;
  29. /**
  30. * Contructor that set the connection to the server and store domain
  31. *
  32. * @access public
  33. * @param string $server
  34. * @param string $domain
  35. */
  36. public function __construct($server, $domain)
  37. {
  38. $this->connection = ldap_connect($server);
  39. ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, $this->protocolVersion);
  40. $this->baseDn = 'dc='.strtr($domain, array('.' => ',dc=')); // foo.example.com -> dc=foo,dc=example,dc=com
  41. $this->baseDomain = $domain;
  42. }
  43. /**
  44. * Set the private $protocolVersion variable
  45. *
  46. * @access public
  47. * @param int $newProtocolVersion 2 | 3
  48. */
  49. public function setProtocolVersion($newProtocolVersion)
  50. {
  51. if ( $this->connection ) {
  52. $this->protocolVersion = $newProtocolVersion;
  53. ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, $this->protocolVersion);
  54. }
  55. }
  56. /**
  57. * Bind to LDAP with an account
  58. *
  59. * @access public
  60. * @param string $userDnArray
  61. * @param string $password
  62. * @return boolean
  63. */
  64. public function connect($userDnArray, $password)
  65. {
  66. if ( $this->connection )
  67. return ldap_bind( $this->connection, $this->arrayToDn($userDnArray).$this->baseDn, $password);
  68. }
  69. /**
  70. * Undind LDAP for disconnecting
  71. *
  72. * @access public
  73. * @return boolean
  74. */
  75. public function disconnect()
  76. {
  77. if (ldap_unbind($this->connection)) {
  78. $this->connected = false;
  79. return true;
  80. } else return false;
  81. }
  82. /**
  83. * Throw or return LDAP error
  84. *
  85. * @access protected
  86. * @param boolean $throw
  87. * @return exception|string
  88. */
  89. protected function ldapError($throw = true)
  90. {
  91. $error = 'Error: ('. ldap_errno($this->connection) .') '. ldap_error($this->connection);
  92. if ($throw) throw new Exception($error);
  93. else return $error;
  94. }
  95. /**
  96. * Ldapify an array of attributes
  97. *
  98. * @access protected
  99. * @param array $array
  100. * @return string
  101. */
  102. protected function arrayToDn($array, $trailingComma = true)
  103. {
  104. $result = '';
  105. if (!empty($array))
  106. {
  107. $i = 0;
  108. foreach ($array as $prefix => $value)
  109. {
  110. if ($i == 0) $result .= $prefix.'='.$value;
  111. else $result .= ','.$prefix.'='.$value;
  112. $i++;
  113. }
  114. if ($trailingComma) $result .= ',';
  115. }
  116. return $result;
  117. }
  118. }
  119. # ============================================================================ #
  120. # LDAP ENTRY OPERATION FUNCTIONS #
  121. # ============================================================================ #
  122. class LdapEntry extends LdapConnection
  123. {
  124. /**
  125. * Protected variables with default values
  126. *
  127. * @access protected
  128. */
  129. protected $searchFilter = '(objectClass=*)';
  130. protected $searchPath = array();
  131. protected $attributesToFetch = array();
  132. protected $models = array();
  133. protected $actualDn;
  134. /**
  135. * Contructor that set the connection and import model
  136. *
  137. * @access public
  138. * @param string $server
  139. * @param string $domain
  140. * @param string $modelPath - Absolute path to the model directory
  141. */
  142. public function __construct($server, $domain, $modelPath)
  143. {
  144. parent::__construct($server, $domain);
  145. if ($handle = opendir($modelPath)) // Import models
  146. {
  147. while (false !== ($entry = readdir($handle)))
  148. {
  149. if ($entry != '..' && $entry != '.')
  150. {
  151. require_once $modelPath.'/'.$entry;
  152. $entry = substr($entry, 0, strpos($entry, '.php'));
  153. $classname = ucfirst($entry);
  154. $this->$entry = new $classname(null, null, null);
  155. $this->$entry->connection = $this->connection;
  156. $this->$entry->baseDn = $this->baseDn;
  157. $this->$entry->baseDomain = $this->baseDomain;
  158. foreach ($this->$entry->options as $option => $value)
  159. {
  160. $this->$entry->$option = $value;
  161. }
  162. foreach ($this->$entry->fields as $attribute => $value)
  163. {
  164. $this->$entry->$attribute = ''; // Erk
  165. $this->$entry->attributesToFetch[] = $attribute;
  166. }
  167. }
  168. }
  169. closedir($handle);
  170. }
  171. }
  172. /**
  173. * Dynamic method builder (PHP is magic...)
  174. *
  175. * @access public
  176. * @param string $method - The undeclared method
  177. * @param array $arguments - Arguments passed to this undeclared method
  178. */
  179. public function __call($method, $arguments)
  180. {
  181. // Get
  182. if ($result = $this->getModelAndAttributeFromMethod('get', $method))
  183. return $this->$result['model']->$result['attr'];
  184. // Set
  185. if ($result = $this->getModelAndAttributeFromMethod('set', $method))
  186. $this->$result['model']->$result['attr'] = $arguments[0];
  187. // Save
  188. if ($model = $this->getModelFromMethod('save', $method))
  189. return $this->save($model);
  190. // Populate
  191. if ($model = $this->getModelFromMethod('populate', $method))
  192. return $this->populate($model, $arguments[0]);
  193. // Delete
  194. if ($model = $this->getModelFromMethod('delete', $method))
  195. {
  196. $dn = $this->arrayToDn(array_merge($this->$model->options['dnPattern'], $arguments[0], $this->$model->options['searchPath'])).$this->baseDn;
  197. return $this->delete($dn);
  198. }
  199. // Validate uniqueness
  200. if ($result = $this->getModelAndAttributeFromMethod('validateUniquenessOf', $method))
  201. return $this->validateUniquenessOf($result['attr'], $result['model']);
  202. // Validate length
  203. if ($result = $this->getModelAndAttributeFromMethod('validateLengthOf', $method))
  204. return $this->validateLengthOf($result['attr'], $result['model'], $arguments[0], $arguments[1]);
  205. // Validate format
  206. if ($result = $this->getModelAndAttributeFromMethod('validateFormatOf', $method))
  207. return $this->validateFormatOf($result['attr'], $result['model'], $arguments[0]);
  208. return false;
  209. }
  210. /**
  211. * Local function to get model and attribute from the self-made method
  212. *
  213. * @access public
  214. * @param string $methodName - The undeclared self-made name
  215. * @param string $method - The full method name
  216. * @return array|boolean - An array containing model name and attribute name, or false
  217. */
  218. public function getModelAndAttributeFromMethod($methodName, $method)
  219. {
  220. if (preg_match('#^'.$methodName.'#', $method))
  221. {
  222. $substract = strlen($methodName);
  223. $modelAndAttr = substr($method,$substract,strlen($method)-$substract);
  224. $modelAndAttr{0} = strtolower($modelAndAttr{0});
  225. $result['model'] = preg_replace('#[A-Z][a-z0-9]+#', '', $modelAndAttr);
  226. $result['attr'] = strtolower(preg_replace('#'.$result['model'].'#', '', $modelAndAttr));
  227. return $result;
  228. } else return false;
  229. }
  230. /**
  231. * Local function to get model from the self-made method
  232. *
  233. * @access public
  234. * @param string $methodName - The self-made method name
  235. * @param string $method - The full method name
  236. * @return string|boolean - The model name or false
  237. */
  238. public function getModelFromMethod($methodName, $method)
  239. {
  240. if (preg_match('#^'.$methodName.'#', $method))
  241. {
  242. $substract = strlen($methodName);
  243. return strtolower(substr($method,$substract,strlen($method)-$substract));
  244. } else return false;
  245. }
  246. /**
  247. * Set the protected $searchFilter variable
  248. *
  249. * @access public
  250. * @param $string $newSearchFilter
  251. */
  252. public function setSearchFilter($newSearchFilter)
  253. {
  254. $this->searchFilter = $newSearchFilter;
  255. }
  256. /**
  257. * Set the protected $searchPath variable
  258. *
  259. * @access public
  260. * @param string $newSearchPath
  261. */
  262. public function setSearchPath($newSearchPath)
  263. {
  264. $this->searchPath = $newSearchPath;
  265. }
  266. /**
  267. * Set the protected $attributesToFetch variable
  268. *
  269. * @access public
  270. * @param array $newAttributesArray
  271. */
  272. public function setAttributesToFetch($newAttributesArray)
  273. {
  274. $this->attributesToFetch = $newAttributesArray;
  275. }
  276. /**
  277. * Populate all entity attributes by finding it with a typical attribute
  278. *
  279. * @access public
  280. * @param string $model - The model name
  281. * @param array $attributeArray - The attribute key with its value
  282. */
  283. public function populate($model, $attributeArray)
  284. {
  285. $result = $this->$model->findOneBy($attributeArray);
  286. foreach ($result as $attribute => $attrValue)
  287. {
  288. $this->$model->$attribute = $attrValue;
  289. }
  290. foreach ($this->$model->options['dnPattern'] as $patternKey => $patternValue) {
  291. if (array_key_exists($patternKey, $result))
  292. $this->$model->actualDn[$patternKey] = $result[$patternKey];
  293. }
  294. }
  295. /**
  296. * Get LDAP entries with the default search filter or a pattern
  297. *
  298. * Pattern example : (&(objectclass=person)(cn=Alex*)(!(mail=*example.org)))
  299. * -> All the persons with a name beginning with Alex and a mail that does not finish with example.org
  300. *
  301. * @access public
  302. * @param string $pattern - The search pattern (default all objects)
  303. * @param boolean $toArray
  304. * @return array|boolean - Filtered array by default, multidimensionnal array, or false
  305. */
  306. public function findAll($pattern = null, $toArray = true)
  307. {
  308. if (empty($pattern)) $pattern = $this->searchFilter;
  309. $result = ldap_search
  310. (
  311. $this->connection,
  312. $this->baseDn,
  313. $pattern
  314. );
  315. if ($result)
  316. {
  317. if ($toArray) return $this->entriesToArray($result);
  318. else return ldap_get_entries($this->connection, $result);
  319. }
  320. else return false;
  321. }
  322. /**
  323. * Get the first LDAP entry with the default search filter or a pattern
  324. *
  325. * @access public
  326. * @param string $pattern - The search pattern (default all objects)
  327. * @param boolean $toArray
  328. * @return array|boolean - Filtered array by default, multidimensionnal array, or false
  329. * @see findAll()
  330. */
  331. public function first($pattern, $toArray = true)
  332. {
  333. $result = ldap_search
  334. (
  335. $this->connection,
  336. $this->arrayToDn($this->searchPath).$this->baseDn,
  337. $pattern,
  338. $this->attributesToFetch
  339. );
  340. if ($result)
  341. {
  342. if ($toArray) return $this->entryToArray($result);
  343. else return ldap_get_entries($this->connection, $result);
  344. }
  345. else return false;
  346. }
  347. /**
  348. * Get an LDAP entry with an attribute and its value
  349. *
  350. * @access public
  351. * @param array $attributeArray - The attribute key with its value
  352. * @param boolean $toArray
  353. * @return array|boolean - Filtered array by default, multidimensionnal array, or false
  354. */
  355. public function findOneBy($attributeArray, $toArray = true)
  356. {
  357. $result = ldap_search
  358. (
  359. $this->connection,
  360. $this->arrayToDn($this->searchPath).$this->baseDn,
  361. "(".$this->arrayToDn($attributeArray, false).")",
  362. $this->attributesToFetch
  363. );
  364. if ($result)
  365. {
  366. if ($toArray) return $this->entryToArray($result);
  367. else return ldap_get_entries($this->connection, $result);
  368. }
  369. else return false;
  370. }
  371. /**
  372. * Save defined attributes
  373. *
  374. * @access public
  375. * @param string $model - The model name
  376. * @return boolean - Fail | Win
  377. */
  378. public function save($model)
  379. {
  380. $this->$model->beforeSave();
  381. if (isset($this->$model->actualDn))
  382. $objectArray['actualDn'] = $this->arrayToDn(array_merge($this->$model->actualDn, $this->$model->searchPath)).$this->baseDn;
  383. $objectArray['newRdn'] = $this->arrayToDn($this->$model->dnPattern, false);
  384. $objectArray['newDn'] = $this->arrayToDn(array_merge($this->$model->dnPattern, $this->$model->searchPath)).$this->baseDn;
  385. $objectArray['objectClass'] = $this->$model->objectClass;
  386. foreach ($this->$model->fields as $attribute => $value) {
  387. $objectArray[$attribute] = $this->$model->$attribute;
  388. }
  389. if ($this->validate($model))
  390. {
  391. if (isset($this->$model->actualDn))
  392. return $this->update($objectArray);
  393. else
  394. return $this->create($objectArray);
  395. $this->$model->afterSave();
  396. return true;
  397. }
  398. }
  399. public function beforeSave() {} // Void function to implement
  400. public function afterSave() {} // Void function to implement
  401. /**
  402. * Create an LDAP entry
  403. *
  404. * @access public
  405. * @param array $attributesArray
  406. * @return exception - If it fails to add
  407. */
  408. public function create($attributesArray)
  409. {
  410. if (is_array($attributesArray['newDn']))
  411. $attributesArray['newDn'] = $this->arrayToDn($attributesArray['newDn']).$this->baseDn;
  412. $newEntry = $this->attributesArrayFilter($attributesArray);
  413. if (!ldap_add($this->connection, $attributesArray['newDn'], $newEntry))
  414. return $this->ldapError();
  415. else
  416. return true;
  417. }
  418. /**
  419. * Update an LDAP entry
  420. *
  421. * @access public
  422. * @param array $attributesArray
  423. * @return exception - If it fails to modify
  424. */
  425. public function update($attributesArray)
  426. {
  427. $modEntry = $this->attributesArrayFilter($attributesArray);
  428. if (ldap_rename($this->connection, $attributesArray['actualDn'], $attributesArray['newRdn'], null, true))
  429. {
  430. if (!ldap_mod_replace($this->connection, $attributesArray['newDn'], $modEntry))
  431. $this->ldapError();
  432. else
  433. return true;
  434. }
  435. else $this->ldapError();
  436. }
  437. /**
  438. * Delete an LDAP entry
  439. *
  440. * @access public
  441. * @param string $dn
  442. * @return exception - If it fails to delete
  443. */
  444. public function delete($dn)
  445. {
  446. if (!ldap_delete($this->connection, $dn))
  447. $this->ldapError();
  448. else
  449. return true;
  450. }
  451. /**
  452. * Filter $attributesArray to prepare LDAP operations
  453. *
  454. * @access protected
  455. * @param array $attributesArray
  456. * @return array - The filtered array
  457. */
  458. protected function attributesArrayFilter($attributesArray)
  459. {
  460. foreach ($attributesArray as $attr => $value)
  461. {
  462. if ('actualDn' == $attr || 'newRdn' == $attr || 'newDn' == $attr)
  463. unset($attributesArray[$attr]);
  464. elseif (is_array($value))
  465. {
  466. foreach ($value as $subvalue)
  467. $entry[$attr][] = $subvalue;
  468. }
  469. else $entry[$attr] = $value;
  470. if (empty($value))
  471. {
  472. unset($entry[$attr]);
  473. }
  474. }
  475. return $entry;
  476. }
  477. /**
  478. * Get a workable array instead of the insane result of ldap_get_entries()
  479. *
  480. * @access protected
  481. * @param array $result - The ldap_search() result
  482. * @return array - Pimped array
  483. */
  484. protected function entriesToArray($result)
  485. {
  486. $resultArray = array();
  487. $entry = ldap_first_entry($this->connection, $result);
  488. while ($entry)
  489. {
  490. $row = array();
  491. $attr = ldap_first_attribute($this->connection, $entry);
  492. while ($attr)
  493. {
  494. $val = ldap_get_values_len($this->connection, $entry, $attr);
  495. if (array_key_exists('count', $val) AND $val['count'] == 1)
  496. $row[strtolower($attr)] = $val[0];
  497. else
  498. {
  499. unset($val['count']);
  500. $row[strtolower($attr)] = $val;
  501. }
  502. $attr = ldap_next_attribute($this->connection, $entry);
  503. }
  504. if ($row) $resultArray[] = $row;
  505. $entry = ldap_next_entry($this->connection, $entry);
  506. }
  507. return $resultArray;
  508. }
  509. /**
  510. * Get a workable array for a single entry
  511. *
  512. * @access protected
  513. * @param array $result - The ldap_search() result
  514. * @return array - Simplified array
  515. */
  516. protected function entryToArray($result)
  517. {
  518. $resultArray = array();
  519. if($entry = ldap_first_entry($this->connection, $result))
  520. {
  521. $attr = ldap_first_attribute($this->connection, $entry);
  522. while ($attr)
  523. {
  524. $val = ldap_get_values_len($this->connection, $entry, $attr);
  525. if (array_key_exists('count', $val) AND $val['count'] == 1)
  526. $resultArray[strtolower($attr)] = $val[0];
  527. else
  528. {
  529. unset($val['count']);
  530. $resultArray[strtolower($attr)] = $val;
  531. }
  532. $attr = ldap_next_attribute($this->connection, $entry);
  533. }
  534. return $resultArray;
  535. } else return array();
  536. }
  537. /**
  538. * Validate attributes of a model (from the settings of the model file)
  539. *
  540. * @access protected
  541. * @param string $model - The model name
  542. * @return boolean
  543. */
  544. protected function validate($model)
  545. {
  546. foreach ($this->$model->fields as $attribute => $validationArray) {
  547. if (isset($validationArray['required']) && $validationArray['required'] == true)
  548. {
  549. if (!$this->validatePresenceOf($attribute, $model))
  550. return false;
  551. if (isset($validationArray['minLength']))
  552. {
  553. if (!$this->validateLengthOf($attribute, $model, $validationArray['minLength']))
  554. return false;
  555. }
  556. if (isset($validationArray['maxLength']))
  557. {
  558. if (!$this->validateLengthOf($attribute, $model, 0, $validationArray['maxLength']))
  559. return false;
  560. }
  561. if (isset($validationArray['pattern']))
  562. {
  563. if (!$this->validateFormatOf($attribute, $model, $validationArray['pattern']))
  564. return false;
  565. }
  566. }
  567. if (isset($validationArray['unique']) && $validationArray['unique'] == true)
  568. {
  569. if (!$this->validateUniquenessOf($attribute, $model))
  570. return false;
  571. }
  572. }
  573. return true;
  574. }
  575. /**
  576. * Validate attribute's presence
  577. *
  578. * @access protected
  579. * @param string $attribute
  580. * @param string $model
  581. * @return boolean
  582. */
  583. protected function validatePresenceOf($attribute, $model)
  584. {
  585. return !empty($this->$model->$attribute);
  586. }
  587. /**
  588. * Validate attribute's uniqueness
  589. *
  590. * @access protected
  591. * @param string $attribute
  592. * @param string $model
  593. * @return boolean
  594. */
  595. protected function validateUniquenessOf($attribute, $model)
  596. {
  597. if ($attribute === 'cn')
  598. return true;
  599. $alreadyExists = $this->findOneBy(array($attribute => $this->$model->$attribute));
  600. if (!empty($alreadyExists))
  601. return 'cn='.$alreadyExists['cn'] === $this->arrayToDn($this->$model->actualDn, false);
  602. else return true;
  603. }
  604. /**
  605. * Validate attribute's length
  606. *
  607. * @access protected
  608. * @param string $attribute
  609. * @param string $model
  610. * @param int $minLength
  611. * @param int $maxLength
  612. * @return boolean
  613. */
  614. protected function validateLengthOf($attribute, $model, $minLength = 0, $maxLength = 1024)
  615. {
  616. if (is_array($this->$model->$attribute))
  617. {
  618. foreach ($this->$model->$attribute as $value)
  619. {
  620. if (strlen($value) <= $maxLength && strlen($value) >= $minLength) continue;
  621. else return false;
  622. }
  623. return true;
  624. }
  625. else return strlen($this->$model->$attribute) <= $maxLength && strlen($this->$model->$attribute) >= $minLength;
  626. }
  627. /**
  628. * Validate attribute's pattern
  629. *
  630. * @access protected
  631. * @param string $attribute
  632. * @param string $model
  633. * @param string $regex
  634. * @return boolean
  635. */
  636. protected function validateFormatOf($attribute, $model, $regex)
  637. {
  638. if (is_array($this->$model->$attribute))
  639. {
  640. foreach ($this->$model->$attribute as $value)
  641. {
  642. if (preg_match($regex, $value)) continue;
  643. else return false;
  644. }
  645. return true;
  646. }
  647. else return preg_match($regex, $this->$model->$attribute);
  648. }
  649. }