root / drupal7 / sites / all / modules / ldap / ldap_test / LdapServerTest.class.php @ a84ddea4
1 |
<?php
|
---|---|
2 |
|
3 |
/**
|
4 |
* @file
|
5 |
* Simpletest ldapServer class for testing without an actual ldap server
|
6 |
*
|
7 |
*/
|
8 |
|
9 |
/**
|
10 |
* LDAP Server Class
|
11 |
*
|
12 |
* This class is used to create, work with, and eventually destroy ldap_server
|
13 |
* objects.
|
14 |
*
|
15 |
* @todo make bindpw protected
|
16 |
*/
|
17 |
|
18 |
ldap_servers_module_load_include('php', 'ldap_servers', 'LdapServer.class'); |
19 |
|
20 |
class LdapServerTest extends LdapServer { |
21 |
|
22 |
public $entries; |
23 |
public $methodResponses; |
24 |
public $searchResults; |
25 |
public $binddn = FALSE; // Default to an anonymous bind. |
26 |
public $bindpw = FALSE; // Default to an anonymous bind. |
27 |
|
28 |
/**
|
29 |
* Constructor Method
|
30 |
*
|
31 |
* can take array of form property_name => property_value
|
32 |
* or $sid, where sid is used to derive the include file.
|
33 |
*/
|
34 |
function __construct($sid) { |
35 |
if (!is_scalar($sid)) { |
36 |
$test_data = $sid; |
37 |
$sid = $test_data['sid']; |
38 |
} |
39 |
else {
|
40 |
$test_data = variable_get('ldap_test_server__' . $sid, array()); |
41 |
} |
42 |
|
43 |
$bindpw = (isset($test_data['bindpw'])) ? $test_data['bindpw'] : 'goodpwd'; |
44 |
$this->sid = $sid; |
45 |
$this->refreshFakeData();
|
46 |
$this->initDerivedProperties($bindpw); |
47 |
} |
48 |
|
49 |
public function refreshFakeData() { |
50 |
// debug("refreshFakeData sid=". $this->sid);
|
51 |
$test_data = variable_get('ldap_test_server__' . $this->sid, array()); |
52 |
$this->methodResponses = (is_array($test_data) && isset($test_data['methodResponses'])) ? $test_data['methodResponses'] : array(); |
53 |
$this->entries = (is_array($test_data) && isset($test_data['ldap'])) ? $test_data['ldap'] : array(); |
54 |
// debug('this->entries');debug($this->entries);
|
55 |
$this->searchResults = (isset($test_data['search_results'])) ? $test_data['search_results'] : array(); |
56 |
$this->detailedWatchdogLog = variable_get('ldap_help_watchdog_detail', 0); |
57 |
foreach ($test_data['properties'] as $property_name => $property_value ) { |
58 |
$this->{$property_name} = $property_value; |
59 |
} |
60 |
// $this->basedn = unserialize($this->basedn);
|
61 |
if (isset($test_data['bindpw']) && $test_data['bindpw'] != '') { |
62 |
$this->bindpw = ldap_servers_decrypt($this->bindpw); |
63 |
} |
64 |
} |
65 |
|
66 |
/**
|
67 |
* Destructor Method
|
68 |
*/
|
69 |
function __destruct() { |
70 |
// if alterations to server configuration must be maintained throughout simpletest, variable_set('ldap_authorization_test_server__'. $sid, array());
|
71 |
} |
72 |
|
73 |
/**
|
74 |
* Connect Method
|
75 |
*/
|
76 |
function connect() { |
77 |
return $this->methodResponses['connect']; |
78 |
} |
79 |
|
80 |
|
81 |
function bind($userdn = NULL, $pass = NULL, $anon_bind = FALSE) { |
82 |
$userdn = ($userdn != NULL) ? $userdn : $this->binddn; |
83 |
$pass = ($pass != NULL) ? $pass : $this->bindpw; |
84 |
|
85 |
if (! isset($this->entries[$userdn])) { |
86 |
$ldap_errno = LDAP_NO_SUCH_OBJECT; // 0x20 or 32 |
87 |
if (function_exists('ldap_err2str')) { |
88 |
$ldap_error = ldap_err2str($ldap_errno); |
89 |
} |
90 |
else {
|
91 |
$ldap_error = "Failed to find $userdn in LdapServerTest.class.php"; |
92 |
} |
93 |
} |
94 |
elseif (isset($this->entries[$userdn]['password'][0]) && $this->entries[$userdn]['password'][0] == $pass && $pass) { |
95 |
return LDAP_SUCCESS; |
96 |
} |
97 |
else {
|
98 |
if (!$pass) { |
99 |
debug("Simpletest failure for $userdn. No password submitted");
|
100 |
} |
101 |
if (! isset($this->entries[$userdn]['password'][0])) { |
102 |
debug("Simpletest failure for $userdn. No password in entry to test for bind"); debug($this->entries[$userdn]); |
103 |
} |
104 |
$ldap_errno = LDAP_INVALID_CREDENTIALS; |
105 |
if (function_exists('ldap_err2str')) { |
106 |
$ldap_error = ldap_err2str($ldap_errno); |
107 |
} |
108 |
else {
|
109 |
$ldap_error = "Credentials for $userdn failed in LdapServerTest.class.php"; |
110 |
} |
111 |
} |
112 |
|
113 |
$watchdog_tokens = array('%user' => $userdn, '%errno' => $ldap_errno, '%error' => $ldap_error); |
114 |
watchdog('ldap', "LDAP bind failure for user %user. Error %errno: %error", $watchdog_tokens); |
115 |
return $ldap_errno; |
116 |
|
117 |
} |
118 |
|
119 |
/**
|
120 |
* Disconnect (unbind) from an active LDAP server.
|
121 |
*/
|
122 |
function disconnect() { |
123 |
|
124 |
} |
125 |
|
126 |
/**
|
127 |
* Perform an LDAP search.
|
128 |
*
|
129 |
* @param string $filter
|
130 |
* The search filter. such as sAMAccountName=jbarclay
|
131 |
* @param string $basedn
|
132 |
* The search base. If NULL, we use $this->basedn
|
133 |
* @param array $attributes
|
134 |
* List of desired attributes. If omitted, we only return "dn".
|
135 |
*
|
136 |
* @return
|
137 |
* An array of matching entries->attributes, or FALSE if the search is
|
138 |
* empty.
|
139 |
*/
|
140 |
function search($base_dn = NULL, $filter, $attributes = array(), $attrsonly = 0, $sizelimit = 0, $timelimit = 0, $deref = LDAP_DEREF_NEVER, $scope = LDAP_SCOPE_SUBTREE) { |
141 |
|
142 |
// debug("ldap test server search base_dn=$base_dn, filter=$filter");
|
143 |
|
144 |
$lcase_attribute = array(); |
145 |
foreach ($attributes as $i => $attribute_name) { |
146 |
$lcase_attribute[] = drupal_strtolower($attribute_name); |
147 |
} |
148 |
$attributes = $lcase_attribute; |
149 |
|
150 |
$filter = trim(str_replace(array("\n", " "), array('', ''), $filter)); // for test matching simplicity remove line breaks and tab spacing |
151 |
|
152 |
if ($base_dn == NULL) { |
153 |
if (count($this->basedn) == 1) { |
154 |
$base_dn = $this->basedn[0]; |
155 |
} |
156 |
else {
|
157 |
// debug("fail basedn: ldap test server search base_dn=$base_dn, filter=$filter");
|
158 |
return FALSE; |
159 |
} |
160 |
} |
161 |
|
162 |
/**
|
163 |
* Search CASE 1: for some mock ldap servers, a set of fixed ldap filters
|
164 |
* are prepolulated in test data
|
165 |
*/
|
166 |
if (isset($this->searchResults[$filter][$base_dn])) { |
167 |
// debug("case1 filter= $filter base_dn=$base_dn ");
|
168 |
$results = $this->searchResults[$filter][$base_dn]; |
169 |
foreach ($results as $i => $entry) { |
170 |
if (is_array($entry) && isset($entry['FULLENTRY'])) { |
171 |
unset($results[$i]['FULLENTRY']); |
172 |
$dn = $results[$i]['dn']; |
173 |
$results[$i] = $this->entries[$dn]; |
174 |
$results[$i]['dn'] = $dn; |
175 |
} |
176 |
} |
177 |
// debug($results);
|
178 |
return $results; |
179 |
} |
180 |
|
181 |
/**
|
182 |
* Search CASE 2: attempt to programmatically evaluate ldap filter
|
183 |
* by looping through fake ldap entries
|
184 |
*/
|
185 |
$base_dn = drupal_strtolower($base_dn); |
186 |
$filter = trim($filter, "()"); |
187 |
$subqueries = array(); |
188 |
$operand = FALSE; |
189 |
|
190 |
if (strpos($filter, '&') === 0) { |
191 |
// debug('2.A.');
|
192 |
/**
|
193 |
* case 2.A.: filter of form (&(<attribute>=<value>)(<attribute>=<value>)(<attribute>=<value>))
|
194 |
* such as (&(samaccountname=hpotter)(samaccountname=hpotter)(samaccountname=hpotter))
|
195 |
*/
|
196 |
$operand = '&'; |
197 |
$filter = substr($filter, 1); |
198 |
$filter = trim($filter, "()"); |
199 |
$parts = explode(')(', $filter); |
200 |
foreach ($parts as $i => $pair) { |
201 |
$subqueries[] = explode('=', $pair); |
202 |
} |
203 |
} |
204 |
elseif (strpos($filter, '|') === 0) { |
205 |
//debug('2.B.');
|
206 |
/**
|
207 |
* case 2.B: filter of form (|(<attribute>=<value>)(<attribute>=<value>)(<attribute>=<value>))
|
208 |
* such as (|(samaccountname=hpotter)(samaccountname=hpotter)(samaccountname=hpotter))
|
209 |
*/
|
210 |
$operand = '|'; |
211 |
$filter = substr($filter, 1); |
212 |
$filter = trim($filter, "()"); |
213 |
$parts = explode(')(', $filter); |
214 |
$parts = explode(')(', $filter); |
215 |
foreach ($parts as $i => $pair) { |
216 |
$subqueries[] = explode('=', $pair); |
217 |
} |
218 |
// debug("operand=$operand, filter=$filter subqueries"); debug($subqueries);
|
219 |
// debug($this->entries['cn=hpotter,ou=people,dc=hogwarts,dc=edu']);
|
220 |
// debug($this->entries['cn=clone0,ou=people,dc=hogwarts,dc=edu']);
|
221 |
} |
222 |
elseif (count(explode('=', $filter)) == 2) { |
223 |
// debug('2.C.');
|
224 |
/**
|
225 |
* case 2.C.: filter of form (<attribute>=<value>)
|
226 |
* such as (samaccountname=hpotter)
|
227 |
*/
|
228 |
$operand = '|'; |
229 |
$subqueries[] = explode('=', $filter); |
230 |
} |
231 |
else {
|
232 |
// debug('no case');
|
233 |
return FALSE; |
234 |
} |
235 |
|
236 |
|
237 |
|
238 |
|
239 |
// need to perform feaux ldap search here with data in
|
240 |
$results = array(); |
241 |
|
242 |
if ($operand == '|') { |
243 |
foreach ($subqueries as $i => $subquery) { |
244 |
$filter_attribute = drupal_strtolower($subquery[0]); |
245 |
$filter_value = $subquery[1]; |
246 |
// debug("filter_attribute=$filter_attribute, filter_value=$filter_value");
|
247 |
foreach ($this->entries as $dn => $entry) { |
248 |
$dn_lcase = drupal_strtolower($dn); |
249 |
|
250 |
// if not in basedn, skip
|
251 |
// eg. basedn ou=campus accounts,dc=ad,dc=myuniversity,dc=edu
|
252 |
// should be leftmost string in:
|
253 |
// cn=jdoe,ou=campus accounts,dc=ad,dc=myuniversity,dc=edu
|
254 |
//$pos = strpos($dn_lcase, $base_dn);
|
255 |
$substring = strrev(substr(strrev($dn_lcase), 0, strlen($base_dn))); |
256 |
$cascmp = strcasecmp($base_dn, $substring); |
257 |
//debug("dn_lcase=$dn_lcase, base_dn=$base_dn,pos=$pos,substring=$substring,cascmp=$cascmp");
|
258 |
if ($cascmp !== 0) { |
259 |
|
260 |
continue; // not in basedn |
261 |
} |
262 |
// if doesn't filter attribute has no data, continue
|
263 |
$attr_value_to_compare = FALSE; |
264 |
foreach ($entry as $attr_name => $attr_value) { |
265 |
if (drupal_strtolower($attr_name) == $filter_attribute) { |
266 |
$attr_value_to_compare = $attr_value; |
267 |
break;
|
268 |
} |
269 |
} |
270 |
// debug("filter value=$filter_value, attr_value_to_compare="); debug($attr_value_to_compare);
|
271 |
if (!$attr_value_to_compare || drupal_strtolower($attr_value_to_compare[0]) != $filter_value) { |
272 |
continue;
|
273 |
} |
274 |
|
275 |
// match!
|
276 |
// debug("match"); debug($attr_value); debug($attributes);
|
277 |
$entry['dn'] = $dn; |
278 |
if ($attributes) { |
279 |
$selected_data = array(); |
280 |
foreach ($attributes as $i => $attr_name) { |
281 |
$selected_data[$attr_name] = (isset($entry[$attr_name])) ? $entry[$attr_name] : NULL; |
282 |
} |
283 |
$results[] = $selected_data; |
284 |
} |
285 |
else {
|
286 |
$results[] = $entry; |
287 |
} |
288 |
} |
289 |
} |
290 |
} |
291 |
elseif ($operand == '&') { // reverse the loops |
292 |
foreach ($this->entries as $dn => $entry) { |
293 |
$dn_lcase = drupal_strtolower($dn); |
294 |
$match = TRUE; // until 1 subquery fails |
295 |
foreach ($subqueries as $i => $subquery) { |
296 |
$filter_attribute = drupal_strtolower($subquery[0]); |
297 |
$filter_value = $subquery[1]; |
298 |
|
299 |
$substring = strrev(substr(strrev($dn_lcase), 0, strlen($base_dn))); |
300 |
$cascmp = strcasecmp($base_dn, $substring); |
301 |
//debug("dn_lcase=$dn_lcase, base_dn=$base_dn,pos=$pos,substring=$substring,cascmp=$cascmp");
|
302 |
if ($cascmp !== 0) { |
303 |
$match = FALSE; |
304 |
break; // not in basedn |
305 |
} |
306 |
// if doesn't filter attribute has no data, continue
|
307 |
$attr_value_to_compare = FALSE; |
308 |
foreach ($entry as $attr_name => $attr_value) { |
309 |
if (drupal_strtolower($attr_name) == $filter_attribute) { |
310 |
$attr_value_to_compare = $attr_value; |
311 |
break;
|
312 |
} |
313 |
} |
314 |
// debug("filter value=$filter_value, attr_value_to_compare="); debug($attr_value_to_compare);
|
315 |
if (!$attr_value_to_compare || drupal_strtolower($attr_value_to_compare[0]) != $filter_value) { |
316 |
$match = FALSE; |
317 |
break; // not in basedn |
318 |
} |
319 |
|
320 |
} |
321 |
if ($match === TRUE) { |
322 |
$entry['dn'] = $dn; |
323 |
if ($attributes) { |
324 |
$selected_data = array(); |
325 |
foreach ($attributes as $i => $attr_name) { |
326 |
$selected_data[$attr_name] = (isset($entry[$attr_name])) ? $entry[$attr_name] : NULL; |
327 |
} |
328 |
$results[] = $selected_data; |
329 |
} |
330 |
else {
|
331 |
$results[] = $entry; |
332 |
} |
333 |
} |
334 |
} |
335 |
} |
336 |
|
337 |
$results['count'] = count($results); |
338 |
// debug("ldap test server search results"); debug($results);
|
339 |
return $results; |
340 |
} |
341 |
|
342 |
/**
|
343 |
* does dn exist for this server?
|
344 |
*
|
345 |
* @param string $dn
|
346 |
* @param enum $return = 'boolean' or 'ldap_entry'
|
347 |
*
|
348 |
* @param return FALSE or ldap entry array
|
349 |
*/
|
350 |
function dnExists($find_dn, $return = 'boolean', $attributes = array('objectclass')) { |
351 |
$this->refreshFakeData();
|
352 |
$test_data = variable_get('ldap_test_server__' . $this->sid, array()); |
353 |
foreach ($this->entries as $entry_dn => $entry) { |
354 |
$match = (strcasecmp($entry_dn, $find_dn) == 0); |
355 |
|
356 |
if ($match) { |
357 |
// debug("testserver:dnExists,match=$match, entry_dn=$entry_dn, find_dn=$find_dn"); debug($entry);
|
358 |
return ($return == 'boolean') ? TRUE : $entry; |
359 |
} |
360 |
} |
361 |
// debug("testserver:dnExists, no match");
|
362 |
return FALSE; // not match found in loop |
363 |
|
364 |
} |
365 |
|
366 |
public function countEntries($ldap_result) { |
367 |
return ldap_count_entries($this->connection, $ldap_result); |
368 |
} |
369 |
|
370 |
|
371 |
public static function getLdapServerObjects($sid = NULL, $type = NULL, $flatten = FALSE) { |
372 |
$servers = array(); |
373 |
if ($sid) { |
374 |
$servers[$sid] = new LdapServerTest($sid); |
375 |
} |
376 |
else {
|
377 |
$server_ids = variable_get('ldap_test_servers', array()); |
378 |
foreach ($server_ids as $sid => $_sid) { |
379 |
$servers[$sid] = new LdapServerTest($sid); |
380 |
} |
381 |
} |
382 |
|
383 |
if ($flatten && $sid) { |
384 |
return $servers[$sid]; |
385 |
} |
386 |
else {
|
387 |
return $servers; |
388 |
} |
389 |
} |
390 |
|
391 |
|
392 |
/**
|
393 |
* create ldap entry.
|
394 |
*
|
395 |
* @param array $ldap_entry should follow the structure of ldap_add functions
|
396 |
* entry array: http://us.php.net/manual/en/function.ldap-add.php
|
397 |
$attributes["attribute1"] = "value";
|
398 |
$attributes["attribute2"][0] = "value1";
|
399 |
$attributes["attribute2"][1] = "value2";
|
400 |
* @return boolean result
|
401 |
*/
|
402 |
|
403 |
public function createLdapEntry($ldap_entry, $dn = NULL) { |
404 |
$result = FALSE; |
405 |
$test_data = variable_get('ldap_test_server__' . $this->sid, array()); |
406 |
|
407 |
if (isset($ldap_entry['dn'])) { |
408 |
$dn = $ldap_entry['dn']; |
409 |
unset($ldap_entry['dn']); |
410 |
} |
411 |
|
412 |
if ($dn && !isset($test_data['entries'][$dn])) { |
413 |
$test_data['entries'][$dn] = $ldap_entry; |
414 |
$test_data['ldap'][$dn] = $ldap_entry; |
415 |
variable_set('ldap_test_server__' . $this->sid, $test_data); |
416 |
$this->refreshFakeData();
|
417 |
$result = TRUE; |
418 |
} |
419 |
return $result; |
420 |
} |
421 |
|
422 |
function modifyLdapEntry($dn, $attributes = NULL, $old_attributes = FALSE) { |
423 |
if (!$attributes) { |
424 |
$attributes = array(); |
425 |
} |
426 |
$test_data = variable_get('ldap_test_server__' . $this->sid, array()); |
427 |
if (!isset($test_data['entries'][$dn])) { |
428 |
return FALSE; |
429 |
} |
430 |
$ldap_entry = $test_data['entries'][$dn]; |
431 |
|
432 |
foreach ($attributes as $key => $cur_val) { |
433 |
if ($cur_val == '') { |
434 |
unset($ldap_entry[$key]); |
435 |
} |
436 |
elseif (is_array($cur_val)) { |
437 |
foreach ($cur_val as $mv_key => $mv_cur_val) { |
438 |
if ($mv_cur_val == '') { |
439 |
unset($ldap_entry[$key][$mv_key]); |
440 |
} |
441 |
else {
|
442 |
if (is_array($mv_cur_val)) { |
443 |
$ldap_entry[$key][$mv_key] = $mv_cur_val; |
444 |
} |
445 |
else {
|
446 |
$ldap_entry[$key][$mv_key][] = $mv_cur_val; |
447 |
} |
448 |
} |
449 |
} |
450 |
unset($ldap_entry[$key]['count']); |
451 |
$ldap_entry[$key]['count'] = count($ldap_entry[$key]); |
452 |
} |
453 |
else {
|
454 |
$ldap_entry[$key][0] = $cur_val; |
455 |
$ldap_entry[$key]['count'] = count($ldap_entry[$key]); |
456 |
} |
457 |
} |
458 |
|
459 |
$test_data['entries'][$dn] = $ldap_entry; |
460 |
$test_data['ldap'][$dn] = $ldap_entry; |
461 |
// debug("modifyLdapEntry:server test data before save $dn"); debug($test_data['entries'][$dn]);
|
462 |
variable_set('ldap_test_server__' . $this->sid, $test_data); |
463 |
$this->refreshFakeData();
|
464 |
return TRUE; |
465 |
|
466 |
} |
467 |
|
468 |
/**
|
469 |
* Perform an LDAP delete.
|
470 |
*
|
471 |
* @param string $dn
|
472 |
*
|
473 |
* @return boolean result per ldap_delete
|
474 |
*/
|
475 |
|
476 |
public function delete($dn) { |
477 |
|
478 |
$test_data = variable_get('ldap_test_server__' . $this->sid, array()); |
479 |
// debug("test ldap server, delete=$dn, test data="); debug(array_keys($test_data['users']));
|
480 |
$deleted = FALSE; |
481 |
foreach (array('entries', 'users', 'groups', 'ldap') as $test_data_sub_array) { |
482 |
if (isset($test_data[$test_data_sub_array][$dn])) { |
483 |
unset($test_data[$test_data_sub_array][$dn]); |
484 |
$deleted = TRUE; |
485 |
} |
486 |
} |
487 |
if ($deleted) { |
488 |
variable_set('ldap_test_server__' . $this->sid, $test_data); |
489 |
$this->refreshFakeData();
|
490 |
return TRUE; |
491 |
} |
492 |
else {
|
493 |
return FALSE; |
494 |
} |
495 |
|
496 |
} |
497 |
|
498 |
} |