root / drupal7 / sites / all / modules / ldap / ldap_servers / LdapServer.class.php @ 91af538d
1 |
<?php
|
---|---|
2 |
|
3 |
/**
|
4 |
* @file
|
5 |
* Defines server classes and related functions.
|
6 |
*/
|
7 |
|
8 |
/**
|
9 |
* TODO check if this already exists or find a better place for this function.
|
10 |
*
|
11 |
* Formats a ldap-entry ready to be printed on console.
|
12 |
* TODO describe preconditions for ldap_entry.
|
13 |
*/
|
14 |
function pretty_print_ldap_entry($ldap_entry) { |
15 |
$m = [];
|
16 |
for ($i = 0; $i < $ldap_entry['count']; $i++) { |
17 |
$k = $ldap_entry[$i]; |
18 |
$v = $ldap_entry[$k]; |
19 |
if (is_array($v)) { |
20 |
$m2 = [];
|
21 |
$max = $v['count'] > 3 ? 3 : $v['count']; |
22 |
for ($j = 0; $j < $max; $j++) { |
23 |
$m2[] = $v[$j]; |
24 |
} |
25 |
$v = "(" . join(", ", $m2) . ")"; |
26 |
} |
27 |
$m[] = $k . ": " . $v; |
28 |
} |
29 |
return join(", ", $m); |
30 |
} |
31 |
|
32 |
/**
|
33 |
* LDAP Server Class.
|
34 |
*
|
35 |
* This class is used to create, work with, and eventually destroy ldap_server
|
36 |
* objects.
|
37 |
*
|
38 |
* @todo make bindpw protected
|
39 |
*/
|
40 |
class LdapServer { |
41 |
|
42 |
const LDAP_CONNECT_ERROR = 0x5b; |
43 |
const LDAP_SUCCESS = 0x00; |
44 |
const LDAP_OPERATIONS_ERROR = 0x01; |
45 |
const LDAP_PROTOCOL_ERROR = 0x02; |
46 |
|
47 |
public $sid; |
48 |
public $numericSid; |
49 |
public $name; |
50 |
public $status; |
51 |
public $ldap_type; |
52 |
public $address; |
53 |
public $port = 389; |
54 |
public $tls = FALSE; |
55 |
public $followrefs = FALSE; |
56 |
public $bind_method = 0; |
57 |
public $basedn = []; |
58 |
|
59 |
/**
|
60 |
* Default to an anonymous bind.
|
61 |
*/
|
62 |
public $binddn = FALSE; |
63 |
|
64 |
/**
|
65 |
* Default to an anonymous bind.
|
66 |
*/
|
67 |
public $bindpw = FALSE; |
68 |
|
69 |
public $user_dn_expression; |
70 |
public $user_attr; |
71 |
|
72 |
/**
|
73 |
* Lowercase.
|
74 |
*/
|
75 |
public $account_name_attr; |
76 |
|
77 |
/**
|
78 |
* Lowercase.
|
79 |
*/
|
80 |
public $mail_attr; |
81 |
public $mail_template; |
82 |
public $picture_attr; |
83 |
|
84 |
/**
|
85 |
* Lowercase.
|
86 |
*/
|
87 |
public $unique_persistent_attr; |
88 |
public $unique_persistent_attr_binary = FALSE; |
89 |
public $ldapToDrupalUserPhp; |
90 |
public $testingDrupalUsername; |
91 |
public $testingDrupalUserDn; |
92 |
public $detailed_watchdog_log; |
93 |
public $editPath; |
94 |
|
95 |
/**
|
96 |
* Can this server be queried without user credentials provided?
|
97 |
*/
|
98 |
public $queriableWithoutUserCredentials = FALSE; |
99 |
|
100 |
/**
|
101 |
* Array of attributes needed keyed on $op such as 'user_update'.
|
102 |
*/
|
103 |
public $userAttributeNeededCache = []; |
104 |
|
105 |
public $groupFunctionalityUnused = 0; |
106 |
public $groupObjectClass; |
107 |
|
108 |
/**
|
109 |
* 1 | 0.
|
110 |
*/
|
111 |
public $groupNested = 0; |
112 |
public $groupDeriveFromDn = FALSE; |
113 |
|
114 |
/**
|
115 |
* Lowercase.
|
116 |
*/
|
117 |
public $groupDeriveFromDnAttr = NULL; |
118 |
|
119 |
/**
|
120 |
* Does a user attribute containing groups exist?
|
121 |
*/
|
122 |
public $groupUserMembershipsAttrExists = FALSE; |
123 |
|
124 |
/**
|
125 |
* Lowercase name of user attribute containing groups.
|
126 |
*/
|
127 |
public $groupUserMembershipsAttr = NULL; |
128 |
/**
|
129 |
* User attribute containing memberships is configured enough to use.
|
130 |
*/
|
131 |
public $groupUserMembershipsConfigured = FALSE; |
132 |
|
133 |
/**
|
134 |
* Lowercase // members, uniquemember, memberUid.
|
135 |
*/
|
136 |
public $groupMembershipsAttr = NULL; |
137 |
|
138 |
|
139 |
/**
|
140 |
* Lowercase // dn, cn, etc contained in groupMembershipsAttr.
|
141 |
*/
|
142 |
public $groupMembershipsAttrMatchingUserAttr = NULL; |
143 |
|
144 |
/**
|
145 |
* Are groupMembershipsAttrMatchingUserAttr and
|
146 |
* groupGroupEntryMembershipsConfigured populated.
|
147 |
*/
|
148 |
public $groupGroupEntryMembershipsConfigured = FALSE; |
149 |
|
150 |
public $groupTestGroupDn = NULL; |
151 |
public $groupTestGroupDnWriteable = NULL; |
152 |
|
153 |
private $group_properties = [ |
154 |
'groupObjectClass',
|
155 |
'groupNested',
|
156 |
'groupDeriveFromDn',
|
157 |
'groupDeriveFromDnAttr',
|
158 |
'groupUserMembershipsAttrExists',
|
159 |
'groupUserMembershipsAttr',
|
160 |
'groupMembershipsAttrMatchingUserAttr',
|
161 |
'groupTestGroupDn',
|
162 |
'groupTestGroupDnWriteable',
|
163 |
]; |
164 |
|
165 |
public $paginationEnabled = FALSE; |
166 |
public $searchPagination = FALSE; |
167 |
public $searchPageSize = 1000; |
168 |
public $searchPageStart = 0; |
169 |
public $searchPageEnd = NULL; |
170 |
|
171 |
public $inDatabase = FALSE; |
172 |
public $connection; |
173 |
|
174 |
/**
|
175 |
* Direct mapping of db to object properties.
|
176 |
*
|
177 |
* @return array
|
178 |
*/
|
179 |
public static function field_to_properties_map() { |
180 |
return [
|
181 |
'sid' => 'sid', |
182 |
'numeric_sid' => 'numericSid', |
183 |
'name' => 'name' , |
184 |
'status' => 'status', |
185 |
'ldap_type' => 'ldap_type', |
186 |
'address' => 'address', |
187 |
'port' => 'port', |
188 |
'tls' => 'tls', |
189 |
'followrefs' => 'followrefs', |
190 |
'bind_method' => 'bind_method', |
191 |
'basedn' => 'basedn', |
192 |
'binddn' => 'binddn', |
193 |
'user_dn_expression' => 'user_dn_expression', |
194 |
'user_attr' => 'user_attr', |
195 |
'account_name_attr' => 'account_name_attr', |
196 |
'mail_attr' => 'mail_attr', |
197 |
'mail_template' => 'mail_template', |
198 |
'picture_attr' => 'picture_attr', |
199 |
'unique_persistent_attr' => 'unique_persistent_attr', |
200 |
'unique_persistent_attr_binary' => 'unique_persistent_attr_binary', |
201 |
'ldap_to_drupal_user' => 'ldapToDrupalUserPhp', |
202 |
'testing_drupal_username' => 'testingDrupalUsername', |
203 |
'testing_drupal_user_dn' => 'testingDrupalUserDn', |
204 |
|
205 |
'grp_unused' => 'groupFunctionalityUnused', |
206 |
'grp_object_cat' => 'groupObjectClass', |
207 |
'grp_nested' => 'groupNested', |
208 |
'grp_user_memb_attr_exists' => 'groupUserMembershipsAttrExists', |
209 |
'grp_user_memb_attr' => 'groupUserMembershipsAttr', |
210 |
'grp_memb_attr' => 'groupMembershipsAttr', |
211 |
'grp_memb_attr_match_user_attr' => 'groupMembershipsAttrMatchingUserAttr', |
212 |
'grp_derive_from_dn' => 'groupDeriveFromDn', |
213 |
'grp_derive_from_dn_attr' => 'groupDeriveFromDnAttr', |
214 |
'grp_test_grp_dn' => 'groupTestGroupDn', |
215 |
'grp_test_grp_dn_writeable' => 'groupTestGroupDnWriteable', |
216 |
|
217 |
'search_pagination' => 'searchPagination', |
218 |
'search_page_size' => 'searchPageSize', |
219 |
|
220 |
]; |
221 |
|
222 |
} |
223 |
|
224 |
/**
|
225 |
* Constructor Method.
|
226 |
*
|
227 |
* @param $sid
|
228 |
*/
|
229 |
public function __construct($sid) { |
230 |
if (!is_scalar($sid)) { |
231 |
return;
|
232 |
} |
233 |
$this->detailed_watchdog_log = variable_get('ldap_help_watchdog_detail', 0); |
234 |
$server_record = FALSE; |
235 |
if (module_exists('ctools')) { |
236 |
ctools_include('export');
|
237 |
$result = ctools_export_load_object('ldap_servers', 'names', [$sid]); |
238 |
if (isset($result[$sid])) { |
239 |
$server_record = new stdClass(); |
240 |
foreach ($result[$sid] as $db_field_name => $value) { |
241 |
$server_record->{$db_field_name} = $value; |
242 |
} |
243 |
} |
244 |
} |
245 |
else {
|
246 |
$select = db_select('ldap_servers') |
247 |
->fields('ldap_servers')
|
248 |
->condition('ldap_servers.sid', $sid) |
249 |
->execute(); |
250 |
foreach ($select as $record) { |
251 |
if ($record->sid == $sid) { |
252 |
$server_record = $record; |
253 |
} |
254 |
} |
255 |
} |
256 |
|
257 |
$server_record_bindpw = NULL; |
258 |
if (!$server_record) { |
259 |
$this->inDatabase = FALSE; |
260 |
} |
261 |
else {
|
262 |
$this->inDatabase = TRUE; |
263 |
$this->sid = $sid; |
264 |
$this->detailedWatchdogLog = variable_get('ldap_help_watchdog_detail', 0); |
265 |
foreach ($this->field_to_properties_map() as $db_field_name => $property_name) { |
266 |
if (isset($server_record->$db_field_name)) { |
267 |
$this->{$property_name} = $server_record->$db_field_name; |
268 |
} |
269 |
} |
270 |
$server_record_bindpw = property_exists($server_record, 'bindpw') ? $server_record->bindpw : ''; |
271 |
} |
272 |
$this->initDerivedProperties($server_record_bindpw); |
273 |
} |
274 |
|
275 |
/**
|
276 |
* This method sets properties that don't directly map from db record.
|
277 |
*
|
278 |
* It is split out so it can be shared with ldapServerTest.class.php.
|
279 |
*
|
280 |
* @param $bindpw
|
281 |
*/
|
282 |
protected function initDerivedProperties($bindpw) { |
283 |
|
284 |
// Get this->basedn in array format.
|
285 |
if (!$this->basedn) { |
286 |
$this->basedn = [];
|
287 |
} |
288 |
// Do nothing.
|
289 |
elseif (is_array($this->basedn)) { |
290 |
} |
291 |
else {
|
292 |
$basedn_unserialized = @unserialize($this->basedn); |
293 |
if (is_array($basedn_unserialized)) { |
294 |
$this->basedn = $basedn_unserialized; |
295 |
} |
296 |
else {
|
297 |
$this->basedn = [];
|
298 |
$token = is_scalar($basedn_unserialized) ? $basedn_unserialized : print_r($basedn_unserialized, TRUE); |
299 |
debug("basednb desearialization error" . $token); |
300 |
watchdog('ldap_servers', 'Failed to deserialize LdapServer::basedn of !basedn', ['!basedn' => $token], WATCHDOG_ERROR); |
301 |
} |
302 |
|
303 |
} |
304 |
|
305 |
if ($this->followrefs && !function_exists('ldap_set_rebind_proc')) { |
306 |
$this->followrefs = FALSE; |
307 |
} |
308 |
|
309 |
if ($bindpw) { |
310 |
$this->bindpw = ($bindpw == '') ? '' : ldap_servers_decrypt($bindpw); |
311 |
} |
312 |
|
313 |
$bind_overrides = variable_get('ldap_servers_overrides', []); |
314 |
if (isset($bind_overrides[$this->sid])) { |
315 |
if (isset($bind_overrides[$this->sid]['binddn'])) { |
316 |
$this->binddn = $bind_overrides[$this->sid]['binddn']; |
317 |
} |
318 |
if (isset($bind_overrides[$this->sid]['bindpw'])) { |
319 |
$this->bindpw = $bind_overrides[$this->sid]['bindpw']; |
320 |
} |
321 |
} |
322 |
|
323 |
$this->paginationEnabled = (boolean) (ldap_servers_php_supports_pagination() && $this->searchPagination); |
324 |
|
325 |
$this->queriableWithoutUserCredentials = (boolean) ( |
326 |
$this->bind_method == LDAP_SERVERS_BIND_METHOD_SERVICE_ACCT || |
327 |
$this->bind_method == LDAP_SERVERS_BIND_METHOD_ANON_USER |
328 |
); |
329 |
$this->editPath = (!$this->sid) ? '' : 'admin/config/people/ldap/servers/edit/' . $this->sid; |
330 |
|
331 |
$this->groupGroupEntryMembershipsConfigured = ($this->groupMembershipsAttrMatchingUserAttr && $this->groupMembershipsAttr); |
332 |
$this->groupUserMembershipsConfigured = ($this->groupUserMembershipsAttrExists && $this->groupUserMembershipsAttr); |
333 |
} |
334 |
|
335 |
/**
|
336 |
* Destructor Method.
|
337 |
*/
|
338 |
public function __destruct() { |
339 |
// Close the server connection to be sure.
|
340 |
$this->disconnect();
|
341 |
} |
342 |
|
343 |
/**
|
344 |
* Invoke Method.
|
345 |
*/
|
346 |
public function __invoke() { |
347 |
$this->connect();
|
348 |
$this->bind();
|
349 |
} |
350 |
|
351 |
/**
|
352 |
* Connect Method.
|
353 |
*/
|
354 |
public function connect() { |
355 |
if (!function_exists('ldap_connect')) { |
356 |
watchdog('ldap_servers', 'PHP LDAP extension not found, aborting.'); |
357 |
return LDAP_NOT_SUPPORTED; |
358 |
} |
359 |
|
360 |
if (!$con = ldap_connect($this->address, $this->port)) { |
361 |
watchdog('ldap_servers', 'LDAP Connect failure to ' . $this->address . ':' . $this->port); |
362 |
return LDAP_CONNECT_ERROR; |
363 |
} |
364 |
|
365 |
ldap_set_option($con, LDAP_OPT_PROTOCOL_VERSION, 3); |
366 |
ldap_set_option($con, LDAP_OPT_REFERRALS, (int) $this->followrefs); |
367 |
|
368 |
// Use TLS if we are configured and able to.
|
369 |
if ($this->tls) { |
370 |
ldap_get_option($con, LDAP_OPT_PROTOCOL_VERSION, $vers); |
371 |
if ($vers == -1) { |
372 |
watchdog('ldap_servers', 'Could not get LDAP protocol version.'); |
373 |
return LDAP_PROTOCOL_ERROR; |
374 |
} |
375 |
if ($vers != 3) { |
376 |
watchdog('ldap_servers', 'Could not start TLS, only supported by LDAP v3.'); |
377 |
return LDAP_CONNECT_ERROR; |
378 |
} |
379 |
elseif (!function_exists('ldap_start_tls')) { |
380 |
watchdog('ldap_servers', 'Could not start TLS. It does not seem to be supported by this PHP setup.'); |
381 |
return LDAP_CONNECT_ERROR; |
382 |
} |
383 |
elseif (!ldap_start_tls($con)) { |
384 |
$msg = t("Could not start TLS. (Error %errno: %error).", ['%errno' => ldap_errno($con), '%error' => ldap_error($con)]); |
385 |
watchdog('ldap_servers', $msg); |
386 |
return LDAP_CONNECT_ERROR; |
387 |
} |
388 |
} |
389 |
|
390 |
// Store the resulting resource.
|
391 |
$this->connection = $con; |
392 |
return LDAP_SUCCESS; |
393 |
} |
394 |
|
395 |
/**
|
396 |
* Bind (authenticate) against an active LDAP database.
|
397 |
*
|
398 |
* @param $userdn
|
399 |
* The DN to bind against. If NULL, we use $this->binddn
|
400 |
* @param $pass
|
401 |
* The password search base. If NULL, we use $this->bindpw
|
402 |
*
|
403 |
* @return
|
404 |
* Result of bind; TRUE if successful, FALSE otherwise.
|
405 |
*/
|
406 |
public function bind($userdn = NULL, $pass = NULL, $anon_bind = FALSE) { |
407 |
|
408 |
// Ensure that we have an active server connection.
|
409 |
if (!$this->connection) { |
410 |
watchdog('ldap_servers', "LDAP bind failure for user %user. Not connected to LDAP server.", ['%user' => $userdn]); |
411 |
return LDAP_CONNECT_ERROR; |
412 |
} |
413 |
|
414 |
if ($anon_bind === FALSE && $userdn === NULL && $pass === NULL && $this->bind_method == LDAP_SERVERS_BIND_METHOD_ANON) { |
415 |
$anon_bind = TRUE; |
416 |
} |
417 |
if ($anon_bind === TRUE) { |
418 |
if (@!ldap_bind($this->connection)) { |
419 |
if ($this->detailedWatchdogLog) { |
420 |
watchdog('ldap_servers', "LDAP anonymous bind error. Error %errno: %error", ['%errno' => ldap_errno($this->connection), '%error' => ldap_error($this->connection)]); |
421 |
} |
422 |
return ldap_errno($this->connection); |
423 |
} |
424 |
} |
425 |
else {
|
426 |
$userdn = ($userdn != NULL) ? $userdn : $this->binddn; |
427 |
$pass = ($pass != NULL) ? $pass : $this->bindpw; |
428 |
|
429 |
if ($this->followrefs) { |
430 |
$rebHandler = new LdapServersRebindHandler($userdn, $pass); |
431 |
ldap_set_rebind_proc($this->connection, [$rebHandler, 'rebind_callback']); |
432 |
} |
433 |
|
434 |
if (drupal_strlen($pass) == 0 || drupal_strlen($userdn) == 0) { |
435 |
watchdog('ldap_servers', "LDAP bind failure for user userdn=%userdn, pass=%pass.", ['%userdn' => $userdn, '%pass' => $pass]); |
436 |
return LDAP_LOCAL_ERROR; |
437 |
} |
438 |
if (@!ldap_bind($this->connection, $userdn, $pass)) { |
439 |
if ($this->detailedWatchdogLog) { |
440 |
watchdog('ldap_servers', "LDAP bind failure for user %user. Error %errno: %error", ['%user' => $userdn, '%errno' => ldap_errno($this->connection), '%error' => ldap_error($this->connection)]); |
441 |
} |
442 |
return ldap_errno($this->connection); |
443 |
} |
444 |
} |
445 |
|
446 |
return LDAP_SUCCESS; |
447 |
} |
448 |
|
449 |
/**
|
450 |
* Disconnect (unbind) from an active LDAP server.
|
451 |
*/
|
452 |
public function disconnect() { |
453 |
if (!$this->connection) { |
454 |
// Never bound or not currently bound, so no need to disconnect
|
455 |
// watchdog('ldap_servers', 'LDAP disconnect failure from '. $this->server_addr . ':' . $this->port);.
|
456 |
} |
457 |
else {
|
458 |
ldap_unbind($this->connection);
|
459 |
$this->connection = NULL; |
460 |
} |
461 |
} |
462 |
|
463 |
/**
|
464 |
*
|
465 |
*/
|
466 |
public function connectAndBindIfNotAlready() { |
467 |
if (!$this->connection) { |
468 |
$this->connect();
|
469 |
$this->bind();
|
470 |
} |
471 |
} |
472 |
|
473 |
/**
|
474 |
* Does dn exist for this server?
|
475 |
*
|
476 |
* @param string $dn
|
477 |
* @param enum $return
|
478 |
* = 'boolean' or 'ldap_entry'.
|
479 |
* @param array $attributes
|
480 |
* in same form as ldap_read $attributes parameter.
|
481 |
*
|
482 |
* @return bool|array
|
483 |
*/
|
484 |
public function dnExists($dn, $return = 'boolean', $attributes = NULL) { |
485 |
|
486 |
$params = [
|
487 |
'base_dn' => $dn, |
488 |
'attributes' => $attributes, |
489 |
'attrsonly' => FALSE, |
490 |
'filter' => '(objectclass=*)', |
491 |
'sizelimit' => 0, |
492 |
'timelimit' => 0, |
493 |
'deref' => NULL, |
494 |
]; |
495 |
|
496 |
if ($return == 'boolean' || !is_array($attributes)) { |
497 |
$params['attributes'] = ['objectclass']; |
498 |
} |
499 |
else {
|
500 |
$params['attributes'] = $attributes; |
501 |
} |
502 |
|
503 |
$result = $this->ldapQuery(LDAP_SCOPE_BASE, $params); |
504 |
if ($result !== FALSE) { |
505 |
$entries = @ldap_get_entries($this->connection, $result); |
506 |
if ($entries !== FALSE && $entries['count'] > 0) { |
507 |
return ($return == 'boolean') ? TRUE : $entries[0]; |
508 |
} |
509 |
} |
510 |
|
511 |
return FALSE; |
512 |
|
513 |
} |
514 |
|
515 |
/**
|
516 |
* @param $ldap_result
|
517 |
* as ldap link identifier
|
518 |
*
|
519 |
* @return FALSE on error or number of entries.
|
520 |
* (if 0 entries will return 0)
|
521 |
*/
|
522 |
public function countEntries($ldap_result) { |
523 |
return ldap_count_entries($this->connection, $ldap_result); |
524 |
} |
525 |
|
526 |
/**
|
527 |
* Create ldap entry.
|
528 |
*
|
529 |
* @param array $attributes
|
530 |
* should follow the structure of ldap_add functions
|
531 |
* entry array: http://us.php.net/manual/en/function.ldap-add.php
|
532 |
* $attributes["attribute1"] = "value";
|
533 |
* $attributes["attribute2"][0] = "value1";
|
534 |
* $attributes["attribute2"][1] = "value2";.
|
535 |
*
|
536 |
* @return boolean result
|
537 |
*/
|
538 |
public function createLdapEntry($attributes, $dn = NULL) { |
539 |
|
540 |
if (!$this->connection) { |
541 |
$this->connect();
|
542 |
$this->bind();
|
543 |
} |
544 |
if (isset($attributes['dn'])) { |
545 |
$dn = $attributes['dn']; |
546 |
unset($attributes['dn']); |
547 |
} |
548 |
elseif (!$dn) { |
549 |
return FALSE; |
550 |
} |
551 |
|
552 |
if (!empty($attributes['unicodePwd']) && ($this->ldap_type == 'ad')) { |
553 |
$attributes['unicodePwd'] = ldap_servers_convert_password_for_active_directory_unicodePwd($attributes['unicodePwd']); |
554 |
} |
555 |
|
556 |
$result = @ldap_add($this->connection, $dn, $attributes); |
557 |
if (!$result) { |
558 |
$error = "LDAP Server ldap_add(%dn) Error Server ID = %sid, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str "; |
559 |
$tokens = ['%dn' => $dn, '%sid' => $this->sid, '%ldap_errno' => ldap_errno($this->connection), '%ldap_err2str' => ldap_err2str(ldap_errno($this->connection))]; |
560 |
watchdog('ldap_servers', $error, $tokens, WATCHDOG_ERROR); |
561 |
} |
562 |
|
563 |
return $result; |
564 |
} |
565 |
|
566 |
/**
|
567 |
* Compares 2 LDAP entries and returns the difference.
|
568 |
*
|
569 |
* Given 2 ldap entries, old and new, removes unchanged values to avoid
|
570 |
* security errors and incorrect date modified.
|
571 |
*
|
572 |
* @param array $new_entry
|
573 |
* LDAP entry array in form <attribute> => <value>, or
|
574 |
* <attribute> => array(<value1>, <value2>, ...).
|
575 |
* @param array $old_entry
|
576 |
* LDAP entry in form <attribute> =>
|
577 |
* array('count' => N, <value1>, <value2>, ...).
|
578 |
*
|
579 |
* @return array
|
580 |
* The $new_entry with unchanged attributes removed.
|
581 |
*
|
582 |
* @see \LdapServer::modifyLdapEntry()
|
583 |
*/
|
584 |
public static function removeUnchangedAttributes($new_entry, $old_entry) { |
585 |
|
586 |
foreach ($new_entry as $key => $new_val) { |
587 |
$old_value = FALSE; |
588 |
$old_value_is_scalar = FALSE; |
589 |
$key_lcase = drupal_strtolower($key); |
590 |
if (isset($old_entry[$key_lcase])) { |
591 |
if ($old_entry[$key_lcase]['count'] == 1) { |
592 |
$old_value = $old_entry[$key_lcase][0]; |
593 |
$old_value_is_scalar = TRUE; |
594 |
} |
595 |
else {
|
596 |
unset($old_entry[$key_lcase]['count']); |
597 |
$old_value = $old_entry[$key_lcase]; |
598 |
$old_value_is_scalar = FALSE; |
599 |
} |
600 |
} |
601 |
|
602 |
// Identical multivalued attributes.
|
603 |
if (is_array($new_val) && is_array($old_value) && count(array_diff($new_val, $old_value)) == 0) { |
604 |
unset($new_entry[$key]); |
605 |
} |
606 |
elseif ($old_value_is_scalar && !is_array($new_val) && drupal_strtolower($old_value) == drupal_strtolower($new_val)) { |
607 |
// don't change values that aren't changing to avoid false permission constraints.
|
608 |
unset($new_entry[$key]); |
609 |
} |
610 |
} |
611 |
|
612 |
return $new_entry; |
613 |
} |
614 |
|
615 |
/**
|
616 |
* Modify attributes of ldap entry.
|
617 |
*
|
618 |
* @param string $dn
|
619 |
* DN of entry.
|
620 |
* @param array $attributes
|
621 |
* should follow the structure of ldap_add functions
|
622 |
* entry array: http://us.php.net/manual/en/function.ldap-add.php
|
623 |
* $attributes["attribute1"] = "value";
|
624 |
* $attributes["attribute2"][0] = "value1";
|
625 |
* $attributes["attribute2"][1] = "value2";.
|
626 |
*
|
627 |
* @return TRUE on success FALSE on error
|
628 |
*/
|
629 |
public function modifyLdapEntry($dn, $attributes = [], $old_attributes = FALSE) { |
630 |
|
631 |
$this->connectAndBindIfNotAlready();
|
632 |
|
633 |
if (!$old_attributes) { |
634 |
$result = @ldap_read($this->connection, $dn, 'objectClass=*'); |
635 |
if (!$result) { |
636 |
$error = "LDAP Server ldap_read(%dn) in LdapServer::modifyLdapEntry() Error Server ID = %sid, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str "; |
637 |
$tokens = ['%dn' => $dn, '%sid' => $this->sid, '%ldap_errno' => ldap_errno($this->connection), '%ldap_err2str' => ldap_err2str(ldap_errno($this->connection))]; |
638 |
watchdog('ldap_servers', $error, $tokens, WATCHDOG_ERROR); |
639 |
return FALSE; |
640 |
} |
641 |
|
642 |
$entries = ldap_get_entries($this->connection, $result); |
643 |
if (is_array($entries) && $entries['count'] == 1) { |
644 |
$old_attributes = $entries[0]; |
645 |
} |
646 |
} |
647 |
|
648 |
if (!empty($attributes['unicodePwd']) && ($this->ldap_type == 'ad')) { |
649 |
$attributes['unicodePwd'] = ldap_servers_convert_password_for_active_directory_unicodePwd($attributes['unicodePwd']); |
650 |
} |
651 |
|
652 |
$attributes = $this->removeUnchangedAttributes($attributes, $old_attributes); |
653 |
|
654 |
foreach ($attributes as $key => $cur_val) { |
655 |
$old_value = FALSE; |
656 |
$key_lcase = drupal_strtolower($key); |
657 |
if (isset($old_attributes[$key_lcase])) { |
658 |
if ($old_attributes[$key_lcase]['count'] == 1) { |
659 |
$old_value = $old_attributes[$key_lcase][0]; |
660 |
} |
661 |
else {
|
662 |
unset($old_attributes[$key_lcase]['count']); |
663 |
$old_value = $old_attributes[$key_lcase]; |
664 |
} |
665 |
} |
666 |
|
667 |
// Remove enpty attributes.
|
668 |
if ($cur_val == '' && $old_value != '') { |
669 |
unset($attributes[$key]); |
670 |
$result = @ldap_mod_del($this->connection, $dn, [$key_lcase => $old_value]); |
671 |
if (!$result) { |
672 |
$error = "LDAP Server ldap_mod_del(%dn) in LdapServer::modifyLdapEntry() Error Server ID = %sid, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str "; |
673 |
$tokens = ['%dn' => $dn, '%sid' => $this->sid, '%ldap_errno' => ldap_errno($this->connection), '%ldap_err2str' => ldap_err2str(ldap_errno($this->connection))]; |
674 |
watchdog('ldap_servers', $error, $tokens, WATCHDOG_ERROR); |
675 |
return FALSE; |
676 |
} |
677 |
} |
678 |
elseif (is_array($cur_val)) { |
679 |
foreach ($cur_val as $mv_key => $mv_cur_val) { |
680 |
if ($mv_cur_val == '') { |
681 |
// Remove empty values in multivalues attributes.
|
682 |
unset($attributes[$key][$mv_key]); |
683 |
} |
684 |
else {
|
685 |
$attributes[$key][$mv_key] = $mv_cur_val; |
686 |
} |
687 |
} |
688 |
} |
689 |
} |
690 |
|
691 |
if (count($attributes) > 0) { |
692 |
$result = @ldap_modify($this->connection, $dn, $attributes); |
693 |
if (!$result) { |
694 |
$error = "LDAP Server ldap_modify(%dn) in LdapServer::modifyLdapEntry() Error Server ID = %sid, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str "; |
695 |
$tokens = ['%dn' => $dn, '%sid' => $this->sid, '%ldap_errno' => ldap_errno($this->connection), '%ldap_err2str' => ldap_err2str(ldap_errno($this->connection))]; |
696 |
watchdog('ldap_servers', $error, $tokens, WATCHDOG_ERROR); |
697 |
return FALSE; |
698 |
} |
699 |
} |
700 |
|
701 |
return TRUE; |
702 |
|
703 |
} |
704 |
|
705 |
/**
|
706 |
* Perform an LDAP delete.
|
707 |
*
|
708 |
* @param string $dn
|
709 |
*
|
710 |
* @return boolean result per ldap_delete
|
711 |
*/
|
712 |
public function delete($dn) { |
713 |
if (!$this->connection) { |
714 |
$this->connect();
|
715 |
$this->bind();
|
716 |
} |
717 |
$result = @ldap_delete($this->connection, $dn); |
718 |
if (!$result) { |
719 |
$error = "LDAP Server delete(%dn) in LdapServer::delete() Error Server ID = %sid, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str "; |
720 |
$tokens = ['%dn' => $dn, '%sid' => $this->sid, '%ldap_errno' => ldap_errno($this->connection), '%ldap_err2str' => ldap_err2str(ldap_errno($this->connection))]; |
721 |
watchdog('ldap_servers', $error, $tokens, WATCHDOG_ERROR); |
722 |
} |
723 |
return $result; |
724 |
} |
725 |
|
726 |
/**
|
727 |
* Perform an LDAP search on all base dns and aggregate into one result.
|
728 |
*
|
729 |
* @param string $filter
|
730 |
* The search filter. such as sAMAccountName=jbarclay. attribute values (e.g. jbarclay) should be esacaped before calling.
|
731 |
*
|
732 |
* @param array $attributes
|
733 |
* List of desired attributes. If omitted, we only return "dn".
|
734 |
*
|
735 |
* @remaining params mimick ldap_search() function params
|
736 |
*
|
737 |
* @return array
|
738 |
* An array of matching entries->attributes (will have 0 elements if search
|
739 |
* returns no results), or FALSE on error on any of the basedn queries.
|
740 |
*/
|
741 |
public function searchAllBaseDns( |
742 |
$filter,
|
743 |
$attributes = [],
|
744 |
$attrsonly = 0, |
745 |
$sizelimit = 0, |
746 |
$timelimit = 0, |
747 |
$deref = NULL, |
748 |
$scope = LDAP_SCOPE_SUBTREE |
749 |
) { |
750 |
$all_entries = [];
|
751 |
// Need to search on all basedns one at a time.
|
752 |
foreach ($this->basedn as $base_dn) { |
753 |
// No attributes, just dns needed.
|
754 |
$entries = $this->search($base_dn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref, $scope); |
755 |
// If error in any search, return false.
|
756 |
if ($entries === FALSE) { |
757 |
return FALSE; |
758 |
} |
759 |
if (count($all_entries) == 0) { |
760 |
$all_entries = $entries; |
761 |
unset($all_entries['count']); |
762 |
} |
763 |
else {
|
764 |
$existing_count = count($all_entries); |
765 |
unset($entries['count']); |
766 |
foreach ($entries as $i => $entry) { |
767 |
$all_entries[$existing_count + $i] = $entry; |
768 |
} |
769 |
} |
770 |
} |
771 |
$all_entries['count'] = count($all_entries); |
772 |
return $all_entries; |
773 |
|
774 |
} |
775 |
|
776 |
/**
|
777 |
* Perform an LDAP search.
|
778 |
*
|
779 |
* @param string $basedn
|
780 |
* The search base. If NULL, we use $this->basedn. should not be esacaped.
|
781 |
* @param string $filter
|
782 |
* The search filter. such as sAMAccountName=jbarclay. attribute values
|
783 |
* (e.g. jbarclay) should be esacaped before calling.
|
784 |
*
|
785 |
* @param array $attributes
|
786 |
* List of desired attributes. If omitted, we only return "dn".
|
787 |
*
|
788 |
* @remaining params mimick ldap_search() function params
|
789 |
*
|
790 |
* @return
|
791 |
* An array of matching entries->attributes (will have 0
|
792 |
* elements if search returns no results),
|
793 |
* or FALSE on error.
|
794 |
*/
|
795 |
public function search($base_dn = NULL, |
796 |
$filter,
|
797 |
$attributes = [],
|
798 |
$attrsonly = 0, |
799 |
$sizelimit = 0, |
800 |
$timelimit = 0, |
801 |
$deref = NULL, |
802 |
$scope = LDAP_SCOPE_SUBTREE) { |
803 |
|
804 |
/**
|
805 |
* pagingation issues:
|
806 |
* -- see documentation queue: http://markmail.org/message/52w24iae3g43ikix#query:+page:1+mid:bez5vpl6smgzmymy+state:results
|
807 |
* -- wait for php 5.4? https://svn.php.net/repository/php/php-src/tags/php_5_4_0RC6/NEWS (ldap_control_paged_result
|
808 |
* -- http://sgehrig.wordpress.com/2009/11/06/reading-paged-ldap-results-with-php-is-a-show-stopper/
|
809 |
*/
|
810 |
|
811 |
if ($base_dn == NULL) { |
812 |
if (count($this->basedn) == 1) { |
813 |
$base_dn = $this->basedn[0]; |
814 |
} |
815 |
else {
|
816 |
return FALSE; |
817 |
} |
818 |
} |
819 |
|
820 |
$attr_display = is_array($attributes) ? join(',', $attributes) : 'none'; |
821 |
$query = 'ldap_search() call: ' . join(",\n", [ |
822 |
'base_dn: ' . $base_dn, |
823 |
'filter = ' . $filter, |
824 |
'attributes: ' . $attr_display, |
825 |
'attrsonly = ' . $attrsonly, |
826 |
'sizelimit = ' . $sizelimit, |
827 |
'timelimit = ' . $timelimit, |
828 |
'deref = ' . $deref, |
829 |
'scope = ' . $scope, |
830 |
] |
831 |
); |
832 |
if ($this->detailed_watchdog_log) { |
833 |
watchdog('ldap_servers', $query, []); |
834 |
} |
835 |
|
836 |
// When checking multiple servers, there's a chance we might not be connected yet.
|
837 |
if (!$this->connection) { |
838 |
$this->connect();
|
839 |
$this->bind();
|
840 |
} |
841 |
|
842 |
$ldap_query_params = [
|
843 |
'connection' => $this->connection, |
844 |
'base_dn' => $base_dn, |
845 |
'filter' => $filter, |
846 |
'attributes' => $attributes, |
847 |
'attrsonly' => $attrsonly, |
848 |
'sizelimit' => $sizelimit, |
849 |
'timelimit' => $timelimit, |
850 |
'deref' => $deref, |
851 |
'query_display' => $query, |
852 |
'scope' => $scope, |
853 |
]; |
854 |
|
855 |
if ($this->searchPagination && $this->paginationEnabled) { |
856 |
$aggregated_entries = $this->pagedLdapQuery($ldap_query_params); |
857 |
return $aggregated_entries; |
858 |
} |
859 |
else {
|
860 |
$result = $this->ldapQuery($scope, $ldap_query_params); |
861 |
if ($result && ($this->countEntries($result) !== FALSE)) { |
862 |
$entries = ldap_get_entries($this->connection, $result); |
863 |
drupal_alter('ldap_server_search_results', $entries, $ldap_query_params); |
864 |
return (is_array($entries)) ? $entries : FALSE; |
865 |
} |
866 |
elseif ($this->ldapErrorNumber()) { |
867 |
$watchdog_tokens = [
|
868 |
'%basedn' => $ldap_query_params['base_dn'], |
869 |
'%filter' => $ldap_query_params['filter'], |
870 |
'%attributes' => print_r($ldap_query_params['attributes'], TRUE), |
871 |
'%errmsg' => $this->errorMsg('ldap'), |
872 |
'%errno' => $this->ldapErrorNumber(), |
873 |
]; |
874 |
watchdog('ldap_servers', "LDAP ldap_search error. basedn: %basedn| filter: %filter| attributes: |
875 |
%attributes| errmsg: %errmsg| ldap err no: %errno|", $watchdog_tokens); |
876 |
return FALSE; |
877 |
} |
878 |
else {
|
879 |
return FALSE; |
880 |
} |
881 |
} |
882 |
} |
883 |
|
884 |
/**
|
885 |
* Execute a paged ldap query and return entries as one aggregated array.
|
886 |
*
|
887 |
* $this->searchPageStart and $this->searchPageEnd should be set before calling if
|
888 |
* a particular set of pages is desired.
|
889 |
*
|
890 |
* @param array $ldap_query_params
|
891 |
* of form:
|
892 |
* 'base_dn' => base_dn,
|
893 |
* 'filter' => filter,
|
894 |
* 'attributes' => attributes,
|
895 |
* 'attrsonly' => attrsonly,
|
896 |
* 'sizelimit' => sizelimit,
|
897 |
* 'timelimit' => timelimit,
|
898 |
* 'deref' => deref,
|
899 |
* 'scope' => scope,
|
900 |
*
|
901 |
* (this array of parameters is primarily passed on to ldapQuery() method)
|
902 |
*
|
903 |
* @return array of ldap entries or FALSE on error.
|
904 |
*/
|
905 |
public function pagedLdapQuery($ldap_query_params) { |
906 |
|
907 |
if (!($this->searchPagination && $this->paginationEnabled)) { |
908 |
$watchdog_tokens = [
|
909 |
'%basedn' => $ldap_query_params['base_dn'], |
910 |
'%filter' => $ldap_query_params['filter'], |
911 |
'%attributes' => print_r($ldap_query_params['attributes'], TRUE), |
912 |
'%errmsg' => $this->errorMsg('ldap'), |
913 |
'%errno' => $this->ldapErrorNumber(), |
914 |
]; |
915 |
watchdog('ldap_servers', "LDAP server pagedLdapQuery() called when functionality not available in php install or |
916 |
not enabled in ldap server configuration. error. basedn: %basedn| filter: %filter| attributes:
|
917 |
%attributes| errmsg: %errmsg| ldap err no: %errno|", $watchdog_tokens); |
918 |
return FALSE; |
919 |
} |
920 |
|
921 |
$page_token = ''; |
922 |
$page = 0; |
923 |
$estimated_entries = 0; |
924 |
$aggregated_entries = [];
|
925 |
$aggregated_entries_count = 0; |
926 |
$has_page_results = FALSE; |
927 |
|
928 |
do {
|
929 |
ldap_control_paged_result($this->connection, $this->searchPageSize, TRUE, $page_token); |
930 |
$result = $this->ldapQuery($ldap_query_params['scope'], $ldap_query_params); |
931 |
|
932 |
if ($page >= $this->searchPageStart) { |
933 |
$skipped_page = FALSE; |
934 |
if ($result && ($this->countEntries($result) !== FALSE)) { |
935 |
$page_entries = ldap_get_entries($this->connection, $result); |
936 |
unset($page_entries['count']); |
937 |
$has_page_results = (is_array($page_entries) && count($page_entries) > 0); |
938 |
$aggregated_entries = array_merge($aggregated_entries, $page_entries); |
939 |
$aggregated_entries_count = count($aggregated_entries); |
940 |
} |
941 |
elseif ($this->ldapErrorNumber()) { |
942 |
$watchdog_tokens = [
|
943 |
'%basedn' => $ldap_query_params['base_dn'], |
944 |
'%filter' => $ldap_query_params['filter'], |
945 |
'%attributes' => print_r($ldap_query_params['attributes'], TRUE), |
946 |
'%errmsg' => $this->errorMsg('ldap'), |
947 |
'%errno' => $this->ldapErrorNumber(), |
948 |
]; |
949 |
watchdog('ldap_servers', "LDAP ldap_search error. basedn: %basedn| filter: %filter| attributes: |
950 |
%attributes| errmsg: %errmsg| ldap err no: %errno|", $watchdog_tokens); |
951 |
return FALSE; |
952 |
} |
953 |
else {
|
954 |
return FALSE; |
955 |
} |
956 |
} |
957 |
else {
|
958 |
$skipped_page = TRUE; |
959 |
} |
960 |
@ldap_control_paged_result_response($this->connection, $result, $page_token, $estimated_entries); |
961 |
if ($ldap_query_params['sizelimit'] && $this->ldapErrorNumber() == LDAP_SIZELIMIT_EXCEEDED) { |
962 |
// False positive error thrown. do not set result limit error when $sizelimit specified.
|
963 |
} |
964 |
elseif ($this->hasError()) { |
965 |
watchdog('ldap_servers', 'ldap_control_paged_result_response() function error. LDAP Error: %message, ldap_list() parameters: %query', |
966 |
['%message' => $this->errorMsg('ldap'), '%query' => $ldap_query_params['query_display']], |
967 |
WATCHDOG_ERROR);
|
968 |
} |
969 |
|
970 |
if (isset($ldap_query_params['sizelimit']) && $ldap_query_params['sizelimit'] && $aggregated_entries_count >= $ldap_query_params['sizelimit']) { |
971 |
$discarded_entries = array_splice($aggregated_entries, $ldap_query_params['sizelimit']); |
972 |
break;
|
973 |
} |
974 |
// User defined pagination has run out.
|
975 |
elseif ($this->searchPageEnd !== NULL && $page >= $this->searchPageEnd) { |
976 |
break;
|
977 |
} |
978 |
// Ldap reference pagination has run out.
|
979 |
elseif ($page_token === NULL || $page_token == '') { |
980 |
break;
|
981 |
} |
982 |
$page++;
|
983 |
} while ($skipped_page || $has_page_results); |
984 |
|
985 |
$aggregated_entries['count'] = count($aggregated_entries); |
986 |
return $aggregated_entries; |
987 |
} |
988 |
|
989 |
/**
|
990 |
* Execute ldap query and return ldap records.
|
991 |
*
|
992 |
* @param scope
|
993 |
*
|
994 |
* @params see pagedLdapQuery $params
|
995 |
*
|
996 |
* @return array of ldap entries
|
997 |
*/
|
998 |
public function ldapQuery($scope, $params) { |
999 |
|
1000 |
$this->connectAndBindIfNotAlready();
|
1001 |
|
1002 |
switch ($scope) { |
1003 |
case LDAP_SCOPE_SUBTREE: |
1004 |
$result = @ldap_search($this->connection, $params['base_dn'], $params['filter'], $params['attributes'], $params['attrsonly'], |
1005 |
$params['sizelimit'], $params['timelimit'], $params['deref']); |
1006 |
if ($params['sizelimit'] && $this->ldapErrorNumber() == LDAP_SIZELIMIT_EXCEEDED) { |
1007 |
// False positive error thrown. do not return result limit error when $sizelimit specified.
|
1008 |
} |
1009 |
elseif ($this->hasError()) { |
1010 |
watchdog('ldap_servers', 'ldap_search() function error. LDAP Error: %message, ldap_search() parameters: %query', |
1011 |
['%message' => $this->errorMsg('ldap'), '%query' => $params['query_display']], |
1012 |
WATCHDOG_ERROR);
|
1013 |
} |
1014 |
break;
|
1015 |
|
1016 |
case LDAP_SCOPE_BASE: |
1017 |
$result = @ldap_read($this->connection, $params['base_dn'], $params['filter'], $params['attributes'], $params['attrsonly'], |
1018 |
$params['sizelimit'], $params['timelimit'], $params['deref']); |
1019 |
if ($params['sizelimit'] && $this->ldapErrorNumber() == LDAP_SIZELIMIT_EXCEEDED) { |
1020 |
// False positive error thrown. do not result limit error when $sizelimit specified.
|
1021 |
} |
1022 |
elseif ($this->hasError()) { |
1023 |
watchdog('ldap_servers', 'ldap_read() function error. LDAP Error: %message, ldap_read() parameters: %query', |
1024 |
['%message' => $this->errorMsg('ldap'), '%query' => @$params['query_display']], |
1025 |
WATCHDOG_ERROR);
|
1026 |
} |
1027 |
break;
|
1028 |
|
1029 |
case LDAP_SCOPE_ONELEVEL: |
1030 |
$result = @ldap_list($this->connection, $params['base_dn'], $params['filter'], $params['attributes'], $params['attrsonly'], |
1031 |
$params['sizelimit'], $params['timelimit'], $params['deref']); |
1032 |
if ($params['sizelimit'] && $this->ldapErrorNumber() == LDAP_SIZELIMIT_EXCEEDED) { |
1033 |
// False positive error thrown. do not result limit error when $sizelimit specified.
|
1034 |
} |
1035 |
elseif ($this->hasError()) { |
1036 |
watchdog('ldap_servers', 'ldap_list() function error. LDAP Error: %message, ldap_list() parameters: %query', |
1037 |
['%message' => $this->errorMsg('ldap'), '%query' => $params['query_display']], |
1038 |
WATCHDOG_ERROR);
|
1039 |
} |
1040 |
break;
|
1041 |
} |
1042 |
return $result; |
1043 |
} |
1044 |
|
1045 |
/**
|
1046 |
* @param array $dns
|
1047 |
* Mixed Case.
|
1048 |
* @return array $dns Lower Case
|
1049 |
*/
|
1050 |
public function dnArrayToLowerCase($dns) { |
1051 |
return array_keys(array_change_key_case(array_flip($dns), CASE_LOWER)); |
1052 |
} |
1053 |
|
1054 |
/**
|
1055 |
* UserUserEntityFromPuid.
|
1056 |
*
|
1057 |
* @param string $puid
|
1058 |
* Binary or string as returned from ldap_read or other ldap function.
|
1059 |
*
|
1060 |
* @return mixed
|
1061 |
*/
|
1062 |
public function userUserEntityFromPuid($puid) { |
1063 |
|
1064 |
$query = new EntityFieldQuery(); |
1065 |
$query->entityCondition('entity_type', 'user') |
1066 |
->fieldCondition('ldap_user_puid_sid', 'value', $this->sid, '=') |
1067 |
->fieldCondition('ldap_user_puid', 'value', $puid, '=') |
1068 |
->fieldCondition('ldap_user_puid_property', 'value', $this->unique_persistent_attr, '=') |
1069 |
// Run the query as user 1.
|
1070 |
->addMetaData('account', user_load(1)); |
1071 |
|
1072 |
$result = $query->execute(); |
1073 |
|
1074 |
if (isset($result['user'])) { |
1075 |
$uids = array_keys($result['user']); |
1076 |
if (count($uids) == 1) { |
1077 |
$user = entity_load('user', array_keys($result['user'])); |
1078 |
return $user[$uids[0]]; |
1079 |
} |
1080 |
else {
|
1081 |
$uids = join(',', $uids); |
1082 |
$tokens = ['%uids' => $uids, '%puid' => $puid, '%sid' => $this->sid, '%ldap_user_puid_property' => $this->unique_persistent_attr]; |
1083 |
watchdog('ldap_servers', 'multiple users (uids: %uids) with same puid (puid=%puid, sid=%sid, ldap_user_puid_property=%ldap_user_puid_property)', $tokens, WATCHDOG_ERROR); |
1084 |
return FALSE; |
1085 |
} |
1086 |
} |
1087 |
else {
|
1088 |
return FALSE; |
1089 |
} |
1090 |
|
1091 |
} |
1092 |
|
1093 |
/**
|
1094 |
* @param $drupal_username
|
1095 |
* @param $watchdog_tokens
|
1096 |
*
|
1097 |
* @return string
|
1098 |
*/
|
1099 |
public function userUsernameToLdapNameTransform($drupal_username, &$watchdog_tokens) { |
1100 |
if ($this->ldapToDrupalUserPhp && module_exists('php')) { |
1101 |
global $name; |
1102 |
$old_name_value = $name; |
1103 |
$name = $drupal_username; |
1104 |
$code = "<?php global \$name; \n" . $this->ldapToDrupalUserPhp . "; \n ?>"; |
1105 |
$watchdog_tokens['%code'] = $this->ldapToDrupalUserPhp; |
1106 |
$code_result = php_eval($code); |
1107 |
$watchdog_tokens['%code_result'] = $code_result; |
1108 |
$ldap_username = $code_result; |
1109 |
$watchdog_tokens['%ldap_username'] = $ldap_username; |
1110 |
// Important because of global scope of $name.
|
1111 |
$name = $old_name_value; |
1112 |
if ($this->detailedWatchdogLog) { |
1113 |
watchdog('ldap_servers', '%drupal_user_name tansformed to %ldap_username by applying code <code>%code</code>', $watchdog_tokens, WATCHDOG_DEBUG); |
1114 |
} |
1115 |
} |
1116 |
else {
|
1117 |
$ldap_username = $drupal_username; |
1118 |
} |
1119 |
|
1120 |
// Let other modules alter the ldap name.
|
1121 |
$context = [
|
1122 |
'ldap_server' => $this, |
1123 |
]; |
1124 |
drupal_alter('ldap_servers_username_to_ldapname', $ldap_username, $drupal_username, $context); |
1125 |
|
1126 |
return $ldap_username; |
1127 |
|
1128 |
} |
1129 |
|
1130 |
/**
|
1131 |
* UserUsernameFromLdapEntry.
|
1132 |
*
|
1133 |
* @param array $ldap_entry
|
1134 |
*
|
1135 |
* @return string
|
1136 |
* user's username value
|
1137 |
*/
|
1138 |
public function userUsernameFromLdapEntry($ldap_entry) { |
1139 |
|
1140 |
if ($this->account_name_attr) { |
1141 |
$accountname = (empty($ldap_entry[$this->account_name_attr][0])) ? FALSE : $ldap_entry[$this->account_name_attr][0]; |
1142 |
} |
1143 |
elseif ($this->user_attr) { |
1144 |
$accountname = (empty($ldap_entry[$this->user_attr][0])) ? FALSE : $ldap_entry[$this->user_attr][0]; |
1145 |
} |
1146 |
else {
|
1147 |
$accountname = FALSE; |
1148 |
} |
1149 |
|
1150 |
return $accountname; |
1151 |
} |
1152 |
|
1153 |
/**
|
1154 |
* UserUsernameFromDn.
|
1155 |
*
|
1156 |
* @param string $dn
|
1157 |
*
|
1158 |
* @return mixed
|
1159 |
* string user's username value of FALSE
|
1160 |
*/
|
1161 |
public function userUsernameFromDn($dn) { |
1162 |
|
1163 |
$ldap_entry = @$this->dnExists($dn, 'ldap_entry', []); |
1164 |
if (!$ldap_entry || !is_array($ldap_entry)) { |
1165 |
return FALSE; |
1166 |
} |
1167 |
else {
|
1168 |
return $this->userUsernameFromLdapEntry($ldap_entry); |
1169 |
} |
1170 |
|
1171 |
} |
1172 |
|
1173 |
/**
|
1174 |
* @param ldap entry array $ldap_entry
|
1175 |
*
|
1176 |
* @return string user's mail value or FALSE if none present
|
1177 |
*/
|
1178 |
public function userEmailFromLdapEntry($ldap_entry) { |
1179 |
|
1180 |
// Not using template.
|
1181 |
if ($ldap_entry && $this->mail_attr) { |
1182 |
$mail = isset($ldap_entry[$this->mail_attr][0]) ? $ldap_entry[$this->mail_attr][0] : FALSE; |
1183 |
return $mail; |
1184 |
} |
1185 |
// Template is of form [cn]@illinois.edu.
|
1186 |
elseif ($ldap_entry && $this->mail_template) { |
1187 |
ldap_servers_module_load_include('inc', 'ldap_servers', 'ldap_servers.functions'); |
1188 |
return ldap_servers_token_replace($ldap_entry, $this->mail_template, 'ldap_entry'); |
1189 |
} |
1190 |
else {
|
1191 |
return FALSE; |
1192 |
} |
1193 |
} |
1194 |
|
1195 |
/**
|
1196 |
* @param array $ldap_entry
|
1197 |
*
|
1198 |
* @return object|bool
|
1199 |
* Drupal file object image user's thumbnail or FALSE if none present or
|
1200 |
* ERROR happens.
|
1201 |
*/
|
1202 |
public function userPictureFromLdapEntry($ldap_entry, $drupal_username = FALSE) { |
1203 |
if ($ldap_entry && $this->picture_attr) { |
1204 |
// Check if ldap entry has been provisioned.
|
1205 |
$image_data = isset($ldap_entry[$this->picture_attr][0]) ? $ldap_entry[$this->picture_attr][0] : FALSE; |
1206 |
if (!$image_data) { |
1207 |
return FALSE; |
1208 |
} |
1209 |
|
1210 |
$md5thumb = md5($image_data); |
1211 |
|
1212 |
/**
|
1213 |
* If the existing account already has picture check if it has changed. If
|
1214 |
* so remove the old file and create the new one. If a picture is not set
|
1215 |
* but the account has an md5 hash, something is wrong and we exit.
|
1216 |
*/
|
1217 |
if ($drupal_username && $account = user_load_by_name($drupal_username)) { |
1218 |
if ($account->uid == 0 || $account->uid == 1) { |
1219 |
return FALSE; |
1220 |
} |
1221 |
if (isset($account->picture)) { |
1222 |
// Check if image has changed.
|
1223 |
if (isset($account->data['ldap_user']['init']['thumb5md']) && $md5thumb === $account->data['ldap_user']['init']['thumb5md']) { |
1224 |
// No change, return same image.
|
1225 |
$account->picture->md5Sum = $md5thumb; |
1226 |
return $account->picture; |
1227 |
} |
1228 |
else {
|
1229 |
// Image is different, remove file object.
|
1230 |
if (is_object($account->picture)) { |
1231 |
file_delete($account->picture, TRUE); |
1232 |
} |
1233 |
elseif (is_string($account->picture)) { |
1234 |
$file = file_load(intval($account->picture)); |
1235 |
file_delete($file, TRUE); |
1236 |
} |
1237 |
} |
1238 |
} |
1239 |
elseif (isset($account->data['ldap_user']['init']['thumb5md'])) { |
1240 |
watchdog('ldap_servers', "Some error happened during thumbnailPhoto sync."); |
1241 |
return FALSE; |
1242 |
} |
1243 |
} |
1244 |
return $this->savePictureData($image_data, $md5thumb); |
1245 |
} |
1246 |
return FALSE; |
1247 |
} |
1248 |
|
1249 |
/**
|
1250 |
* @param $image_data
|
1251 |
* @param $md5thumb
|
1252 |
*
|
1253 |
* @return bool|\stdClass
|
1254 |
*/
|
1255 |
private function savePictureData($image_data, $md5thumb) { |
1256 |
// Create tmp file to get image format.
|
1257 |
$filename = uniqid(); |
1258 |
$fileuri = file_directory_temp() . '/' . $filename; |
1259 |
$size = file_put_contents($fileuri, $image_data); |
1260 |
$info = image_get_info($fileuri); |
1261 |
unlink($fileuri); |
1262 |
// Create file object.
|
1263 |
$file = file_save_data($image_data, file_default_scheme() . '://' . variable_get('user_picture_path') . '/' . $filename . '.' . $info['extension']); |
1264 |
$file->md5Sum = $md5thumb; |
1265 |
// Standard Drupal validators for user pictures.
|
1266 |
$validators = [
|
1267 |
'file_validate_is_image' => [],
|
1268 |
'file_validate_image_resolution' => [variable_get('user_picture_dimensions', '85x85')], |
1269 |
'file_validate_size' => [variable_get('user_picture_file_size', '30') * 1024], |
1270 |
]; |
1271 |
$errors = file_validate($file, $validators); |
1272 |
if (empty($errors)) { |
1273 |
return $file; |
1274 |
} |
1275 |
else {
|
1276 |
foreach ($errors as $err => $err_val) { |
1277 |
watchdog('ldap_servers', "Error storing picture: %error", ["%error" => $err_val], WATCHDOG_ERROR); |
1278 |
} |
1279 |
return FALSE; |
1280 |
} |
1281 |
} |
1282 |
|
1283 |
/**
|
1284 |
* @param array $ldap_entry
|
1285 |
*
|
1286 |
* @return string
|
1287 |
* user's PUID or permanent user id (within ldap), converted from binary, if applicable
|
1288 |
*/
|
1289 |
public function userPuidFromLdapEntry($ldap_entry) { |
1290 |
|
1291 |
if ($this->unique_persistent_attr |
1292 |
&& isset($ldap_entry[$this->unique_persistent_attr][0]) |
1293 |
&& is_scalar($ldap_entry[$this->unique_persistent_attr][0]) |
1294 |
) { |
1295 |
if (is_array($ldap_entry[$this->unique_persistent_attr])) { |
1296 |
$puid = $ldap_entry[$this->unique_persistent_attr][0]; |
1297 |
} |
1298 |
else {
|
1299 |
$puid = $ldap_entry[$this->unique_persistent_attr]; |
1300 |
} |
1301 |
return ($this->unique_persistent_attr_binary) ? ldap_servers_binary($puid) : $puid; |
1302 |
} |
1303 |
else {
|
1304 |
return FALSE; |
1305 |
} |
1306 |
} |
1307 |
|
1308 |
/**
|
1309 |
* @param mixed $user
|
1310 |
* - drupal user object (stdClass Object)
|
1311 |
* - ldap entry of user (array)
|
1312 |
* - ldap dn of user (string)
|
1313 |
* - drupal username of user (string)
|
1314 |
*
|
1315 |
* @return array $ldap_user_entry (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
|
1316 |
*/
|
1317 |
public function user_lookup($user) { |
1318 |
return $this->userUserToExistingLdapEntry($user); |
1319 |
} |
1320 |
|
1321 |
/**
|
1322 |
*
|
1323 |
*/
|
1324 |
public function userUserToExistingLdapEntry($user) { |
1325 |
|
1326 |
if (is_object($user)) { |
1327 |
$user_ldap_entry = $this->userUserNameToExistingLdapEntry($user->name); |
1328 |
} |
1329 |
elseif (is_array($user)) { |
1330 |
$user_ldap_entry = $user; |
1331 |
} |
1332 |
elseif (is_scalar($user)) { |
1333 |
// Username.
|
1334 |
if (strpos($user, '=') === FALSE) { |
1335 |
$user_ldap_entry = $this->userUserNameToExistingLdapEntry($user); |
1336 |
} |
1337 |
else {
|
1338 |
$user_ldap_entry = $this->dnExists($user, 'ldap_entry'); |
1339 |
} |
1340 |
} |
1341 |
return $user_ldap_entry; |
1342 |
} |
1343 |
|
1344 |
/**
|
1345 |
* Queries LDAP server for the user.
|
1346 |
*
|
1347 |
* @param string $drupal_user_name
|
1348 |
*
|
1349 |
* @param string or int $prov_event
|
1350 |
* This could be anything, particularly when used by other modules.
|
1351 |
* Other modules should use string like 'mymodule_myevent'
|
1352 |
* LDAP_USER_EVENT_ALL signifies get all attributes needed by all other
|
1353 |
* contexts/ops.
|
1354 |
*
|
1355 |
* @return array
|
1356 |
* representing ldap data of a user. for example of returned value.
|
1357 |
* 'sid' => ldap server id
|
1358 |
* 'mail' => derived from ldap mail (not always populated).
|
1359 |
* 'dn' => dn of user
|
1360 |
* 'attr' => single ldap entry array in form returned from ldap_search() extension, e.g.
|
1361 |
* 'dn' => dn of entry
|
1362 |
*/
|
1363 |
public function userUserNameToExistingLdapEntry($drupal_user_name, $ldap_context = NULL) { |
1364 |
|
1365 |
$watchdog_tokens = ['%drupal_user_name' => $drupal_user_name]; |
1366 |
$ldap_username = $this->userUsernameToLdapNameTransform($drupal_user_name, $watchdog_tokens); |
1367 |
if (!$ldap_username) { |
1368 |
return FALSE; |
1369 |
} |
1370 |
if (!$ldap_context) { |
1371 |
$attributes = [];
|
1372 |
} |
1373 |
else {
|
1374 |
$attribute_maps = ldap_servers_attributes_needed($this->sid, $ldap_context); |
1375 |
$attributes = array_keys($attribute_maps); |
1376 |
} |
1377 |
|
1378 |
foreach ($this->basedn as $basedn) { |
1379 |
if (empty($basedn)) { |
1380 |
continue;
|
1381 |
} |
1382 |
$filter = '(' . $this->user_attr . '=' . ldap_server_massage_text($ldap_username, 'attr_value', LDAP_SERVER_MASSAGE_QUERY_LDAP) . ')'; |
1383 |
$result = $this->search($basedn, $filter, $attributes); |
1384 |
if (!$result || !isset($result['count']) || !$result['count']) { |
1385 |
continue;
|
1386 |
} |
1387 |
|
1388 |
// Must find exactly one user for authentication to work.
|
1389 |
if ($result['count'] != 1) { |
1390 |
$count = $result['count']; |
1391 |
watchdog('ldap_servers', "Error: !count users found with $filter under $basedn.", ['!count' => $count], WATCHDOG_ERROR); |
1392 |
continue;
|
1393 |
} |
1394 |
$match = $result[0]; |
1395 |
// These lines serve to fix the attribute name in case a
|
1396 |
// naughty server (i.e.: MS Active Directory) is messing the
|
1397 |
// characters' case.
|
1398 |
// This was contributed by Dan "Gribnif" Wilga, and described
|
1399 |
// here: http://drupal.org/node/87833
|
1400 |
$name_attr = $this->user_attr; |
1401 |
|
1402 |
if (isset($match[$name_attr][0])) { |
1403 |
// Leave name.
|
1404 |
} |
1405 |
elseif (isset($match[drupal_strtolower($name_attr)][0])) { |
1406 |
$name_attr = drupal_strtolower($name_attr); |
1407 |
|
1408 |
} |
1409 |
else {
|
1410 |
if ($this->bind_method == LDAP_SERVERS_BIND_METHOD_ANON_USER) { |
1411 |
$result = [
|
1412 |
'dn' => $match['dn'], |
1413 |
'mail' => $this->userEmailFromLdapEntry($match), |
1414 |
'attr' => $match, |
1415 |
'sid' => $this->sid, |
1416 |
]; |
1417 |
return $result; |
1418 |
} |
1419 |
else {
|
1420 |
continue;
|
1421 |
} |
1422 |
} |
1423 |
|
1424 |
// Finally, we must filter out results with spaces added before
|
1425 |
// or after, which are considered OK by LDAP but are no good for us
|
1426 |
// We allow lettercase independence, as requested by Marc Galera
|
1427 |
// on http://drupal.org/node/97728
|
1428 |
//
|
1429 |
// Some setups have multiple $name_attr per entry, as pointed out by
|
1430 |
// Clarence "sparr" Risher on http://drupal.org/node/102008, so we
|
1431 |
// loop through all possible options.
|
1432 |
foreach ($match[$name_attr] as $value) { |
1433 |
if (drupal_strtolower(trim($value)) == drupal_strtolower($ldap_username)) { |
1434 |
$result = [
|
1435 |
'dn' => $match['dn'], |
1436 |
'mail' => $this->userEmailFromLdapEntry($match), |
1437 |
'attr' => $match, |
1438 |
'sid' => $this->sid, |
1439 |
]; |
1440 |
return $result; |
1441 |
} |
1442 |
} |
1443 |
} |
1444 |
} |
1445 |
|
1446 |
/**
|
1447 |
* Is a user a member of group?
|
1448 |
*
|
1449 |
* @param string $group_dn
|
1450 |
* MIXED CASE.
|
1451 |
* @param mixed $user
|
1452 |
* - drupal user object (stdClass Object)
|
1453 |
* - ldap entry of user (array)
|
1454 |
* - ldap dn of user (array)
|
1455 |
* - drupal user name (string)
|
1456 |
* @param enum $nested
|
1457 |
* = NULL (default to server configuration), TRUE, or FALSE indicating to
|
1458 |
* test for nested groups.
|
1459 |
*
|
1460 |
* @return bool
|
1461 |
*/
|
1462 |
public function groupIsMember($group_dn, $user, $nested = NULL) { |
1463 |
|
1464 |
$nested = ($nested === TRUE || $nested === FALSE) ? $nested : $this->groupNested; |
1465 |
$group_dns = $this->groupMembershipsFromUser($user, 'group_dns', $nested); |
1466 |
// While list of group dns is going to be in correct mixed case, $group_dn may not since it may be derived from user entered values
|
1467 |
// so make sure in_array() is case insensitive.
|
1468 |
return (is_array($group_dns) && in_array(drupal_strtolower($group_dn), $this->dnArrayToLowerCase($group_dns))); |
1469 |
} |
1470 |
|
1471 |
/**
|
1472 |
* NOT TESTED
|
1473 |
* add a group entry.
|
1474 |
*
|
1475 |
* @param string $group_dn
|
1476 |
* as ldap dn.
|
1477 |
* @param array $attributes
|
1478 |
* in key value form
|
1479 |
* $attributes = array(
|
1480 |
* "attribute1" = "value",
|
1481 |
* "attribute2" = array("value1", "value2"),
|
1482 |
* )
|
1483 |
*
|
1484 |
* @return boolean success
|
1485 |
*/
|
1486 |
public function groupAddGroup($group_dn, $attributes = []) { |
1487 |
|
1488 |
if ($this->dnExists($group_dn, 'boolean')) { |
1489 |
return FALSE; |
1490 |
} |
1491 |
|
1492 |
$attributes = array_change_key_case($attributes, CASE_LOWER); |
1493 |
$objectclass = (empty($attributes['objectclass'])) ? $this->groupObjectClass : $attributes['objectclass']; |
1494 |
$attributes['objectclass'] = $objectclass; |
1495 |
|
1496 |
/**
|
1497 |
* 2. give other modules a chance to add or alter attributes
|
1498 |
*/
|
1499 |
$context = [
|
1500 |
'action' => 'add', |
1501 |
'corresponding_drupal_data' => [$group_dn => $attributes], |
1502 |
'corresponding_drupal_data_type' => 'group', |
1503 |
]; |
1504 |
$ldap_entries = [$group_dn => $attributes]; |
1505 |
drupal_alter('ldap_entry_pre_provision', $ldap_entries, $this, $context); |
1506 |
$attributes = $ldap_entries[$group_dn]; |
1507 |
|
1508 |
/**
|
1509 |
* 4. provision ldap entry
|
1510 |
* @todo how is error handling done here?
|
1511 |
*/
|
1512 |
$ldap_entry_created = $this->createLdapEntry($attributes, $group_dn); |
1513 |
|
1514 |
/**
|
1515 |
* 5. allow other modules to react to provisioned ldap entry
|
1516 |
* @todo how is error handling done here?
|
1517 |
*/
|
1518 |
if ($ldap_entry_created) { |
1519 |
module_invoke_all('ldap_entry_post_provision', $ldap_entries, $this, $context); |
1520 |
return TRUE; |
1521 |
} |
1522 |
else {
|
1523 |
return FALSE; |
1524 |
} |
1525 |
|
1526 |
} |
1527 |
|
1528 |
/**
|
1529 |
* NOT TESTED
|
1530 |
* remove a group entry.
|
1531 |
*
|
1532 |
* @param string $group_dn
|
1533 |
* as ldap dn.
|
1534 |
* @param bool $only_if_group_empty
|
1535 |
* TRUE = group should not be removed if not empty
|
1536 |
* FALSE = groups should be deleted regardless of members.
|
1537 |
*
|
1538 |
* @return bool
|
1539 |
*/
|
1540 |
public function groupRemoveGroup($group_dn, $only_if_group_empty = TRUE) { |
1541 |
|
1542 |
if ($only_if_group_empty) { |
1543 |
$members = $this->groupAllMembers($group_dn); |
1544 |
if (is_array($members) && count($members) > 0) { |
1545 |
return FALSE; |
1546 |
} |
1547 |
} |
1548 |
|
1549 |
return $this->delete($group_dn); |
1550 |
|
1551 |
} |
1552 |
|
1553 |
/**
|
1554 |
* NOT TESTED
|
1555 |
* add a member to a group.
|
1556 |
*
|
1557 |
* @param string $ldap_user_dn
|
1558 |
* as ldap dn.
|
1559 |
* @param mixed $user
|
1560 |
* - drupal user object (stdClass Object)
|
1561 |
* - ldap entry of user (array) (with top level keys of 'dn', 'mail',
|
1562 |
* 'sid' and 'attr' )
|
1563 |
* - ldap dn of user (array)
|
1564 |
* - drupal username of user (string)
|
1565 |
*
|
1566 |
* @return bool
|
1567 |
*/
|
1568 |
public function groupAddMember($group_dn, $user) { |
1569 |
|
1570 |
$user_ldap_entry = $this->userUserToExistingLdapEntry($user); |
1571 |
$result = FALSE; |
1572 |
if ($user_ldap_entry && $this->groupGroupEntryMembershipsConfigured) { |
1573 |
$add = [];
|
1574 |
$add[$this->groupMembershipsAttr] = $user_ldap_entry['dn']; |
1575 |
$this->connectAndBindIfNotAlready();
|
1576 |
$result = @ldap_mod_add($this->connection, $group_dn, $add); |
1577 |
} |
1578 |
|
1579 |
return $result; |
1580 |
} |
1581 |
|
1582 |
/**
|
1583 |
* NOT TESTED
|
1584 |
* remove a member from a group.
|
1585 |
*
|
1586 |
* @param string $group_dn
|
1587 |
* as ldap dn.
|
1588 |
* @param mixed $user
|
1589 |
* - drupal user object (stdClass Object)
|
1590 |
* - ldap entry of user (array) (with top level keys of 'dn', 'mail',
|
1591 |
* 'sid' and 'attr' )
|
1592 |
* - ldap dn of user (array)
|
1593 |
* - drupal username of user (string)
|
1594 |
*
|
1595 |
* @return bool
|
1596 |
*/
|
1597 |
public function groupRemoveMember($group_dn, $user) { |
1598 |
|
1599 |
$user_ldap_entry = $this->userUserToExistingLdapEntry($user); |
1600 |
$result = FALSE; |
1601 |
if ($user_ldap_entry && $this->groupGroupEntryMembershipsConfigured) { |
1602 |
$del = [];
|
1603 |
$del[$this->groupMembershipsAttr] = $user_ldap_entry['dn']; |
1604 |
$this->connectAndBindIfNotAlready();
|
1605 |
$result = @ldap_mod_del($this->connection, $group_dn, $del); |
1606 |
} |
1607 |
return $result; |
1608 |
} |
1609 |
|
1610 |
/**
|
1611 |
* Get all members of a group.
|
1612 |
*
|
1613 |
* @todo: NOT IMPLEMENTED: nested groups
|
1614 |
*
|
1615 |
* @param string $group_dn
|
1616 |
* as ldap dn.
|
1617 |
*
|
1618 |
* @return false
|
1619 |
* on error otherwise array of group members (could be users or groups)
|
1620 |
*/
|
1621 |
public function groupAllMembers($group_dn) { |
1622 |
if (!$this->groupGroupEntryMembershipsConfigured) { |
1623 |
return FALSE; |
1624 |
} |
1625 |
$attributes = [$this->groupMembershipsAttr, 'cn']; |
1626 |
$group_entry = $this->dnExists($group_dn, 'ldap_entry', $attributes); |
1627 |
if (!$group_entry) { |
1628 |
return FALSE; |
1629 |
} |
1630 |
else {
|
1631 |
// If attributes weren't returned, don't give false empty group.
|
1632 |
if (empty($group_entry['cn'])) { |
1633 |
return FALSE; |
1634 |
} |
1635 |
if (empty($group_entry[$this->groupMembershipsAttr])) { |
1636 |
// If no attribute returned, no members.
|
1637 |
return [];
|
1638 |
} |
1639 |
$members = $group_entry[$this->groupMembershipsAttr]; |
1640 |
if (isset($members['count'])) { |
1641 |
unset($members['count']); |
1642 |
} |
1643 |
return $members; |
1644 |
} |
1645 |
|
1646 |
$this->groupMembersResursive($current_group_entries, $all_group_dns, $tested_group_ids, 0, $max_levels, $object_classes); |
1647 |
|
1648 |
return $all_group_dns; |
1649 |
|
1650 |
} |
1651 |
|
1652 |
/**
|
1653 |
* NOT IMPLEMENTED
|
1654 |
* recurse through all child groups and add members.
|
1655 |
*
|
1656 |
* @param array $current_group_entries
|
1657 |
* of ldap group entries that are starting point. should include at least
|
1658 |
* 1 entry.
|
1659 |
* @param array $all_group_dns
|
1660 |
* as array of all groups user is a member of. MIXED CASE VALUES.
|
1661 |
* @param array $tested_group_ids
|
1662 |
* as array of tested group dn, cn, uid, etc. MIXED CASE VALUES
|
1663 |
* whether these value are dn, cn, uid, etc depends on what attribute
|
1664 |
* members, uniquemember, memberUid contains whatever attribute is in
|
1665 |
* $this->$tested_group_ids to avoid redundant recursing.
|
1666 |
* @param int $level
|
1667 |
* of recursion.
|
1668 |
* @param int $max_levels
|
1669 |
* as max recursion allowed.
|
1670 |
*
|
1671 |
* @return bool
|
1672 |
*/
|
1673 |
public function groupMembersResursive($current_member_entries, &$all_member_dns, &$tested_group_ids, $level, $max_levels, $object_classes = FALSE) { |
1674 |
|
1675 |
if (!$this->groupGroupEntryMembershipsConfigured || !is_array($current_member_entries) || count($current_member_entries) == 0) { |
1676 |
return FALSE; |
1677 |
} |
1678 |
if (isset($current_member_entries['count'])) { |
1679 |
unset($current_member_entries['count']); |
1680 |
}; |
1681 |
|
1682 |
foreach ($current_member_entries as $i => $member_entry) { |
1683 |
// 1. Add entry itself if of the correct type to $all_member_dns.
|
1684 |
$objectClassMatch = (!$object_classes || (count(array_intersect(array_values($member_entry['objectclass']), $object_classes)) > 0)); |
1685 |
$objectIsGroup = in_array($this->groupObjectClass, array_values($member_entry['objectclass'])); |
1686 |
// Add member.
|
1687 |
if ($objectClassMatch && !in_array($member_entry['dn'], $all_member_dns)) { |
1688 |
$all_member_dns[] = $member_entry['dn']; |
1689 |
} |
1690 |
|
1691 |
// 2. If its a group, keep recurse the group for descendants.
|
1692 |
if ($objectIsGroup && $level < $max_levels) { |
1693 |
if ($this->groupMembershipsAttrMatchingUserAttr == 'dn') { |
1694 |
$group_id = $member_entry['dn']; |
1695 |
} |
1696 |
else {
|
1697 |
$group_id = $member_entry[$this->groupMembershipsAttrMatchingUserAttr][0]; |
1698 |
} |
1699 |
// 3. skip any groups that have already been tested.
|
1700 |
if (!in_array($group_id, $tested_group_ids)) { |
1701 |
$tested_group_ids[] = $group_id; |
1702 |
$member_ids = $member_entry[$this->groupMembershipsAttr]; |
1703 |
if (isset($member_ids['count'])) { |
1704 |
unset($member_ids['count']); |
1705 |
}; |
1706 |
$ors = [];
|
1707 |
foreach ($member_ids as $i => $member_id) { |
1708 |
// @todo this would be replaced by query template
|
1709 |
$ors[] = $this->groupMembershipsAttr . '=' . ldap_pear_escape_filter_value($member_id); |
1710 |
} |
1711 |
|
1712 |
if (count($ors)) { |
1713 |
// e.g. (|(cn=group1)(cn=group2)) or (|(dn=cn=group1,ou=blah...)(dn=cn=group2,ou=blah...))
|
1714 |
$query_for_child_members = '(|(' . join(")(", $ors) . '))'; |
1715 |
// Add or on object classe, otherwise get all object classes.
|
1716 |
if (count($object_classes)) { |
1717 |
$object_classes_ors = ['(objectClass=' . $this->groupObjectClass . ')']; |
1718 |
foreach ($object_classes as $object_class) { |
1719 |
$object_classes_ors[] = '(objectClass=' . $object_class . ')'; |
1720 |
} |
1721 |
$query_for_child_members = '&(|' . join($object_classes_ors) . ')(' . $query_for_child_members . ')'; |
1722 |
} |
1723 |
// Need to search on all basedns one at a time.
|
1724 |
foreach ($this->basedn as $base_dn) { |
1725 |
$child_member_entries = $this->search($base_dn, $query_for_child_members, ['objectclass', $this->groupMembershipsAttr, $this->groupMembershipsAttrMatchingUserAttr]); |
1726 |
if ($child_member_entries !== FALSE) { |
1727 |
$this->groupMembersResursive($child_member_entries, $all_member_dns, $tested_group_ids, $level + 1, $max_levels, $object_classes); |
1728 |
} |
1729 |
} |
1730 |
} |
1731 |
} |
1732 |
} |
1733 |
} |
1734 |
} |
1735 |
|
1736 |
/**
|
1737 |
* Get list of all groups that a user is a member of.
|
1738 |
*
|
1739 |
* If $nested = TRUE,
|
1740 |
* list will include all parent group. That is if user is a member of "programmer" group
|
1741 |
* and "programmer" group is a member of "it" group, user is a member of
|
1742 |
* both "programmer" and "it" groups.
|
1743 |
*
|
1744 |
* If $nested = FALSE, list will only include groups user is in directly.
|
1745 |
*
|
1746 |
* @param mixed
|
1747 |
* - drupal user object (stdClass Object)
|
1748 |
* - ldap entry of user (array) (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
|
1749 |
* - ldap dn of user (array)
|
1750 |
* - drupal username of user (string)
|
1751 |
* @param mixed $return
|
1752 |
* = 'group_dns'.
|
1753 |
* @param bool $nested
|
1754 |
* if groups should be recursed or not.
|
1755 |
*
|
1756 |
* @return array of groups dns in mixed case or FALSE on error
|
1757 |
*/
|
1758 |
public function groupMembershipsFromUser($user, $return = 'group_dns', $nested = NULL) { |
1759 |
|
1760 |
$group_dns = FALSE; |
1761 |
$user_ldap_entry = @$this->userUserToExistingLdapEntry($user); |
1762 |
if (!$user_ldap_entry || $this->groupFunctionalityUnused) { |
1763 |
return FALSE; |
1764 |
} |
1765 |
if ($nested === NULL) { |
1766 |
$nested = $this->groupNested; |
1767 |
} |
1768 |
|
1769 |
// Preferred method.
|
1770 |
if ($this->groupUserMembershipsConfigured) { |
1771 |
$group_dns = $this->groupUserMembershipsFromUserAttr($user_ldap_entry, $nested); |
1772 |
} |
1773 |
elseif ($this->groupGroupEntryMembershipsConfigured) { |
1774 |
$group_dns = $this->groupUserMembershipsFromEntry($user_ldap_entry, $nested); |
1775 |
} |
1776 |
else {
|
1777 |
watchdog('ldap_servers', 'groupMembershipsFromUser: Group memberships for server have not been configured.', [], WATCHDOG_WARNING); |
1778 |
return FALSE; |
1779 |
} |
1780 |
if ($return == 'group_dns') { |
1781 |
return $group_dns; |
1782 |
} |
1783 |
|
1784 |
} |
1785 |
|
1786 |
/**
|
1787 |
* Get list of all groups that a user is a member of by using memberOf attribute first,
|
1788 |
* then if nesting is true, using group entries to find parent groups.
|
1789 |
*
|
1790 |
* If $nested = TRUE,
|
1791 |
* list will include all parent group. That is if user is a member of "programmer" group
|
1792 |
* and "programmer" group is a member of "it" group, user is a member of
|
1793 |
* both "programmer" and "it" groups.
|
1794 |
*
|
1795 |
* If $nested = FALSE, list will only include groups user is in directly.
|
1796 |
*
|
1797 |
* @param mixed
|
1798 |
* - drupal user object (stdClass Object)
|
1799 |
* - ldap entry of user (array) (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
|
1800 |
* - ldap dn of user (array)
|
1801 |
* - drupal username of user (string)
|
1802 |
* @param bool $nested
|
1803 |
* if groups should be recursed or not.
|
1804 |
*
|
1805 |
* @return array of group dns
|
1806 |
*/
|
1807 |
public function groupUserMembershipsFromUserAttr($user, $nested = NULL) { |
1808 |
|
1809 |
if (!$this->groupUserMembershipsConfigured) { |
1810 |
return FALSE; |
1811 |
} |
1812 |
if ($nested === NULL) { |
1813 |
$nested = $this->groupNested; |
1814 |
} |
1815 |
|
1816 |
$not_user_ldap_entry = empty($user['attr'][$this->groupUserMembershipsAttr]); |
1817 |
// If drupal user passed in, try to get user_ldap_entry.
|
1818 |
if ($not_user_ldap_entry) { |
1819 |
$user = $this->userUserToExistingLdapEntry($user); |
1820 |
$not_user_ldap_entry = empty($user['attr'][$this->groupUserMembershipsAttr]); |
1821 |
if ($not_user_ldap_entry) { |
1822 |
// user's membership attribute is not present. either misconfigured or query failed.
|
1823 |
return FALSE; |
1824 |
} |
1825 |
} |
1826 |
// If not exited yet, $user must be user_ldap_entry.
|
1827 |
$user_ldap_entry = $user; |
1828 |
$all_group_dns = [];
|
1829 |
$tested_group_ids = [];
|
1830 |
$level = 0; |
1831 |
|
1832 |
$member_group_dns = $user_ldap_entry['attr'][$this->groupUserMembershipsAttr]; |
1833 |
if (isset($member_group_dns['count'])) { |
1834 |
unset($member_group_dns['count']); |
1835 |
} |
1836 |
$ors = [];
|
1837 |
foreach ($member_group_dns as $i => $member_group_dn) { |
1838 |
$all_group_dns[] = $member_group_dn; |
1839 |
if ($nested) { |
1840 |
if ($this->groupMembershipsAttrMatchingUserAttr == 'dn') { |
1841 |
$member_value = $member_group_dn; |
1842 |
} |
1843 |
else {
|
1844 |
$member_value = ldap_servers_get_first_rdn_value_from_dn($member_group_dn, $this->groupMembershipsAttrMatchingUserAttr); |
1845 |
} |
1846 |
$ors[] = $this->groupMembershipsAttr . '=' . ldap_pear_escape_filter_value($member_value); |
1847 |
} |
1848 |
} |
1849 |
|
1850 |
if ($nested && count($ors)) { |
1851 |
$count = count($ors); |
1852 |
// Only 50 or so per query.
|
1853 |
for ($i = 0; $i < $count; $i = $i + LDAP_SERVER_LDAP_QUERY_CHUNK) { |
1854 |
$current_ors = array_slice($ors, $i, LDAP_SERVER_LDAP_QUERY_CHUNK); |
1855 |
// e.g. (|(cn=group1)(cn=group2)) or (|(dn=cn=group1,ou=blah...)(dn=cn=group2,ou=blah...))
|
1856 |
$or = '(|(' . join(")(", $current_ors) . '))'; |
1857 |
$query_for_parent_groups = '(&(objectClass=' . $this->groupObjectClass . ')' . $or . ')'; |
1858 |
|
1859 |
// Need to search on all basedns one at a time.
|
1860 |
foreach ($this->basedn as $base_dn) { |
1861 |
// No attributes, just dns needed.
|
1862 |
$group_entries = $this->search($base_dn, $query_for_parent_groups); |
1863 |
if ($group_entries !== FALSE && $level < LDAP_SERVER_LDAP_QUERY_RECURSION_LIMIT) { |
1864 |
$this->groupMembershipsFromEntryRecursive($group_entries, $all_group_dns, $tested_group_ids, $level + 1, LDAP_SERVER_LDAP_QUERY_RECURSION_LIMIT); |
1865 |
} |
1866 |
} |
1867 |
} |
1868 |
} |
1869 |
|
1870 |
return $all_group_dns; |
1871 |
} |
1872 |
|
1873 |
/**
|
1874 |
* Get list of all groups that a user is a member of by querying groups.
|
1875 |
*
|
1876 |
* If $nested = TRUE,
|
1877 |
* list will include all parent group. That is if user is a member of "programmer" group
|
1878 |
* and "programmer" group is a member of "it" group, user is a member of
|
1879 |
* both "programmer" and "it" groups.
|
1880 |
*
|
1881 |
* If $nested = FALSE, list will only include groups user is in directly.
|
1882 |
*
|
1883 |
* @param mixed
|
1884 |
* - drupal user object (stdClass Object)
|
1885 |
* - ldap entry of user (array) (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
|
1886 |
* - ldap dn of user (array)
|
1887 |
* - drupal username of user (string)
|
1888 |
* @param bool $nested
|
1889 |
* if groups should be recursed or not.
|
1890 |
*
|
1891 |
* @return array of group dns MIXED CASE VALUES
|
1892 |
*
|
1893 |
* @see tests/DeriveFromEntry/ldap_servers.inc for fuller notes and test example
|
1894 |
*/
|
1895 |
public function groupUserMembershipsFromEntry($user, $nested = NULL) { |
1896 |
|
1897 |
if (!$this->groupGroupEntryMembershipsConfigured) { |
1898 |
return FALSE; |
1899 |
} |
1900 |
if ($nested === NULL) { |
1901 |
$nested = $this->groupNested; |
1902 |
} |
1903 |
|
1904 |
$user_ldap_entry = $this->userUserToExistingLdapEntry($user); |
1905 |
|
1906 |
// MIXED CASE VALUES.
|
1907 |
$all_group_dns = [];
|
1908 |
// Array of dns already tested to avoid excess queries MIXED CASE VALUES.
|
1909 |
$tested_group_ids = [];
|
1910 |
$level = 0; |
1911 |
|
1912 |
if ($this->groupMembershipsAttrMatchingUserAttr == 'dn') { |
1913 |
$member_value = $user_ldap_entry['dn']; |
1914 |
} |
1915 |
else {
|
1916 |
$member_value = $user_ldap_entry['attr'][$this->groupMembershipsAttrMatchingUserAttr][0]; |
1917 |
} |
1918 |
$member_value = ldap_pear_escape_filter_value($member_value); |
1919 |
if ($this->groupObjectClass == '') { |
1920 |
$group_query = '(' . $this->groupMembershipsAttr . "=$member_value)"; |
1921 |
} |
1922 |
else {
|
1923 |
$group_query = '(&(objectClass=' . $this->groupObjectClass . ')(' . $this->groupMembershipsAttr . "=$member_value))"; |
1924 |
} |
1925 |
|
1926 |
// Need to search on all basedns one at a time.
|
1927 |
foreach ($this->basedn as $base_dn) { |
1928 |
// Only need dn, so empty array forces return of no attributes.
|
1929 |
$group_entries = $this->search($base_dn, $group_query, []); |
1930 |
if ($group_entries !== FALSE) { |
1931 |
$max_levels = ($nested) ? LDAP_SERVER_LDAP_QUERY_RECURSION_LIMIT : 0; |
1932 |
$this->groupMembershipsFromEntryRecursive($group_entries, $all_group_dns, $tested_group_ids, $level, $max_levels); |
1933 |
} |
1934 |
} |
1935 |
|
1936 |
return $all_group_dns; |
1937 |
} |
1938 |
|
1939 |
/**
|
1940 |
* Recurse through all groups, adding parent groups to $all_group_dns array.
|
1941 |
*
|
1942 |
* @param array $current_group_entries
|
1943 |
* of ldap group entries that are starting point. should include at least 1 entry.
|
1944 |
* @param array $all_group_dns
|
1945 |
* as array of all groups user is a member of. MIXED CASE VALUES.
|
1946 |
* @param array $tested_group_ids
|
1947 |
* as array of tested group dn, cn, uid, etc. MIXED CASE VALUES
|
1948 |
* whether these value are dn, cn, uid, etc depends on what attribute members, uniquemember, memberUid contains
|
1949 |
* whatever attribute is in $this->$tested_group_ids to avoid redundant recursing.
|
1950 |
* @param int $level
|
1951 |
* of recursion.
|
1952 |
* @param int $max_levels
|
1953 |
* as max recursion allowed
|
1954 |
*
|
1955 |
* given set of groups entries ($current_group_entries such as it, hr, accounting),
|
1956 |
* find parent groups (such as staff, people, users) and add them to list of group memberships ($all_group_dns)
|
1957 |
*
|
1958 |
* (&(objectClass=[$this->groupObjectClass])(|([$this->groupMembershipsAttr]=groupid1)([$this->groupMembershipsAttr]=groupid2))
|
1959 |
*
|
1960 |
* @return FALSE for error or misconfiguration, otherwise TRUE. results are passed by reference.
|
1961 |
*/
|
1962 |
public function groupMembershipsFromEntryRecursive($current_group_entries, &$all_group_dns, &$tested_group_ids, $level, $max_levels) { |
1963 |
|
1964 |
if (!$this->groupGroupEntryMembershipsConfigured || !is_array($current_group_entries) || count($current_group_entries) == 0) { |
1965 |
return FALSE; |
1966 |
} |
1967 |
if (isset($current_group_entries['count'])) { |
1968 |
unset($current_group_entries['count']); |
1969 |
}; |
1970 |
|
1971 |
$ors = [];
|
1972 |
foreach ($current_group_entries as $i => $group_entry) { |
1973 |
if ($this->groupMembershipsAttrMatchingUserAttr == 'dn') { |
1974 |
$member_id = $group_entry['dn']; |
1975 |
} |
1976 |
// Maybe cn, uid, etc is held.
|
1977 |
else {
|
1978 |
$member_id = ldap_servers_get_first_rdn_value_from_dn($group_entry['dn'], $this->groupMembershipsAttrMatchingUserAttr); |
1979 |
if (!$member_id) { |
1980 |
if ($this->detailed_watchdog_log) { |
1981 |
watchdog('ldap_servers', 'group_entry: %ge', ['%ge' => pretty_print_ldap_entry($group_entry)]); |
1982 |
} |
1983 |
// Group not identified by simple checks yet!
|
1984 |
// examine the entry and see if it matches the configured groupObjectClass
|
1985 |
// TODO do we need to ensure such entry is there?
|
1986 |
$goc = $group_entry['objectclass']; |
1987 |
// TODO is it always an array?
|
1988 |
if (is_array($goc)) { |
1989 |
foreach ($goc as $g) { |
1990 |
$g = drupal_strtolower($g); |
1991 |
if ($g == $this->groupObjectClass) { |
1992 |
// Found a group, current user must be member in it - so:
|
1993 |
if ($this->detailed_watchdog_log) { |
1994 |
watchdog('ldap_servers', 'adding %mi', ['%mi' => $member_id]); |
1995 |
} |
1996 |
$member_id = $group_entry['dn']; |
1997 |
break;
|
1998 |
} |
1999 |
} |
2000 |
} |
2001 |
} |
2002 |
} |
2003 |
|
2004 |
if ($member_id && !in_array($member_id, $tested_group_ids)) { |
2005 |
$tested_group_ids[] = $member_id; |
2006 |
$all_group_dns[] = $group_entry['dn']; |
2007 |
// Add $group_id (dn, cn, uid) to query.
|
2008 |
$ors[] = $this->groupMembershipsAttr . '=' . ldap_pear_escape_filter_value($member_id); |
2009 |
} |
2010 |
} |
2011 |
|
2012 |
if ($level < $max_levels && count($ors)) { |
2013 |
$count = count($ors); |
2014 |
// Only 50 or so per query.
|
2015 |
for ($i = 0; $i < $count; $i = $i + LDAP_SERVER_LDAP_QUERY_CHUNK) { |
2016 |
$current_ors = array_slice($ors, $i, LDAP_SERVER_LDAP_QUERY_CHUNK); |
2017 |
// e.g. (|(cn=group1)(cn=group2)) or (|(dn=cn=group1,ou=blah...)(dn=cn=group2,ou=blah...))
|
2018 |
$or = '(|(' . join(")(", $current_ors) . '))'; |
2019 |
$query_for_parent_groups = '(&(objectClass=' . $this->groupObjectClass . ')' . $or . ')'; |
2020 |
|
2021 |
// Need to search on all basedns one at a time.
|
2022 |
foreach ($this->basedn as $base_dn) { |
2023 |
// No attributes, just dns needed.
|
2024 |
$group_entries = $this->search($base_dn, $query_for_parent_groups); |
2025 |
if ($group_entries !== FALSE) { |
2026 |
$this->groupMembershipsFromEntryRecursive($group_entries, $all_group_dns, $tested_group_ids, $level + 1, $max_levels); |
2027 |
} |
2028 |
} |
2029 |
} |
2030 |
} |
2031 |
|
2032 |
return TRUE; |
2033 |
} |
2034 |
|
2035 |
/**
|
2036 |
* Get "groups" from derived from DN. Has limited usefulness.
|
2037 |
*
|
2038 |
* @param mixed
|
2039 |
* - drupal user object (stdClass Object)
|
2040 |
* - ldap entry of user (array) (with top level keys of 'dn', 'mail', 'sid' and 'attr' )
|
2041 |
* - ldap dn of user (array)
|
2042 |
* - drupal username of user (string)
|
2043 |
*
|
2044 |
* @return array of group strings
|
2045 |
*/
|
2046 |
public function groupUserMembershipsFromDn($user) { |
2047 |
|
2048 |
if (!$this->groupDeriveFromDn || !$this->groupDeriveFromDnAttr) { |
2049 |
return FALSE; |
2050 |
} |
2051 |
elseif ($user_ldap_entry = $this->userUserToExistingLdapEntry($user)) { |
2052 |
return ldap_servers_get_all_rdn_values_from_dn($user_ldap_entry['dn'], $this->groupDeriveFromDnAttr); |
2053 |
} |
2054 |
else {
|
2055 |
return FALSE; |
2056 |
} |
2057 |
|
2058 |
} |
2059 |
|
2060 |
/**
|
2061 |
* Error methods and properties.
|
2062 |
*/
|
2063 |
|
2064 |
public $detailedWatchdogLog = FALSE; |
2065 |
protected $_errorMsg = NULL; |
2066 |
protected $_hasError = FALSE; |
2067 |
protected $_errorName = NULL; |
2068 |
|
2069 |
/**
|
2070 |
*
|
2071 |
*/
|
2072 |
public function setError($_errorName, $_errorMsgText = NULL) { |
2073 |
$this->_errorMsgText = $_errorMsgText; |
2074 |
$this->_errorName = $_errorName; |
2075 |
$this->_hasError = TRUE; |
2076 |
} |
2077 |
|
2078 |
/**
|
2079 |
*
|
2080 |
*/
|
2081 |
public function clearError() { |
2082 |
$this->_hasError = FALSE; |
2083 |
$this->_errorMsg = NULL; |
2084 |
$this->_errorName = NULL; |
2085 |
} |
2086 |
|
2087 |
/**
|
2088 |
*
|
2089 |
*/
|
2090 |
public function hasError() { |
2091 |
return ($this->_hasError || $this->ldapErrorNumber()); |
2092 |
} |
2093 |
|
2094 |
/**
|
2095 |
*
|
2096 |
*/
|
2097 |
public function errorMsg($type = NULL) { |
2098 |
if ($type == 'ldap' && $this->connection) { |
2099 |
return ldap_err2str(ldap_errno($this->connection)); |
2100 |
} |
2101 |
elseif ($type == NULL) { |
2102 |
return $this->_errorMsg; |
2103 |
} |
2104 |
else {
|
2105 |
return NULL; |
2106 |
} |
2107 |
} |
2108 |
|
2109 |
/**
|
2110 |
*
|
2111 |
*/
|
2112 |
public function errorName($type = NULL) { |
2113 |
if ($type == 'ldap' && $this->connection) { |
2114 |
return "LDAP Error: " . ldap_error($this->connection); |
2115 |
} |
2116 |
elseif ($type == NULL) { |
2117 |
return $this->_errorName; |
2118 |
} |
2119 |
else {
|
2120 |
return NULL; |
2121 |
} |
2122 |
} |
2123 |
|
2124 |
/**
|
2125 |
*
|
2126 |
*/
|
2127 |
public function ldapErrorNumber() { |
2128 |
if ($this->connection && ldap_errno($this->connection)) { |
2129 |
return ldap_errno($this->connection); |
2130 |
} |
2131 |
else {
|
2132 |
return FALSE; |
2133 |
} |
2134 |
} |
2135 |
|
2136 |
} |
2137 |
|
2138 |
/**
|
2139 |
* Class for enabling rebind functionality for following referrrals.
|
2140 |
*/
|
2141 |
class LdapServersRebindHandler { |
2142 |
|
2143 |
private $bind_dn = 'Anonymous'; |
2144 |
private $bind_passwd = ''; |
2145 |
|
2146 |
/**
|
2147 |
*
|
2148 |
*/
|
2149 |
public function __construct($bind_user_dn, $bind_user_passwd) { |
2150 |
$this->bind_dn = $bind_user_dn; |
2151 |
$this->bind_passwd = $bind_user_passwd; |
2152 |
} |
2153 |
|
2154 |
/**
|
2155 |
*
|
2156 |
*/
|
2157 |
public function rebind_callback($ldap, $referral) { |
2158 |
// Ldap options.
|
2159 |
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3); |
2160 |
ldap_set_option($ldap, LDAP_OPT_REFERRALS, 1); |
2161 |
ldap_set_rebind_proc($ldap, [$this, 'rebind_callback']); |
2162 |
|
2163 |
// Bind to new host, assumes initial bind dn has access to the referred servers.
|
2164 |
if (!ldap_bind($ldap, $this->bind_dn, $this->bind_passwd)) { |
2165 |
echo "Could not bind to referral server: $referral"; |
2166 |
return 1; |
2167 |
} |
2168 |
return 0; |
2169 |
} |
2170 |
|
2171 |
} |