|
1 <?php |
|
2 |
|
3 /** |
|
4 * Zend Framework |
|
5 * |
|
6 * LICENSE |
|
7 * |
|
8 * This source file is subject to the new BSD license that is bundled |
|
9 * with this package in the file LICENSE.txt. |
|
10 * It is also available through the world-wide-web at this URL: |
|
11 * http://framework.zend.com/license/new-bsd |
|
12 * If you did not receive a copy of the license and are unable to |
|
13 * obtain it through the world-wide-web, please send an email |
|
14 * to license@zend.com so we can send you a copy immediately. |
|
15 * |
|
16 * @category Zend |
|
17 * @package Zend_Ldap |
|
18 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
19 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
20 * @version $Id: Ldap.php 22996 2010-09-22 17:01:46Z sgehrig $ |
|
21 */ |
|
22 |
|
23 /** |
|
24 * @category Zend |
|
25 * @package Zend_Ldap |
|
26 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
27 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
28 */ |
|
29 class Zend_Ldap |
|
30 { |
|
31 const SEARCH_SCOPE_SUB = 1; |
|
32 const SEARCH_SCOPE_ONE = 2; |
|
33 const SEARCH_SCOPE_BASE = 3; |
|
34 |
|
35 const ACCTNAME_FORM_DN = 1; |
|
36 const ACCTNAME_FORM_USERNAME = 2; |
|
37 const ACCTNAME_FORM_BACKSLASH = 3; |
|
38 const ACCTNAME_FORM_PRINCIPAL = 4; |
|
39 |
|
40 /** |
|
41 * String used with ldap_connect for error handling purposes. |
|
42 * |
|
43 * @var string |
|
44 */ |
|
45 private $_connectString; |
|
46 |
|
47 /** |
|
48 * The options used in connecting, binding, etc. |
|
49 * |
|
50 * @var array |
|
51 */ |
|
52 protected $_options = null; |
|
53 |
|
54 /** |
|
55 * The raw LDAP extension resource. |
|
56 * |
|
57 * @var resource |
|
58 */ |
|
59 protected $_resource = null; |
|
60 |
|
61 /** |
|
62 * FALSE if no user is bound to the LDAP resource |
|
63 * NULL if there has been an anonymous bind |
|
64 * username of the currently bound user |
|
65 * |
|
66 * @var boolean|null|string |
|
67 */ |
|
68 protected $_boundUser = false; |
|
69 |
|
70 /** |
|
71 * Caches the RootDSE |
|
72 * |
|
73 * @var Zend_Ldap_Node |
|
74 */ |
|
75 protected $_rootDse = null; |
|
76 |
|
77 /** |
|
78 * Caches the schema |
|
79 * |
|
80 * @var Zend_Ldap_Node |
|
81 */ |
|
82 protected $_schema = null; |
|
83 |
|
84 /** |
|
85 * @deprecated will be removed, use {@see Zend_Ldap_Filter_Abstract::escapeValue()} |
|
86 * @param string $str The string to escape. |
|
87 * @return string The escaped string |
|
88 */ |
|
89 public static function filterEscape($str) |
|
90 { |
|
91 /** |
|
92 * @see Zend_Ldap_Filter_Abstract |
|
93 */ |
|
94 require_once 'Zend/Ldap/Filter/Abstract.php'; |
|
95 return Zend_Ldap_Filter_Abstract::escapeValue($str); |
|
96 } |
|
97 |
|
98 /** |
|
99 * @deprecated will be removed, use {@see Zend_Ldap_Dn::checkDn()} |
|
100 * @param string $dn The DN to parse |
|
101 * @param array $keys An optional array to receive DN keys (e.g. CN, OU, DC, ...) |
|
102 * @param array $vals An optional array to receive DN values |
|
103 * @return boolean True if the DN was successfully parsed or false if the string is |
|
104 * not a valid DN. |
|
105 */ |
|
106 public static function explodeDn($dn, array &$keys = null, array &$vals = null) |
|
107 { |
|
108 /** |
|
109 * @see Zend_Ldap_Dn |
|
110 */ |
|
111 require_once 'Zend/Ldap/Dn.php'; |
|
112 return Zend_Ldap_Dn::checkDn($dn, $keys, $vals); |
|
113 } |
|
114 |
|
115 /** |
|
116 * Constructor. |
|
117 * |
|
118 * @param array|Zend_Config $options Options used in connecting, binding, etc. |
|
119 * @return void |
|
120 * @throws Zend_Ldap_Exception if ext/ldap is not installed |
|
121 */ |
|
122 public function __construct($options = array()) |
|
123 { |
|
124 if (!extension_loaded('ldap')) { |
|
125 /** |
|
126 * @see Zend_Ldap_Exception |
|
127 */ |
|
128 require_once 'Zend/Ldap/Exception.php'; |
|
129 throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded', |
|
130 Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED); |
|
131 } |
|
132 $this->setOptions($options); |
|
133 } |
|
134 |
|
135 /** |
|
136 * Destructor. |
|
137 * |
|
138 * @return void |
|
139 */ |
|
140 public function __destruct() |
|
141 { |
|
142 $this->disconnect(); |
|
143 } |
|
144 |
|
145 /** |
|
146 * @return resource The raw LDAP extension resource. |
|
147 */ |
|
148 public function getResource() |
|
149 { |
|
150 if (!is_resource($this->_resource) || $this->_boundUser === false) { |
|
151 $this->bind(); |
|
152 } |
|
153 return $this->_resource; |
|
154 } |
|
155 |
|
156 /** |
|
157 * Return the LDAP error number of the last LDAP command |
|
158 * |
|
159 * @return int |
|
160 */ |
|
161 public function getLastErrorCode() |
|
162 { |
|
163 $ret = @ldap_get_option($this->_resource, LDAP_OPT_ERROR_NUMBER, $err); |
|
164 if ($ret === true) { |
|
165 if ($err <= -1 && $err >= -17) { |
|
166 /** |
|
167 * @see Zend_Ldap_Exception |
|
168 */ |
|
169 require_once 'Zend/Ldap/Exception.php'; |
|
170 /* For some reason draft-ietf-ldapext-ldap-c-api-xx.txt error |
|
171 * codes in OpenLDAP are negative values from -1 to -17. |
|
172 */ |
|
173 $err = Zend_Ldap_Exception::LDAP_SERVER_DOWN + (-$err - 1); |
|
174 } |
|
175 return $err; |
|
176 } |
|
177 return 0; |
|
178 } |
|
179 |
|
180 /** |
|
181 * Return the LDAP error message of the last LDAP command |
|
182 * |
|
183 * @param int $errorCode |
|
184 * @param array $errorMessages |
|
185 * @return string |
|
186 */ |
|
187 public function getLastError(&$errorCode = null, array &$errorMessages = null) |
|
188 { |
|
189 $errorCode = $this->getLastErrorCode(); |
|
190 $errorMessages = array(); |
|
191 |
|
192 /* The various error retrieval functions can return |
|
193 * different things so we just try to collect what we |
|
194 * can and eliminate dupes. |
|
195 */ |
|
196 $estr1 = @ldap_error($this->_resource); |
|
197 if ($errorCode !== 0 && $estr1 === 'Success') { |
|
198 $estr1 = @ldap_err2str($errorCode); |
|
199 } |
|
200 if (!empty($estr1)) { |
|
201 $errorMessages[] = $estr1; |
|
202 } |
|
203 |
|
204 @ldap_get_option($this->_resource, LDAP_OPT_ERROR_STRING, $estr2); |
|
205 if (!empty($estr2) && !in_array($estr2, $errorMessages)) { |
|
206 $errorMessages[] = $estr2; |
|
207 } |
|
208 |
|
209 $message = ''; |
|
210 if ($errorCode > 0) { |
|
211 $message = '0x' . dechex($errorCode) . ' '; |
|
212 } else { |
|
213 $message = ''; |
|
214 } |
|
215 if (count($errorMessages) > 0) { |
|
216 $message .= '(' . implode('; ', $errorMessages) . ')'; |
|
217 } else { |
|
218 $message .= '(no error message from LDAP)'; |
|
219 } |
|
220 return $message; |
|
221 } |
|
222 |
|
223 /** |
|
224 * Get the currently bound user |
|
225 * |
|
226 * FALSE if no user is bound to the LDAP resource |
|
227 * NULL if there has been an anonymous bind |
|
228 * username of the currently bound user |
|
229 * |
|
230 * @return false|null|string |
|
231 */ |
|
232 public function getBoundUser() |
|
233 { |
|
234 return $this->_boundUser; |
|
235 } |
|
236 |
|
237 /** |
|
238 * Sets the options used in connecting, binding, etc. |
|
239 * |
|
240 * Valid option keys: |
|
241 * host |
|
242 * port |
|
243 * useSsl |
|
244 * username |
|
245 * password |
|
246 * bindRequiresDn |
|
247 * baseDn |
|
248 * accountCanonicalForm |
|
249 * accountDomainName |
|
250 * accountDomainNameShort |
|
251 * accountFilterFormat |
|
252 * allowEmptyPassword |
|
253 * useStartTls |
|
254 * optRefferals |
|
255 * tryUsernameSplit |
|
256 * |
|
257 * @param array|Zend_Config $options Options used in connecting, binding, etc. |
|
258 * @return Zend_Ldap Provides a fluent interface |
|
259 * @throws Zend_Ldap_Exception |
|
260 */ |
|
261 public function setOptions($options) |
|
262 { |
|
263 if ($options instanceof Zend_Config) { |
|
264 $options = $options->toArray(); |
|
265 } |
|
266 |
|
267 $permittedOptions = array( |
|
268 'host' => null, |
|
269 'port' => 0, |
|
270 'useSsl' => false, |
|
271 'username' => null, |
|
272 'password' => null, |
|
273 'bindRequiresDn' => false, |
|
274 'baseDn' => null, |
|
275 'accountCanonicalForm' => null, |
|
276 'accountDomainName' => null, |
|
277 'accountDomainNameShort' => null, |
|
278 'accountFilterFormat' => null, |
|
279 'allowEmptyPassword' => false, |
|
280 'useStartTls' => false, |
|
281 'optReferrals' => false, |
|
282 'tryUsernameSplit' => true, |
|
283 ); |
|
284 |
|
285 foreach ($permittedOptions as $key => $val) { |
|
286 if (array_key_exists($key, $options)) { |
|
287 $val = $options[$key]; |
|
288 unset($options[$key]); |
|
289 /* Enforce typing. This eliminates issues like Zend_Config_Ini |
|
290 * returning '1' as a string (ZF-3163). |
|
291 */ |
|
292 switch ($key) { |
|
293 case 'port': |
|
294 case 'accountCanonicalForm': |
|
295 $permittedOptions[$key] = (int)$val; |
|
296 break; |
|
297 case 'useSsl': |
|
298 case 'bindRequiresDn': |
|
299 case 'allowEmptyPassword': |
|
300 case 'useStartTls': |
|
301 case 'optReferrals': |
|
302 case 'tryUsernameSplit': |
|
303 $permittedOptions[$key] = ($val === true || |
|
304 $val === '1' || strcasecmp($val, 'true') == 0); |
|
305 break; |
|
306 default: |
|
307 $permittedOptions[$key] = trim($val); |
|
308 break; |
|
309 } |
|
310 } |
|
311 } |
|
312 if (count($options) > 0) { |
|
313 $key = key($options); |
|
314 /** |
|
315 * @see Zend_Ldap_Exception |
|
316 */ |
|
317 require_once 'Zend/Ldap/Exception.php'; |
|
318 throw new Zend_Ldap_Exception(null, "Unknown Zend_Ldap option: $key"); |
|
319 } |
|
320 $this->_options = $permittedOptions; |
|
321 return $this; |
|
322 } |
|
323 |
|
324 /** |
|
325 * @return array The current options. |
|
326 */ |
|
327 public function getOptions() |
|
328 { |
|
329 return $this->_options; |
|
330 } |
|
331 |
|
332 /** |
|
333 * @return string The hostname of the LDAP server being used to authenticate accounts |
|
334 */ |
|
335 protected function _getHost() |
|
336 { |
|
337 return $this->_options['host']; |
|
338 } |
|
339 |
|
340 /** |
|
341 * @return int The port of the LDAP server or 0 to indicate that no port value is set |
|
342 */ |
|
343 protected function _getPort() |
|
344 { |
|
345 return $this->_options['port']; |
|
346 } |
|
347 |
|
348 /** |
|
349 * @return boolean The default SSL / TLS encrypted transport control |
|
350 */ |
|
351 protected function _getUseSsl() |
|
352 { |
|
353 return $this->_options['useSsl']; |
|
354 } |
|
355 |
|
356 /** |
|
357 * @return string The default acctname for binding |
|
358 */ |
|
359 protected function _getUsername() |
|
360 { |
|
361 return $this->_options['username']; |
|
362 } |
|
363 |
|
364 /** |
|
365 * @return string The default password for binding |
|
366 */ |
|
367 protected function _getPassword() |
|
368 { |
|
369 return $this->_options['password']; |
|
370 } |
|
371 |
|
372 /** |
|
373 * @return boolean Bind requires DN |
|
374 */ |
|
375 protected function _getBindRequiresDn() |
|
376 { |
|
377 return $this->_options['bindRequiresDn']; |
|
378 } |
|
379 |
|
380 /** |
|
381 * Gets the base DN under which objects of interest are located |
|
382 * |
|
383 * @return string |
|
384 */ |
|
385 public function getBaseDn() |
|
386 { |
|
387 return $this->_options['baseDn']; |
|
388 } |
|
389 |
|
390 /** |
|
391 * @return integer Either ACCTNAME_FORM_BACKSLASH, ACCTNAME_FORM_PRINCIPAL or |
|
392 * ACCTNAME_FORM_USERNAME indicating the form usernames should be canonicalized to. |
|
393 */ |
|
394 protected function _getAccountCanonicalForm() |
|
395 { |
|
396 /* Account names should always be qualified with a domain. In some scenarios |
|
397 * using non-qualified account names can lead to security vulnerabilities. If |
|
398 * no account canonical form is specified, we guess based in what domain |
|
399 * names have been supplied. |
|
400 */ |
|
401 |
|
402 $accountCanonicalForm = $this->_options['accountCanonicalForm']; |
|
403 if (!$accountCanonicalForm) { |
|
404 $accountDomainName = $this->_getAccountDomainName(); |
|
405 $accountDomainNameShort = $this->_getAccountDomainNameShort(); |
|
406 if ($accountDomainNameShort) { |
|
407 $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_BACKSLASH; |
|
408 } else if ($accountDomainName) { |
|
409 $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_PRINCIPAL; |
|
410 } else { |
|
411 $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_USERNAME; |
|
412 } |
|
413 } |
|
414 |
|
415 return $accountCanonicalForm; |
|
416 } |
|
417 |
|
418 /** |
|
419 * @return string The account domain name |
|
420 */ |
|
421 protected function _getAccountDomainName() |
|
422 { |
|
423 return $this->_options['accountDomainName']; |
|
424 } |
|
425 |
|
426 /** |
|
427 * @return string The short account domain name |
|
428 */ |
|
429 protected function _getAccountDomainNameShort() |
|
430 { |
|
431 return $this->_options['accountDomainNameShort']; |
|
432 } |
|
433 |
|
434 /** |
|
435 * @return string A format string for building an LDAP search filter to match |
|
436 * an account |
|
437 */ |
|
438 protected function _getAccountFilterFormat() |
|
439 { |
|
440 return $this->_options['accountFilterFormat']; |
|
441 } |
|
442 |
|
443 /** |
|
444 * @return boolean Allow empty passwords |
|
445 */ |
|
446 protected function _getAllowEmptyPassword() |
|
447 { |
|
448 return $this->_options['allowEmptyPassword']; |
|
449 } |
|
450 |
|
451 /** |
|
452 * @return boolean The default SSL / TLS encrypted transport control |
|
453 */ |
|
454 protected function _getUseStartTls() |
|
455 { |
|
456 return $this->_options['useStartTls']; |
|
457 } |
|
458 |
|
459 /** |
|
460 * @return boolean Opt. Referrals |
|
461 */ |
|
462 protected function _getOptReferrals() |
|
463 { |
|
464 return $this->_options['optReferrals']; |
|
465 } |
|
466 |
|
467 /** |
|
468 * @return boolean Try splitting the username into username and domain |
|
469 */ |
|
470 protected function _getTryUsernameSplit() |
|
471 { |
|
472 return $this->_options['tryUsernameSplit']; |
|
473 } |
|
474 |
|
475 /** |
|
476 * @return string The LDAP search filter for matching directory accounts |
|
477 */ |
|
478 protected function _getAccountFilter($acctname) |
|
479 { |
|
480 /** |
|
481 * @see Zend_Ldap_Filter_Abstract |
|
482 */ |
|
483 require_once 'Zend/Ldap/Filter/Abstract.php'; |
|
484 $this->_splitName($acctname, $dname, $aname); |
|
485 $accountFilterFormat = $this->_getAccountFilterFormat(); |
|
486 $aname = Zend_Ldap_Filter_Abstract::escapeValue($aname); |
|
487 if ($accountFilterFormat) { |
|
488 return sprintf($accountFilterFormat, $aname); |
|
489 } |
|
490 if (!$this->_getBindRequiresDn()) { |
|
491 // is there a better way to detect this? |
|
492 return sprintf("(&(objectClass=user)(sAMAccountName=%s))", $aname); |
|
493 } |
|
494 return sprintf("(&(objectClass=posixAccount)(uid=%s))", $aname); |
|
495 } |
|
496 |
|
497 /** |
|
498 * @param string $name The name to split |
|
499 * @param string $dname The resulting domain name (this is an out parameter) |
|
500 * @param string $aname The resulting account name (this is an out parameter) |
|
501 * @return void |
|
502 */ |
|
503 protected function _splitName($name, &$dname, &$aname) |
|
504 { |
|
505 $dname = null; |
|
506 $aname = $name; |
|
507 |
|
508 if (!$this->_getTryUsernameSplit()) { |
|
509 return; |
|
510 } |
|
511 |
|
512 $pos = strpos($name, '@'); |
|
513 if ($pos) { |
|
514 $dname = substr($name, $pos + 1); |
|
515 $aname = substr($name, 0, $pos); |
|
516 } else { |
|
517 $pos = strpos($name, '\\'); |
|
518 if ($pos) { |
|
519 $dname = substr($name, 0, $pos); |
|
520 $aname = substr($name, $pos + 1); |
|
521 } |
|
522 } |
|
523 } |
|
524 |
|
525 /** |
|
526 * @param string $acctname The name of the account |
|
527 * @return string The DN of the specified account |
|
528 * @throws Zend_Ldap_Exception |
|
529 */ |
|
530 protected function _getAccountDn($acctname) |
|
531 { |
|
532 /** |
|
533 * @see Zend_Ldap_Dn |
|
534 */ |
|
535 require_once 'Zend/Ldap/Dn.php'; |
|
536 if (Zend_Ldap_Dn::checkDn($acctname)) return $acctname; |
|
537 $acctname = $this->getCanonicalAccountName($acctname, Zend_Ldap::ACCTNAME_FORM_USERNAME); |
|
538 $acct = $this->_getAccount($acctname, array('dn')); |
|
539 return $acct['dn']; |
|
540 } |
|
541 |
|
542 /** |
|
543 * @param string $dname The domain name to check |
|
544 * @return boolean |
|
545 */ |
|
546 protected function _isPossibleAuthority($dname) |
|
547 { |
|
548 if ($dname === null) { |
|
549 return true; |
|
550 } |
|
551 $accountDomainName = $this->_getAccountDomainName(); |
|
552 $accountDomainNameShort = $this->_getAccountDomainNameShort(); |
|
553 if ($accountDomainName === null && $accountDomainNameShort === null) { |
|
554 return true; |
|
555 } |
|
556 if (strcasecmp($dname, $accountDomainName) == 0) { |
|
557 return true; |
|
558 } |
|
559 if (strcasecmp($dname, $accountDomainNameShort) == 0) { |
|
560 return true; |
|
561 } |
|
562 return false; |
|
563 } |
|
564 |
|
565 /** |
|
566 * @param string $acctname The name to canonicalize |
|
567 * @param int $type The desired form of canonicalization |
|
568 * @return string The canonicalized name in the desired form |
|
569 * @throws Zend_Ldap_Exception |
|
570 */ |
|
571 public function getCanonicalAccountName($acctname, $form = 0) |
|
572 { |
|
573 $this->_splitName($acctname, $dname, $uname); |
|
574 |
|
575 if (!$this->_isPossibleAuthority($dname)) { |
|
576 /** |
|
577 * @see Zend_Ldap_Exception |
|
578 */ |
|
579 require_once 'Zend/Ldap/Exception.php'; |
|
580 throw new Zend_Ldap_Exception(null, |
|
581 "Binding domain is not an authority for user: $acctname", |
|
582 Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH); |
|
583 } |
|
584 |
|
585 if (!$uname) { |
|
586 /** |
|
587 * @see Zend_Ldap_Exception |
|
588 */ |
|
589 require_once 'Zend/Ldap/Exception.php'; |
|
590 throw new Zend_Ldap_Exception(null, "Invalid account name syntax: $acctname"); |
|
591 } |
|
592 |
|
593 if (function_exists('mb_strtolower')) { |
|
594 $uname = mb_strtolower($uname, 'UTF-8'); |
|
595 } else { |
|
596 $uname = strtolower($uname); |
|
597 } |
|
598 |
|
599 if ($form === 0) { |
|
600 $form = $this->_getAccountCanonicalForm(); |
|
601 } |
|
602 |
|
603 switch ($form) { |
|
604 case Zend_Ldap::ACCTNAME_FORM_DN: |
|
605 return $this->_getAccountDn($acctname); |
|
606 case Zend_Ldap::ACCTNAME_FORM_USERNAME: |
|
607 return $uname; |
|
608 case Zend_Ldap::ACCTNAME_FORM_BACKSLASH: |
|
609 $accountDomainNameShort = $this->_getAccountDomainNameShort(); |
|
610 if (!$accountDomainNameShort) { |
|
611 /** |
|
612 * @see Zend_Ldap_Exception |
|
613 */ |
|
614 require_once 'Zend/Ldap/Exception.php'; |
|
615 throw new Zend_Ldap_Exception(null, 'Option required: accountDomainNameShort'); |
|
616 } |
|
617 return "$accountDomainNameShort\\$uname"; |
|
618 case Zend_Ldap::ACCTNAME_FORM_PRINCIPAL: |
|
619 $accountDomainName = $this->_getAccountDomainName(); |
|
620 if (!$accountDomainName) { |
|
621 /** |
|
622 * @see Zend_Ldap_Exception |
|
623 */ |
|
624 require_once 'Zend/Ldap/Exception.php'; |
|
625 throw new Zend_Ldap_Exception(null, 'Option required: accountDomainName'); |
|
626 } |
|
627 return "$uname@$accountDomainName"; |
|
628 default: |
|
629 /** |
|
630 * @see Zend_Ldap_Exception |
|
631 */ |
|
632 require_once 'Zend/Ldap/Exception.php'; |
|
633 throw new Zend_Ldap_Exception(null, "Unknown canonical name form: $form"); |
|
634 } |
|
635 } |
|
636 |
|
637 /** |
|
638 * @param array $attrs An array of names of desired attributes |
|
639 * @return array An array of the attributes representing the account |
|
640 * @throws Zend_Ldap_Exception |
|
641 */ |
|
642 protected function _getAccount($acctname, array $attrs = null) |
|
643 { |
|
644 $baseDn = $this->getBaseDn(); |
|
645 if (!$baseDn) { |
|
646 /** |
|
647 * @see Zend_Ldap_Exception |
|
648 */ |
|
649 require_once 'Zend/Ldap/Exception.php'; |
|
650 throw new Zend_Ldap_Exception(null, 'Base DN not set'); |
|
651 } |
|
652 |
|
653 $accountFilter = $this->_getAccountFilter($acctname); |
|
654 if (!$accountFilter) { |
|
655 /** |
|
656 * @see Zend_Ldap_Exception |
|
657 */ |
|
658 require_once 'Zend/Ldap/Exception.php'; |
|
659 throw new Zend_Ldap_Exception(null, 'Invalid account filter'); |
|
660 } |
|
661 |
|
662 if (!is_resource($this->getResource())) { |
|
663 $this->bind(); |
|
664 } |
|
665 |
|
666 $accounts = $this->search($accountFilter, $baseDn, self::SEARCH_SCOPE_SUB, $attrs); |
|
667 $count = $accounts->count(); |
|
668 if ($count === 1) { |
|
669 $acct = $accounts->getFirst(); |
|
670 $accounts->close(); |
|
671 return $acct; |
|
672 } else if ($count === 0) { |
|
673 /** |
|
674 * @see Zend_Ldap_Exception |
|
675 */ |
|
676 require_once 'Zend/Ldap/Exception.php'; |
|
677 $code = Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT; |
|
678 $str = "No object found for: $accountFilter"; |
|
679 } else { |
|
680 /** |
|
681 * @see Zend_Ldap_Exception |
|
682 */ |
|
683 require_once 'Zend/Ldap/Exception.php'; |
|
684 $code = Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR; |
|
685 $str = "Unexpected result count ($count) for: $accountFilter"; |
|
686 } |
|
687 $accounts->close(); |
|
688 /** |
|
689 * @see Zend_Ldap_Exception |
|
690 */ |
|
691 require_once 'Zend/Ldap/Exception.php'; |
|
692 throw new Zend_Ldap_Exception($this, $str, $code); |
|
693 } |
|
694 |
|
695 /** |
|
696 * @return Zend_Ldap Provides a fluent interface |
|
697 */ |
|
698 public function disconnect() |
|
699 { |
|
700 if (is_resource($this->_resource)) { |
|
701 @ldap_unbind($this->_resource); |
|
702 } |
|
703 $this->_resource = null; |
|
704 $this->_boundUser = false; |
|
705 return $this; |
|
706 } |
|
707 |
|
708 /** |
|
709 * To connect using SSL it seems the client tries to verify the server |
|
710 * certificate by default. One way to disable this behavior is to set |
|
711 * 'TLS_REQCERT never' in OpenLDAP's ldap.conf and restarting Apache. Or, |
|
712 * if you really care about the server's cert you can put a cert on the |
|
713 * web server. |
|
714 * |
|
715 * @param string $host The hostname of the LDAP server to connect to |
|
716 * @param int $port The port number of the LDAP server to connect to |
|
717 * @param boolean $useSsl Use SSL |
|
718 * @param boolean $useStartTls Use STARTTLS |
|
719 * @return Zend_Ldap Provides a fluent interface |
|
720 * @throws Zend_Ldap_Exception |
|
721 */ |
|
722 public function connect($host = null, $port = null, $useSsl = null, $useStartTls = null) |
|
723 { |
|
724 if ($host === null) { |
|
725 $host = $this->_getHost(); |
|
726 } |
|
727 if ($port === null) { |
|
728 $port = $this->_getPort(); |
|
729 } else { |
|
730 $port = (int)$port; |
|
731 } |
|
732 if ($useSsl === null) { |
|
733 $useSsl = $this->_getUseSsl(); |
|
734 } else { |
|
735 $useSsl = (bool)$useSsl; |
|
736 } |
|
737 if ($useStartTls === null) { |
|
738 $useStartTls = $this->_getUseStartTls(); |
|
739 } else { |
|
740 $useStartTls = (bool)$useStartTls; |
|
741 } |
|
742 |
|
743 if (!$host) { |
|
744 /** |
|
745 * @see Zend_Ldap_Exception |
|
746 */ |
|
747 require_once 'Zend/Ldap/Exception.php'; |
|
748 throw new Zend_Ldap_Exception(null, 'A host parameter is required'); |
|
749 } |
|
750 |
|
751 $useUri = false; |
|
752 /* Because ldap_connect doesn't really try to connect, any connect error |
|
753 * will actually occur during the ldap_bind call. Therefore, we save the |
|
754 * connect string here for reporting it in error handling in bind(). |
|
755 */ |
|
756 $hosts = array(); |
|
757 if (preg_match_all('~ldap(?:i|s)?://~', $host, $hosts, PREG_SET_ORDER) > 0) { |
|
758 $this->_connectString = $host; |
|
759 $useUri = true; |
|
760 $useSsl = false; |
|
761 } else { |
|
762 if ($useSsl) { |
|
763 $this->_connectString = 'ldaps://' . $host; |
|
764 $useUri = true; |
|
765 } else { |
|
766 $this->_connectString = 'ldap://' . $host; |
|
767 } |
|
768 if ($port) { |
|
769 $this->_connectString .= ':' . $port; |
|
770 } |
|
771 } |
|
772 |
|
773 $this->disconnect(); |
|
774 |
|
775 /* Only OpenLDAP 2.2 + supports URLs so if SSL is not requested, just |
|
776 * use the old form. |
|
777 */ |
|
778 $resource = ($useUri) ? @ldap_connect($this->_connectString) : @ldap_connect($host, $port); |
|
779 |
|
780 if (is_resource($resource) === true) { |
|
781 $this->_resource = $resource; |
|
782 $this->_boundUser = false; |
|
783 |
|
784 $optReferrals = ($this->_getOptReferrals()) ? 1 : 0; |
|
785 if (@ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3) && |
|
786 @ldap_set_option($resource, LDAP_OPT_REFERRALS, $optReferrals)) { |
|
787 if ($useSsl || !$useStartTls || @ldap_start_tls($resource)) { |
|
788 return $this; |
|
789 } |
|
790 } |
|
791 |
|
792 /** |
|
793 * @see Zend_Ldap_Exception |
|
794 */ |
|
795 require_once 'Zend/Ldap/Exception.php'; |
|
796 $zle = new Zend_Ldap_Exception($this, "$host:$port"); |
|
797 $this->disconnect(); |
|
798 throw $zle; |
|
799 } |
|
800 /** |
|
801 * @see Zend_Ldap_Exception |
|
802 */ |
|
803 require_once 'Zend/Ldap/Exception.php'; |
|
804 throw new Zend_Ldap_Exception(null, "Failed to connect to LDAP server: $host:$port"); |
|
805 } |
|
806 |
|
807 /** |
|
808 * @param string $username The username for authenticating the bind |
|
809 * @param string $password The password for authenticating the bind |
|
810 * @return Zend_Ldap Provides a fluent interface |
|
811 * @throws Zend_Ldap_Exception |
|
812 */ |
|
813 public function bind($username = null, $password = null) |
|
814 { |
|
815 $moreCreds = true; |
|
816 |
|
817 if ($username === null) { |
|
818 $username = $this->_getUsername(); |
|
819 $password = $this->_getPassword(); |
|
820 $moreCreds = false; |
|
821 } |
|
822 |
|
823 if (empty($username)) { |
|
824 /* Perform anonymous bind |
|
825 */ |
|
826 $username = null; |
|
827 $password = null; |
|
828 } else { |
|
829 /* Check to make sure the username is in DN form. |
|
830 */ |
|
831 /** |
|
832 * @see Zend_Ldap_Dn |
|
833 */ |
|
834 require_once 'Zend/Ldap/Dn.php'; |
|
835 if (!Zend_Ldap_Dn::checkDn($username)) { |
|
836 if ($this->_getBindRequiresDn()) { |
|
837 /* moreCreds stops an infinite loop if _getUsername does not |
|
838 * return a DN and the bind requires it |
|
839 */ |
|
840 if ($moreCreds) { |
|
841 try { |
|
842 $username = $this->_getAccountDn($username); |
|
843 } catch (Zend_Ldap_Exception $zle) { |
|
844 switch ($zle->getCode()) { |
|
845 case Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT: |
|
846 case Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH: |
|
847 case Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED: |
|
848 throw $zle; |
|
849 } |
|
850 throw new Zend_Ldap_Exception(null, |
|
851 'Failed to retrieve DN for account: ' . $username . |
|
852 ' [' . $zle->getMessage() . ']', |
|
853 Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR); |
|
854 } |
|
855 } else { |
|
856 /** |
|
857 * @see Zend_Ldap_Exception |
|
858 */ |
|
859 require_once 'Zend/Ldap/Exception.php'; |
|
860 throw new Zend_Ldap_Exception(null, 'Binding requires username in DN form'); |
|
861 } |
|
862 } else { |
|
863 $username = $this->getCanonicalAccountName($username, |
|
864 $this->_getAccountCanonicalForm()); |
|
865 } |
|
866 } |
|
867 } |
|
868 |
|
869 if (!is_resource($this->_resource)) { |
|
870 $this->connect(); |
|
871 } |
|
872 |
|
873 if ($username !== null && $password === '' && $this->_getAllowEmptyPassword() !== true) { |
|
874 /** |
|
875 * @see Zend_Ldap_Exception |
|
876 */ |
|
877 require_once 'Zend/Ldap/Exception.php'; |
|
878 $zle = new Zend_Ldap_Exception(null, |
|
879 'Empty password not allowed - see allowEmptyPassword option.'); |
|
880 } else { |
|
881 if (@ldap_bind($this->_resource, $username, $password)) { |
|
882 $this->_boundUser = $username; |
|
883 return $this; |
|
884 } |
|
885 |
|
886 $message = ($username === null) ? $this->_connectString : $username; |
|
887 /** |
|
888 * @see Zend_Ldap_Exception |
|
889 */ |
|
890 require_once 'Zend/Ldap/Exception.php'; |
|
891 switch ($this->getLastErrorCode()) { |
|
892 case Zend_Ldap_Exception::LDAP_SERVER_DOWN: |
|
893 /* If the error is related to establishing a connection rather than binding, |
|
894 * the connect string is more informative than the username. |
|
895 */ |
|
896 $message = $this->_connectString; |
|
897 } |
|
898 |
|
899 $zle = new Zend_Ldap_Exception($this, $message); |
|
900 } |
|
901 $this->disconnect(); |
|
902 throw $zle; |
|
903 } |
|
904 |
|
905 /** |
|
906 * A global LDAP search routine for finding information. |
|
907 * |
|
908 * Options can be either passed as single parameters according to the |
|
909 * method signature or as an array with one or more of the following keys |
|
910 * - filter |
|
911 * - baseDn |
|
912 * - scope |
|
913 * - attributes |
|
914 * - sort |
|
915 * - collectionClass |
|
916 * - sizelimit |
|
917 * - timelimit |
|
918 * |
|
919 * @param string|Zend_Ldap_Filter_Abstract|array $filter |
|
920 * @param string|Zend_Ldap_Dn|null $basedn |
|
921 * @param integer $scope |
|
922 * @param array $attributes |
|
923 * @param string|null $sort |
|
924 * @param string|null $collectionClass |
|
925 * @param integer $sizelimit |
|
926 * @param integer $timelimit |
|
927 * @return Zend_Ldap_Collection |
|
928 * @throws Zend_Ldap_Exception |
|
929 */ |
|
930 public function search($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB, array $attributes = array(), |
|
931 $sort = null, $collectionClass = null, $sizelimit = 0, $timelimit = 0) |
|
932 { |
|
933 if (is_array($filter)) { |
|
934 $options = array_change_key_case($filter, CASE_LOWER); |
|
935 foreach ($options as $key => $value) { |
|
936 switch ($key) { |
|
937 case 'filter': |
|
938 case 'basedn': |
|
939 case 'scope': |
|
940 case 'sort': |
|
941 $$key = $value; |
|
942 break; |
|
943 case 'attributes': |
|
944 if (is_array($value)) { |
|
945 $attributes = $value; |
|
946 } |
|
947 break; |
|
948 case 'collectionclass': |
|
949 $collectionClass = $value; |
|
950 break; |
|
951 case 'sizelimit': |
|
952 case 'timelimit': |
|
953 $$key = (int)$value; |
|
954 } |
|
955 } |
|
956 } |
|
957 |
|
958 if ($basedn === null) { |
|
959 $basedn = $this->getBaseDn(); |
|
960 } |
|
961 else if ($basedn instanceof Zend_Ldap_Dn) { |
|
962 $basedn = $basedn->toString(); |
|
963 } |
|
964 |
|
965 if ($filter instanceof Zend_Ldap_Filter_Abstract) { |
|
966 $filter = $filter->toString(); |
|
967 } |
|
968 |
|
969 switch ($scope) { |
|
970 case self::SEARCH_SCOPE_ONE: |
|
971 $search = @ldap_list($this->getResource(), $basedn, $filter, $attributes, 0, $sizelimit, $timelimit); |
|
972 break; |
|
973 case self::SEARCH_SCOPE_BASE: |
|
974 $search = @ldap_read($this->getResource(), $basedn, $filter, $attributes, 0, $sizelimit, $timelimit); |
|
975 break; |
|
976 case self::SEARCH_SCOPE_SUB: |
|
977 default: |
|
978 $search = @ldap_search($this->getResource(), $basedn, $filter, $attributes, 0, $sizelimit, $timelimit); |
|
979 break; |
|
980 } |
|
981 |
|
982 if($search === false) { |
|
983 /** |
|
984 * @see Zend_Ldap_Exception |
|
985 */ |
|
986 require_once 'Zend/Ldap/Exception.php'; |
|
987 throw new Zend_Ldap_Exception($this, 'searching: ' . $filter); |
|
988 } |
|
989 if ($sort !== null && is_string($sort)) { |
|
990 $isSorted = @ldap_sort($this->getResource(), $search, $sort); |
|
991 if($isSorted === false) { |
|
992 /** |
|
993 * @see Zend_Ldap_Exception |
|
994 */ |
|
995 require_once 'Zend/Ldap/Exception.php'; |
|
996 throw new Zend_Ldap_Exception($this, 'sorting: ' . $sort); |
|
997 } |
|
998 } |
|
999 |
|
1000 /** |
|
1001 * Zend_Ldap_Collection_Iterator_Default |
|
1002 */ |
|
1003 require_once 'Zend/Ldap/Collection/Iterator/Default.php'; |
|
1004 $iterator = new Zend_Ldap_Collection_Iterator_Default($this, $search); |
|
1005 return $this->_createCollection($iterator, $collectionClass); |
|
1006 } |
|
1007 |
|
1008 /** |
|
1009 * Extension point for collection creation |
|
1010 * |
|
1011 * @param Zend_Ldap_Collection_Iterator_Default $iterator |
|
1012 * @param string|null $collectionClass |
|
1013 * @return Zend_Ldap_Collection |
|
1014 * @throws Zend_Ldap_Exception |
|
1015 */ |
|
1016 protected function _createCollection(Zend_Ldap_Collection_Iterator_Default $iterator, $collectionClass) |
|
1017 { |
|
1018 if ($collectionClass === null) { |
|
1019 /** |
|
1020 * Zend_Ldap_Collection |
|
1021 */ |
|
1022 require_once 'Zend/Ldap/Collection.php'; |
|
1023 return new Zend_Ldap_Collection($iterator); |
|
1024 } else { |
|
1025 $collectionClass = (string)$collectionClass; |
|
1026 if (!class_exists($collectionClass)) { |
|
1027 /** |
|
1028 * @see Zend_Ldap_Exception |
|
1029 */ |
|
1030 require_once 'Zend/Ldap/Exception.php'; |
|
1031 throw new Zend_Ldap_Exception(null, |
|
1032 "Class '$collectionClass' can not be found"); |
|
1033 } |
|
1034 if (!is_subclass_of($collectionClass, 'Zend_Ldap_Collection')) { |
|
1035 /** |
|
1036 * @see Zend_Ldap_Exception |
|
1037 */ |
|
1038 require_once 'Zend/Ldap/Exception.php'; |
|
1039 throw new Zend_Ldap_Exception(null, |
|
1040 "Class '$collectionClass' must subclass 'Zend_Ldap_Collection'"); |
|
1041 } |
|
1042 return new $collectionClass($iterator); |
|
1043 } |
|
1044 } |
|
1045 |
|
1046 /** |
|
1047 * Count items found by given filter. |
|
1048 * |
|
1049 * @param string|Zend_Ldap_Filter_Abstract $filter |
|
1050 * @param string|Zend_Ldap_Dn|null $basedn |
|
1051 * @param integer $scope |
|
1052 * @return integer |
|
1053 * @throws Zend_Ldap_Exception |
|
1054 */ |
|
1055 public function count($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB) |
|
1056 { |
|
1057 try { |
|
1058 $result = $this->search($filter, $basedn, $scope, array('dn'), null); |
|
1059 } catch (Zend_Ldap_Exception $e) { |
|
1060 if ($e->getCode() === Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT) return 0; |
|
1061 else throw $e; |
|
1062 } |
|
1063 return $result->count(); |
|
1064 } |
|
1065 |
|
1066 /** |
|
1067 * Count children for a given DN. |
|
1068 * |
|
1069 * @param string|Zend_Ldap_Dn $dn |
|
1070 * @return integer |
|
1071 * @throws Zend_Ldap_Exception |
|
1072 */ |
|
1073 public function countChildren($dn) |
|
1074 { |
|
1075 return $this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_ONE); |
|
1076 } |
|
1077 |
|
1078 /** |
|
1079 * Check if a given DN exists. |
|
1080 * |
|
1081 * @param string|Zend_Ldap_Dn $dn |
|
1082 * @return boolean |
|
1083 * @throws Zend_Ldap_Exception |
|
1084 */ |
|
1085 public function exists($dn) |
|
1086 { |
|
1087 return ($this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_BASE) == 1); |
|
1088 } |
|
1089 |
|
1090 /** |
|
1091 * Search LDAP registry for entries matching filter and optional attributes |
|
1092 * |
|
1093 * Options can be either passed as single parameters according to the |
|
1094 * method signature or as an array with one or more of the following keys |
|
1095 * - filter |
|
1096 * - baseDn |
|
1097 * - scope |
|
1098 * - attributes |
|
1099 * - sort |
|
1100 * - reverseSort |
|
1101 * - sizelimit |
|
1102 * - timelimit |
|
1103 * |
|
1104 * @param string|Zend_Ldap_Filter_Abstract|array $filter |
|
1105 * @param string|Zend_Ldap_Dn|null $basedn |
|
1106 * @param integer $scope |
|
1107 * @param array $attributes |
|
1108 * @param string|null $sort |
|
1109 * @param boolean $reverseSort |
|
1110 * @param integer $sizelimit |
|
1111 * @param integer $timelimit |
|
1112 * @return array |
|
1113 * @throws Zend_Ldap_Exception |
|
1114 */ |
|
1115 public function searchEntries($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB, |
|
1116 array $attributes = array(), $sort = null, $reverseSort = false, $sizelimit = 0, $timelimit = 0) |
|
1117 { |
|
1118 if (is_array($filter)) { |
|
1119 $filter = array_change_key_case($filter, CASE_LOWER); |
|
1120 if (isset($filter['collectionclass'])) { |
|
1121 unset($filter['collectionclass']); |
|
1122 } |
|
1123 if (isset($filter['reversesort'])) { |
|
1124 $reverseSort = $filter['reversesort']; |
|
1125 unset($filter['reversesort']); |
|
1126 } |
|
1127 } |
|
1128 $result = $this->search($filter, $basedn, $scope, $attributes, $sort, null, $sizelimit, $timelimit); |
|
1129 $items = $result->toArray(); |
|
1130 if ((bool)$reverseSort === true) { |
|
1131 $items = array_reverse($items, false); |
|
1132 } |
|
1133 return $items; |
|
1134 } |
|
1135 |
|
1136 /** |
|
1137 * Get LDAP entry by DN |
|
1138 * |
|
1139 * @param string|Zend_Ldap_Dn $dn |
|
1140 * @param array $attributes |
|
1141 * @param boolean $throwOnNotFound |
|
1142 * @return array |
|
1143 * @throws Zend_Ldap_Exception |
|
1144 */ |
|
1145 public function getEntry($dn, array $attributes = array(), $throwOnNotFound = false) |
|
1146 { |
|
1147 try { |
|
1148 $result = $this->search("(objectClass=*)", $dn, self::SEARCH_SCOPE_BASE, |
|
1149 $attributes, null); |
|
1150 return $result->getFirst(); |
|
1151 } catch (Zend_Ldap_Exception $e){ |
|
1152 if ($throwOnNotFound !== false) throw $e; |
|
1153 } |
|
1154 return null; |
|
1155 } |
|
1156 |
|
1157 /** |
|
1158 * Prepares an ldap data entry array for insert/update operation |
|
1159 * |
|
1160 * @param array $entry |
|
1161 * @return void |
|
1162 * @throws InvalidArgumentException |
|
1163 */ |
|
1164 public static function prepareLdapEntryArray(array &$entry) |
|
1165 { |
|
1166 if (array_key_exists('dn', $entry)) unset($entry['dn']); |
|
1167 foreach ($entry as $key => $value) { |
|
1168 if (is_array($value)) { |
|
1169 foreach ($value as $i => $v) { |
|
1170 if ($v === null) unset($value[$i]); |
|
1171 else if (!is_scalar($v)) { |
|
1172 throw new InvalidArgumentException('Only scalar values allowed in LDAP data'); |
|
1173 } else { |
|
1174 $v = (string)$v; |
|
1175 if (strlen($v) == 0) { |
|
1176 unset($value[$i]); |
|
1177 } else { |
|
1178 $value[$i] = $v; |
|
1179 } |
|
1180 } |
|
1181 } |
|
1182 $entry[$key] = array_values($value); |
|
1183 } else { |
|
1184 if ($value === null) $entry[$key] = array(); |
|
1185 else if (!is_scalar($value)) { |
|
1186 throw new InvalidArgumentException('Only scalar values allowed in LDAP data'); |
|
1187 } else { |
|
1188 $value = (string)$value; |
|
1189 if (strlen($value) == 0) { |
|
1190 $entry[$key] = array(); |
|
1191 } else { |
|
1192 $entry[$key] = array($value); |
|
1193 } |
|
1194 } |
|
1195 } |
|
1196 } |
|
1197 $entry = array_change_key_case($entry, CASE_LOWER); |
|
1198 } |
|
1199 |
|
1200 /** |
|
1201 * Add new information to the LDAP repository |
|
1202 * |
|
1203 * @param string|Zend_Ldap_Dn $dn |
|
1204 * @param array $entry |
|
1205 * @return Zend_Ldap Provides a fluid interface |
|
1206 * @throws Zend_Ldap_Exception |
|
1207 */ |
|
1208 public function add($dn, array $entry) |
|
1209 { |
|
1210 if (!($dn instanceof Zend_Ldap_Dn)) { |
|
1211 $dn = Zend_Ldap_Dn::factory($dn, null); |
|
1212 } |
|
1213 self::prepareLdapEntryArray($entry); |
|
1214 foreach ($entry as $key => $value) { |
|
1215 if (is_array($value) && count($value) === 0) { |
|
1216 unset($entry[$key]); |
|
1217 } |
|
1218 } |
|
1219 |
|
1220 $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER); |
|
1221 foreach ($rdnParts as $key => $value) { |
|
1222 $value = Zend_Ldap_Dn::unescapeValue($value); |
|
1223 if (!array_key_exists($key, $entry)) { |
|
1224 $entry[$key] = array($value); |
|
1225 } else if (!in_array($value, $entry[$key])) { |
|
1226 $entry[$key] = array_merge(array($value), $entry[$key]); |
|
1227 } |
|
1228 } |
|
1229 $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory', |
|
1230 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated'); |
|
1231 foreach ($adAttributes as $attr) { |
|
1232 if (array_key_exists($attr, $entry)) { |
|
1233 unset($entry[$attr]); |
|
1234 } |
|
1235 } |
|
1236 |
|
1237 $isAdded = @ldap_add($this->getResource(), $dn->toString(), $entry); |
|
1238 if($isAdded === false) { |
|
1239 /** |
|
1240 * @see Zend_Ldap_Exception |
|
1241 */ |
|
1242 require_once 'Zend/Ldap/Exception.php'; |
|
1243 throw new Zend_Ldap_Exception($this, 'adding: ' . $dn->toString()); |
|
1244 } |
|
1245 return $this; |
|
1246 } |
|
1247 |
|
1248 /** |
|
1249 * Update LDAP registry |
|
1250 * |
|
1251 * @param string|Zend_Ldap_Dn $dn |
|
1252 * @param array $entry |
|
1253 * @return Zend_Ldap Provides a fluid interface |
|
1254 * @throws Zend_Ldap_Exception |
|
1255 */ |
|
1256 public function update($dn, array $entry) |
|
1257 { |
|
1258 if (!($dn instanceof Zend_Ldap_Dn)) { |
|
1259 $dn = Zend_Ldap_Dn::factory($dn, null); |
|
1260 } |
|
1261 self::prepareLdapEntryArray($entry); |
|
1262 |
|
1263 $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER); |
|
1264 foreach ($rdnParts as $key => $value) { |
|
1265 $value = Zend_Ldap_Dn::unescapeValue($value); |
|
1266 if (array_key_exists($key, $entry) && !in_array($value, $entry[$key])) { |
|
1267 $entry[$key] = array_merge(array($value), $entry[$key]); |
|
1268 } |
|
1269 } |
|
1270 |
|
1271 $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory', |
|
1272 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated'); |
|
1273 foreach ($adAttributes as $attr) { |
|
1274 if (array_key_exists($attr, $entry)) { |
|
1275 unset($entry[$attr]); |
|
1276 } |
|
1277 } |
|
1278 |
|
1279 if (count($entry) > 0) { |
|
1280 $isModified = @ldap_modify($this->getResource(), $dn->toString(), $entry); |
|
1281 if($isModified === false) { |
|
1282 /** |
|
1283 * @see Zend_Ldap_Exception |
|
1284 */ |
|
1285 require_once 'Zend/Ldap/Exception.php'; |
|
1286 throw new Zend_Ldap_Exception($this, 'updating: ' . $dn->toString()); |
|
1287 } |
|
1288 } |
|
1289 return $this; |
|
1290 } |
|
1291 |
|
1292 /** |
|
1293 * Save entry to LDAP registry. |
|
1294 * |
|
1295 * Internally decides if entry will be updated to added by calling |
|
1296 * {@link exists()}. |
|
1297 * |
|
1298 * @param string|Zend_Ldap_Dn $dn |
|
1299 * @param array $entry |
|
1300 * @return Zend_Ldap Provides a fluid interface |
|
1301 * @throws Zend_Ldap_Exception |
|
1302 */ |
|
1303 public function save($dn, array $entry) |
|
1304 { |
|
1305 if ($dn instanceof Zend_Ldap_Dn) { |
|
1306 $dn = $dn->toString(); |
|
1307 } |
|
1308 if ($this->exists($dn)) $this->update($dn, $entry); |
|
1309 else $this->add($dn, $entry); |
|
1310 return $this; |
|
1311 } |
|
1312 |
|
1313 /** |
|
1314 * Delete an LDAP entry |
|
1315 * |
|
1316 * @param string|Zend_Ldap_Dn $dn |
|
1317 * @param boolean $recursively |
|
1318 * @return Zend_Ldap Provides a fluid interface |
|
1319 * @throws Zend_Ldap_Exception |
|
1320 */ |
|
1321 public function delete($dn, $recursively = false) |
|
1322 { |
|
1323 if ($dn instanceof Zend_Ldap_Dn) { |
|
1324 $dn = $dn->toString(); |
|
1325 } |
|
1326 if ($recursively === true) { |
|
1327 if ($this->countChildren($dn)>0) { |
|
1328 $children = $this->_getChildrenDns($dn); |
|
1329 foreach ($children as $c) { |
|
1330 $this->delete($c, true); |
|
1331 } |
|
1332 } |
|
1333 } |
|
1334 $isDeleted = @ldap_delete($this->getResource(), $dn); |
|
1335 if($isDeleted === false) { |
|
1336 /** |
|
1337 * @see Zend_Ldap_Exception |
|
1338 */ |
|
1339 require_once 'Zend/Ldap/Exception.php'; |
|
1340 throw new Zend_Ldap_Exception($this, 'deleting: ' . $dn); |
|
1341 } |
|
1342 return $this; |
|
1343 } |
|
1344 |
|
1345 /** |
|
1346 * Retrieve the immediate children DNs of the given $parentDn |
|
1347 * |
|
1348 * This method is used in recursive methods like {@see delete()} |
|
1349 * or {@see copy()} |
|
1350 * |
|
1351 * @param string|Zend_Ldap_Dn $parentDn |
|
1352 * @return array of DNs |
|
1353 */ |
|
1354 protected function _getChildrenDns($parentDn) |
|
1355 { |
|
1356 if ($parentDn instanceof Zend_Ldap_Dn) { |
|
1357 $parentDn = $parentDn->toString(); |
|
1358 } |
|
1359 $children = array(); |
|
1360 $search = @ldap_list($this->getResource(), $parentDn, '(objectClass=*)', array('dn')); |
|
1361 for ($entry = @ldap_first_entry($this->getResource(), $search); |
|
1362 $entry !== false; |
|
1363 $entry = @ldap_next_entry($this->getResource(), $entry)) { |
|
1364 $childDn = @ldap_get_dn($this->getResource(), $entry); |
|
1365 if ($childDn === false) { |
|
1366 /** |
|
1367 * @see Zend_Ldap_Exception |
|
1368 */ |
|
1369 require_once 'Zend/Ldap/Exception.php'; |
|
1370 throw new Zend_Ldap_Exception($this, 'getting dn'); |
|
1371 } |
|
1372 $children[] = $childDn; |
|
1373 } |
|
1374 @ldap_free_result($search); |
|
1375 return $children; |
|
1376 } |
|
1377 |
|
1378 /** |
|
1379 * Moves a LDAP entry from one DN to another subtree. |
|
1380 * |
|
1381 * @param string|Zend_Ldap_Dn $from |
|
1382 * @param string|Zend_Ldap_Dn $to |
|
1383 * @param boolean $recursively |
|
1384 * @param boolean $alwaysEmulate |
|
1385 * @return Zend_Ldap Provides a fluid interface |
|
1386 * @throws Zend_Ldap_Exception |
|
1387 */ |
|
1388 public function moveToSubtree($from, $to, $recursively = false, $alwaysEmulate = false) |
|
1389 { |
|
1390 if ($from instanceof Zend_Ldap_Dn) { |
|
1391 $orgDnParts = $from->toArray(); |
|
1392 } else { |
|
1393 $orgDnParts = Zend_Ldap_Dn::explodeDn($from); |
|
1394 } |
|
1395 |
|
1396 if ($to instanceof Zend_Ldap_Dn) { |
|
1397 $newParentDnParts = $to->toArray(); |
|
1398 } else { |
|
1399 $newParentDnParts = Zend_Ldap_Dn::explodeDn($to); |
|
1400 } |
|
1401 |
|
1402 $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts); |
|
1403 $newDn = Zend_Ldap_Dn::fromArray($newDnParts); |
|
1404 return $this->rename($from, $newDn, $recursively, $alwaysEmulate); |
|
1405 } |
|
1406 |
|
1407 /** |
|
1408 * Moves a LDAP entry from one DN to another DN. |
|
1409 * |
|
1410 * This is an alias for {@link rename()} |
|
1411 * |
|
1412 * @param string|Zend_Ldap_Dn $from |
|
1413 * @param string|Zend_Ldap_Dn $to |
|
1414 * @param boolean $recursively |
|
1415 * @param boolean $alwaysEmulate |
|
1416 * @return Zend_Ldap Provides a fluid interface |
|
1417 * @throws Zend_Ldap_Exception |
|
1418 */ |
|
1419 public function move($from, $to, $recursively = false, $alwaysEmulate = false) |
|
1420 { |
|
1421 return $this->rename($from, $to, $recursively, $alwaysEmulate); |
|
1422 } |
|
1423 |
|
1424 /** |
|
1425 * Renames a LDAP entry from one DN to another DN. |
|
1426 * |
|
1427 * This method implicitely moves the entry to another location within the tree. |
|
1428 * |
|
1429 * @param string|Zend_Ldap_Dn $from |
|
1430 * @param string|Zend_Ldap_Dn $to |
|
1431 * @param boolean $recursively |
|
1432 * @param boolean $alwaysEmulate |
|
1433 * @return Zend_Ldap Provides a fluid interface |
|
1434 * @throws Zend_Ldap_Exception |
|
1435 */ |
|
1436 public function rename($from, $to, $recursively = false, $alwaysEmulate = false) |
|
1437 { |
|
1438 $emulate = (bool)$alwaysEmulate; |
|
1439 if (!function_exists('ldap_rename')) $emulate = true; |
|
1440 else if ($recursively) $emulate = true; |
|
1441 |
|
1442 if ($emulate === false) { |
|
1443 if ($from instanceof Zend_Ldap_Dn) { |
|
1444 $from = $from->toString(); |
|
1445 } |
|
1446 |
|
1447 if ($to instanceof Zend_Ldap_Dn) { |
|
1448 $newDnParts = $to->toArray(); |
|
1449 } else { |
|
1450 $newDnParts = Zend_Ldap_Dn::explodeDn($to); |
|
1451 } |
|
1452 |
|
1453 $newRdn = Zend_Ldap_Dn::implodeRdn(array_shift($newDnParts)); |
|
1454 $newParent = Zend_Ldap_Dn::implodeDn($newDnParts); |
|
1455 $isOK = @ldap_rename($this->getResource(), $from, $newRdn, $newParent, true); |
|
1456 if($isOK === false) { |
|
1457 /** |
|
1458 * @see Zend_Ldap_Exception |
|
1459 */ |
|
1460 require_once 'Zend/Ldap/Exception.php'; |
|
1461 throw new Zend_Ldap_Exception($this, 'renaming ' . $from . ' to ' . $to); |
|
1462 } |
|
1463 else if (!$this->exists($to)) $emulate = true; |
|
1464 } |
|
1465 if ($emulate) { |
|
1466 $this->copy($from, $to, $recursively); |
|
1467 $this->delete($from, $recursively); |
|
1468 } |
|
1469 return $this; |
|
1470 } |
|
1471 |
|
1472 /** |
|
1473 * Copies a LDAP entry from one DN to another subtree. |
|
1474 * |
|
1475 * @param string|Zend_Ldap_Dn $from |
|
1476 * @param string|Zend_Ldap_Dn $to |
|
1477 * @param boolean $recursively |
|
1478 * @return Zend_Ldap Provides a fluid interface |
|
1479 * @throws Zend_Ldap_Exception |
|
1480 */ |
|
1481 public function copyToSubtree($from, $to, $recursively = false) |
|
1482 { |
|
1483 if ($from instanceof Zend_Ldap_Dn) { |
|
1484 $orgDnParts = $from->toArray(); |
|
1485 } else { |
|
1486 $orgDnParts = Zend_Ldap_Dn::explodeDn($from); |
|
1487 } |
|
1488 |
|
1489 if ($to instanceof Zend_Ldap_Dn) { |
|
1490 $newParentDnParts = $to->toArray(); |
|
1491 } else { |
|
1492 $newParentDnParts = Zend_Ldap_Dn::explodeDn($to); |
|
1493 } |
|
1494 |
|
1495 $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts); |
|
1496 $newDn = Zend_Ldap_Dn::fromArray($newDnParts); |
|
1497 return $this->copy($from, $newDn, $recursively); |
|
1498 } |
|
1499 |
|
1500 /** |
|
1501 * Copies a LDAP entry from one DN to another DN. |
|
1502 * |
|
1503 * @param string|Zend_Ldap_Dn $from |
|
1504 * @param string|Zend_Ldap_Dn $to |
|
1505 * @param boolean $recursively |
|
1506 * @return Zend_Ldap Provides a fluid interface |
|
1507 * @throws Zend_Ldap_Exception |
|
1508 */ |
|
1509 public function copy($from, $to, $recursively = false) |
|
1510 { |
|
1511 $entry = $this->getEntry($from, array(), true); |
|
1512 |
|
1513 if ($to instanceof Zend_Ldap_Dn) { |
|
1514 $toDnParts = $to->toArray(); |
|
1515 } else { |
|
1516 $toDnParts = Zend_Ldap_Dn::explodeDn($to); |
|
1517 } |
|
1518 $this->add($to, $entry); |
|
1519 |
|
1520 if ($recursively === true && $this->countChildren($from)>0) { |
|
1521 $children = $this->_getChildrenDns($from); |
|
1522 foreach ($children as $c) { |
|
1523 $cDnParts = Zend_Ldap_Dn::explodeDn($c); |
|
1524 $newChildParts = array_merge(array(array_shift($cDnParts)), $toDnParts); |
|
1525 $newChild = Zend_Ldap_Dn::implodeDn($newChildParts); |
|
1526 $this->copy($c, $newChild, true); |
|
1527 } |
|
1528 } |
|
1529 return $this; |
|
1530 } |
|
1531 |
|
1532 /** |
|
1533 * Returns the specified DN as a Zend_Ldap_Node |
|
1534 * |
|
1535 * @param string|Zend_Ldap_Dn $dn |
|
1536 * @return Zend_Ldap_Node|null |
|
1537 * @throws Zend_Ldap_Exception |
|
1538 */ |
|
1539 public function getNode($dn) |
|
1540 { |
|
1541 /** |
|
1542 * Zend_Ldap_Node |
|
1543 */ |
|
1544 require_once 'Zend/Ldap/Node.php'; |
|
1545 return Zend_Ldap_Node::fromLdap($dn, $this); |
|
1546 } |
|
1547 |
|
1548 /** |
|
1549 * Returns the base node as a Zend_Ldap_Node |
|
1550 * |
|
1551 * @return Zend_Ldap_Node |
|
1552 * @throws Zend_Ldap_Exception |
|
1553 */ |
|
1554 public function getBaseNode() |
|
1555 { |
|
1556 return $this->getNode($this->getBaseDn(), $this); |
|
1557 } |
|
1558 |
|
1559 /** |
|
1560 * Returns the RootDSE |
|
1561 * |
|
1562 * @return Zend_Ldap_Node_RootDse |
|
1563 * @throws Zend_Ldap_Exception |
|
1564 */ |
|
1565 public function getRootDse() |
|
1566 { |
|
1567 if ($this->_rootDse === null) { |
|
1568 /** |
|
1569 * @see Zend_Ldap_Node_Schema |
|
1570 */ |
|
1571 require_once 'Zend/Ldap/Node/RootDse.php'; |
|
1572 $this->_rootDse = Zend_Ldap_Node_RootDse::create($this); |
|
1573 } |
|
1574 return $this->_rootDse; |
|
1575 } |
|
1576 |
|
1577 /** |
|
1578 * Returns the schema |
|
1579 * |
|
1580 * @return Zend_Ldap_Node_Schema |
|
1581 * @throws Zend_Ldap_Exception |
|
1582 */ |
|
1583 public function getSchema() |
|
1584 { |
|
1585 if ($this->_schema === null) { |
|
1586 /** |
|
1587 * @see Zend_Ldap_Node_Schema |
|
1588 */ |
|
1589 require_once 'Zend/Ldap/Node/Schema.php'; |
|
1590 $this->_schema = Zend_Ldap_Node_Schema::create($this); |
|
1591 } |
|
1592 return $this->_schema; |
|
1593 } |
|
1594 } |