PageRenderTime 42ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/core/src/main/php/peer/ldap/LDAPClient.class.php

https://github.com/treuter/xp-framework
PHP | 446 lines | 192 code | 45 blank | 209 comment | 32 complexity | 304743d4693dd46e0aba50dd5b8bffe2 MD5 | raw file
  1. <?php
  2. /* This class is part of the XP framework
  3. *
  4. * $Id$
  5. */
  6. // Search scopes
  7. define('LDAP_SCOPE_BASE', 0x0000);
  8. define('LDAP_SCOPE_ONELEVEL', 0x0001);
  9. define('LDAP_SCOPE_SUB', 0x0002);
  10. uses(
  11. 'peer.ConnectException',
  12. 'peer.ldap.LDAPQuery',
  13. 'peer.ldap.LDAPException',
  14. 'peer.ldap.LDAPSearchResult'
  15. );
  16. /**
  17. * LDAP client
  18. *
  19. * Example:
  20. * <code>
  21. * xp::sapi('cli');
  22. * uses('peer.ldap.LDAPClient');
  23. *
  24. * $l= new LDAPClient('ldap.openldap.org');
  25. * try {
  26. * $l->setOption(LDAP_OPT_PROTOCOL_VERSION, 3);
  27. * $l->connect();
  28. * $l->bind();
  29. * $res= $l->search(
  30. * 'ou=People,dc=OpenLDAP,dc=Org',
  31. * '(objectClass=*)'
  32. * );
  33. * } catch (ConnectException $e) {
  34. * $e->printStackTrace();
  35. * exit(-1);
  36. * } catch (LDAPException $e) {
  37. * $e->printStackTrace();
  38. * exit(-1);
  39. * }
  40. *
  41. * Console::writeLinef('===> %d entries found', $res->numEntries());
  42. * while ($entry= $res->getNextEntry()) {
  43. * Console::writeLine('---> ', $entry->toString());
  44. * }
  45. *
  46. * // Disconnect
  47. * $l->close();
  48. * </code>
  49. *
  50. * @see php://ldap
  51. * @see http://developer.netscape.com/docs/manuals/directory/41/ag/
  52. * @see http://developer.netscape.com/docs/manuals/dirsdk/jsdk40/contents.htm
  53. * @see http://perl-ldap.sourceforge.net/doc/Net/LDAP/
  54. * @see http://ldap.akbkhome.com/
  55. * @see rfc://2251
  56. * @see rfc://2252
  57. * @see rfc://2253
  58. * @see rfc://2254
  59. * @see rfc://2255
  60. * @see rfc://2256
  61. * @ext ldap
  62. * @test xp://net.xp_framework.unittest.peer.LDAPTest
  63. * @purpose LDAP client
  64. */
  65. class LDAPClient extends Object {
  66. public
  67. $host,
  68. $port;
  69. public
  70. $_hdl;
  71. /**
  72. * Constructor
  73. *
  74. * @param string host default 'localhost' LDAP server
  75. * @param int port default 389 Port
  76. */
  77. public function __construct($host= 'localhost', $port= 389) {
  78. $this->host= $host;
  79. $this->port= $port;
  80. }
  81. /**
  82. * Connect to the LDAP server
  83. *
  84. * @return resource LDAP resource handle
  85. * @throws peer.ConnectException
  86. */
  87. public function connect() {
  88. if ($this->isConnected()) return TRUE; // Already connected
  89. if (FALSE === ($this->_hdl= ldap_connect($this->host, $this->port))) {
  90. throw new ConnectException('Cannot connect to '.$this->host.':'.$this->port);
  91. }
  92. return $this->_hdl;
  93. }
  94. /**
  95. * Bind
  96. *
  97. * @param string user default NULL
  98. * @param string pass default NULL
  99. * @return bool success
  100. * @throws peer.ldap.LDAPException
  101. * @throws peer.ConnectException
  102. */
  103. public function bind($user= NULL, $pass= NULL) {
  104. if (FALSE === ($res= ldap_bind($this->_hdl, $user, $pass))) {
  105. switch ($error= ldap_errno($this->_hdl)) {
  106. case LDAP_SERVER_DOWN:
  107. throw new ConnectException('Cannot connect to '.$this->host.':'.$this->port);
  108. default:
  109. throw new LDAPException('Cannot bind for "'.$user.'"', $error);
  110. }
  111. }
  112. return $res;
  113. }
  114. /**
  115. * Sets an ldap option value
  116. *
  117. * @param int option
  118. * @param var value
  119. * @return bool success
  120. */
  121. public function setOption($option, $value) {
  122. if (FALSE === ($res= ldap_set_option ($this->_hdl, $option, $value))) {
  123. throw (new LDAPException ('Cannot set value "'.$option.'"', ldap_errno($this->_hdl)));
  124. }
  125. return $res;
  126. }
  127. /**
  128. * Retrieve ldap option value
  129. *
  130. * @param int option
  131. * @return var value
  132. */
  133. public function getOption($option) {
  134. if (FALSE === ($res= ldap_get_option ($this->_hdl, $option, $value))) {
  135. throw (new LDAPException ('Cannot get value "'.$option.'"', ldap_errno($this->_hdl)));
  136. }
  137. return $value;
  138. }
  139. /**
  140. * Checks whether the connection is open
  141. *
  142. * @return bool true, when we're connected, false otherwise (OK, what else?:))
  143. */
  144. public function isConnected() {
  145. return is_resource($this->_hdl);
  146. }
  147. /**
  148. * Closes the connection
  149. *
  150. * @see php://ldap_close
  151. * @return bool success
  152. */
  153. public function close() {
  154. if (!$this->isConnected()) return TRUE;
  155. ldap_unbind($this->_hdl);
  156. $this->_hdl= NULL;
  157. }
  158. /**
  159. * Perform an LDAP search with scope LDAP_SCOPE_SUB
  160. *
  161. * @param string base_dn
  162. * @param string filter
  163. * @param array attributes default array()
  164. * @param int attrsonly default 0,
  165. * @param int sizelimit default 0
  166. * @param int timelimit default 0 Time limit, 0 means no limit
  167. * @param int deref one of LDAP_DEREF_*
  168. * @return peer.ldap.LDAPSearchResult search result object
  169. * @throws peer.ldap.LDAPException
  170. * @see php://ldap_search
  171. */
  172. public function search() {
  173. $args= func_get_args();
  174. array_unshift($args, $this->_hdl);
  175. if (FALSE === ($res= call_user_func_array('ldap_search', $args))) {
  176. throw new LDAPException('Search failed', ldap_errno($this->_hdl));
  177. }
  178. return new LDAPSearchResult($this->_hdl, $res);
  179. }
  180. /**
  181. * Perform an LDAP search specified by a given filter.
  182. *
  183. * @param peer.ldap.LDAPQuery filter
  184. * @return peer.ldap.LDAPSearchResult search result object
  185. */
  186. public function searchBy(LDAPQuery $filter) {
  187. static $methods= array(
  188. LDAP_SCOPE_BASE => 'ldap_read',
  189. LDAP_SCOPE_ONELEVEL => 'ldap_list',
  190. LDAP_SCOPE_SUB => 'ldap_search'
  191. );
  192. if (empty($methods[$filter->getScope()]))
  193. throw new IllegalArgumentException('Scope '.$args[0].' not supported');
  194. if (FALSE === ($res= @call_user_func_array(
  195. $methods[$filter->getScope()], array(
  196. $this->_hdl,
  197. $filter->getBase(),
  198. $filter->getFilter(),
  199. $filter->getAttrs(),
  200. $filter->getAttrsOnly(),
  201. $filter->getSizeLimit(),
  202. $filter->getTimelimit(),
  203. $filter->getDeref()
  204. )))) {
  205. throw new LDAPException('Search failed', ldap_errno($this->_hdl));
  206. }
  207. // Sort results by given sort attributes
  208. if ($filter->getSort()) foreach ($filter->getSort() as $sort) {
  209. ldap_sort($this->_hdl, $res, $sort);
  210. }
  211. return new LDAPSearchResult($this->_hdl, $res);
  212. }
  213. /**
  214. * Perform an LDAP search with a scope
  215. *
  216. * @param int scope search scope, one of the LDAP_SCOPE_* constants
  217. * @param string base_dn
  218. * @param string filter
  219. * @param array attributes default NULL
  220. * @param int attrsonly default 0,
  221. * @param int sizelimit default 0
  222. * @param int timelimit default 0 Time limit, 0 means no limit
  223. * @param int deref one of LDAP_DEREF_*
  224. * @return peer.ldap.LDAPSearchResult search result object
  225. * @throws peer.ldap.LDAPException
  226. * @see php://ldap_search
  227. */
  228. public function searchScope() {
  229. $args= func_get_args();
  230. switch ($args[0]) {
  231. case LDAP_SCOPE_BASE: $func= 'ldap_read'; break;
  232. case LDAP_SCOPE_ONELEVEL: $func= 'ldap_list'; break;
  233. case LDAP_SCOPE_SUB: $func= 'ldap_search'; break;
  234. default: throw new IllegalArgumentException('Scope '.$args[0].' not supported');
  235. }
  236. $args[0]= $this->_hdl;
  237. if (FALSE === ($res= call_user_func_array($func, $args))) {
  238. throw new LDAPException('Search failed', ldap_errno($this->_hdl));
  239. }
  240. return new LDAPSearchResult($this->_hdl, $res);
  241. }
  242. /**
  243. * Read an entry
  244. *
  245. * @param peer.ldap.LDAPEntry entry specifying the dn
  246. * @return peer.ldap.LDAPEntry entry
  247. * @throws lang.IllegalArgumentException
  248. * @throws peer.ldap.LDAPException
  249. */
  250. public function read(LDAPEntry $entry) {
  251. $res= ldap_read($this->_hdl, $entry->getDN(), 'objectClass=*', array(), FALSE, 0);
  252. if (0 != ldap_errno($this->_hdl)) {
  253. throw new LDAPException('Read "'.$entry->getDN().'" failed', ldap_errno($this->_hdl));
  254. }
  255. return LDAPEntry::fromResource($this->_hdl, ldap_first_entry($this->_hdl, $res));
  256. }
  257. /**
  258. * Check if an entry exists
  259. *
  260. * @param peer.ldap.LDAPEntry entry specifying the dn
  261. * @return bool TRUE if the entry exists
  262. */
  263. public function exists(LDAPEntry $entry) {
  264. $res= ldap_read($this->_hdl, $entry->getDN(), 'objectClass=*', array(), FALSE, 0);
  265. // Check for certain error code (#32)
  266. if (LDAP_NO_SUCH_OBJECT == ldap_errno($this->_hdl)) {
  267. return FALSE;
  268. }
  269. // Check for other errors
  270. if (LDAP_SUCCESS != ldap_errno($this->_hdl)) {
  271. throw new LDAPException('Read "'.$entry->getDN().'" failed', ldap_errno($this->_hdl));
  272. }
  273. // No errors occurred, requested object exists
  274. ldap_free_result($res);
  275. return TRUE;
  276. }
  277. /**
  278. * Encode entries (recursively, if needed)
  279. *
  280. * @param var v
  281. * @return string encoded entry
  282. */
  283. protected function _encode($v) {
  284. if (is_array($v)) {
  285. foreach (array_keys($v) as $i) $v[$i]= $this->_encode($v[$i]);
  286. return $v;
  287. }
  288. return iconv(xp::ENCODING, 'utf-8', $v);
  289. }
  290. /**
  291. * Add an entry
  292. *
  293. * @param peer.ldap.LDAPEntry entry
  294. * @return bool success
  295. * @throws lang.IllegalArgumentException when entry parameter is not an LDAPEntry object
  296. * @throws peer.ldap.LDAPException when an error occurs during adding the entry
  297. */
  298. public function add(LDAPEntry $entry) {
  299. // This actually returns NULL on failure, not FALSE, as documented
  300. if (NULL == ($res= ldap_add(
  301. $this->_hdl,
  302. $entry->getDN(),
  303. array_map(array($this, '_encode'), $entry->getAttributes())
  304. ))) {
  305. throw new LDAPException('Add for "'.$entry->getDN().'" failed', ldap_errno($this->_hdl));
  306. }
  307. return $res;
  308. }
  309. /**
  310. * Modify an entry.
  311. *
  312. * Note: Will do a complete update of all fields and can be quite slow
  313. * TBD(?): Be more intelligent about what to update?
  314. *
  315. * @param peer.ldap.LDAPEntry entry
  316. * @return bool success
  317. * @throws lang.IllegalArgumentException when entry parameter is not an LDAPEntry object
  318. * @throws peer.ldap.LDAPException when an error occurs during adding the entry
  319. */
  320. public function modify(LDAPEntry $entry) {
  321. if (FALSE == ($res= ldap_modify(
  322. $this->_hdl,
  323. $entry->getDN(),
  324. array_map(array($this, '_encode'), $entry->getAttributes())
  325. ))) {
  326. throw new LDAPException('Modify for "'.$entry->getDN().'" failed', ldap_errno($this->_hdl));
  327. }
  328. return $res;
  329. }
  330. /**
  331. * Delete an entry
  332. *
  333. * @param peer.ldap.LDAPEntry entry
  334. * @return bool success
  335. * @throws lang.IllegalArgumentException when entry parameter is not an LDAPEntry object
  336. * @throws peer.ldap.LDAPException when an error occurs during adding the entry
  337. */
  338. public function delete(LDAPEntry $entry) {
  339. if (FALSE == ($res= ldap_delete(
  340. $this->_hdl,
  341. $entry->getDN()
  342. ))) {
  343. throw new LDAPException('Delete for "'.$entry->getDN().'" failed', ldap_errno($this->_hdl));
  344. }
  345. return $res;
  346. }
  347. /**
  348. * Add an attribute to an entry
  349. *
  350. * @param peer.ldap.LDAPEntry entry
  351. * @param string name
  352. * @param var value
  353. * @return bool
  354. */
  355. public function addAttribute(LDAPEntry $entry, $name, $value) {
  356. if (FALSE == ($res= ldap_mod_add(
  357. $this->_hdl,
  358. $entry->getDN(),
  359. array($name => $value)
  360. ))) {
  361. throw new LDAPException('Add attribute for "'.$entry->getDN().'" failed', ldap_errno($this->_hdl));
  362. }
  363. return $res;
  364. }
  365. /**
  366. * Delete an attribute from an entry
  367. *
  368. * @param peer.ldap.LDAPEntry entry
  369. * @param string name
  370. * @return bool
  371. */
  372. public function deleteAttribute(LDAPEntry $entry, $name) {
  373. if (FALSE == ($res= ldap_mod_del(
  374. $this->_hdl,
  375. $entry->getDN(),
  376. $name
  377. ))) {
  378. throw new LDAPException('Delete attribute for "'.$entry->getDN().'" failed', ldap_errno($this->_hdl));
  379. }
  380. return $res;
  381. }
  382. /**
  383. * Add an attribute to an entry
  384. *
  385. * @param peer.ldap.LDAPEntry entry
  386. * @param string name
  387. * @param var value
  388. * @return bool
  389. */
  390. public function replaceAttribute(LDAPEntry $entry, $name, $value) {
  391. if (FALSE == ($res= ldap_mod_replace(
  392. $this->_hdl,
  393. $entry->getDN(),
  394. array($name => $value)
  395. ))) {
  396. throw new LDAPException('Add attribute for "'.$entry->getDN().'" failed', ldap_errno($this->_hdl));
  397. }
  398. return $res;
  399. }
  400. }
  401. ?>