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