Commit | Line | Data |
---|---|---|
cc220325 IA |
1 | <?php |
2 | // This file is part of Moodle - http://moodle.org/ | |
3 | // | |
4 | // Moodle is free software: you can redistribute it and/or modify | |
5 | // it under the terms of the GNU General Public License as published by | |
6 | // the Free Software Foundation, either version 3 of the License, or | |
7 | // (at your option) any later version. | |
8 | // | |
9 | // Moodle is distributed in the hope that it will be useful, | |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | // GNU General Public License for more details. | |
13 | // | |
14 | // You should have received a copy of the GNU General Public License | |
15 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
16 | ||
17 | /** | |
18 | * ldap tests. | |
19 | * | |
20 | * @package core | |
21 | * @category phpunit | |
22 | * @copyright Damyon Wiese, Iñaki Arenaza 2014 | |
23 | * @license http://www.gnu.org/copyleft/gpl.html GNU Public License | |
24 | */ | |
25 | ||
26 | defined('MOODLE_INTERNAL') || die(); | |
27 | ||
28 | global $CFG; | |
29 | require_once($CFG->libdir . '/ldaplib.php'); | |
30 | ||
31 | class core_ldaplib_testcase extends advanced_testcase { | |
32 | ||
33 | public function test_ldap_addslashes() { | |
34 | // See http://tools.ietf.org/html/rfc4514#section-5.2 if you want | |
35 | // to add additional tests. | |
36 | ||
37 | $tests = array( | |
38 | array ( | |
39 | 'test' => 'Simplest', | |
40 | 'expected' => 'Simplest', | |
41 | ), | |
42 | array ( | |
43 | 'test' => 'Simple case', | |
44 | 'expected' => 'Simple\\20case', | |
45 | ), | |
46 | array ( | |
47 | 'test' => 'Medium ‒ case', | |
48 | 'expected' => 'Medium\\20‒\\20case', | |
49 | ), | |
50 | array ( | |
51 | 'test' => '#Harder+case#', | |
52 | 'expected' => '\\23Harder\\2bcase\\23', | |
53 | ), | |
54 | array ( | |
55 | 'test' => ' Harder (and); harder case ', | |
56 | 'expected' => '\\20Harder\\20(and)\\3b\\20harder\\20case\\20', | |
57 | ), | |
58 | array ( | |
59 | 'test' => 'Really \\0 (hard) case!\\', | |
60 | 'expected' => 'Really\\20\\5c0\\20(hard)\\20case!\\5c', | |
61 | ), | |
62 | array ( | |
63 | 'test' => 'James "Jim" = Smith, III', | |
64 | 'expected' => 'James\\20\\22Jim\22\\20\\3d\\20Smith\\2c\\20III', | |
65 | ), | |
66 | array ( | |
0fe86bbd RT |
67 | 'test' => ' <jsmith@example.com> ', |
68 | 'expected' => '\\20\\20\\3cjsmith@example.com\\3e\\20', | |
cc220325 IA |
69 | ), |
70 | ); | |
71 | ||
72 | ||
73 | foreach ($tests as $test) { | |
74 | $this->assertSame($test['expected'], ldap_addslashes($test['test'])); | |
75 | } | |
76 | } | |
77 | ||
78 | public function test_ldap_stripslashes() { | |
79 | // See http://tools.ietf.org/html/rfc4514#section-5.2 if you want | |
80 | // to add additional tests. | |
81 | ||
82 | // IMPORTANT NOTICE: While ldap_addslashes() only produces one | |
83 | // of the two defined ways of escaping/quoting (the ESC HEX | |
84 | // HEX way defined in the grammar in Section 3 of RFC-4514) | |
85 | // ldap_stripslashes() has to deal with both of them. So in | |
86 | // addition to testing the same strings we test in | |
87 | // test_ldap_stripslashes(), we need to also test strings | |
88 | // using the second method. | |
89 | ||
90 | $tests = array( | |
91 | array ( | |
92 | 'test' => 'Simplest', | |
93 | 'expected' => 'Simplest', | |
94 | ), | |
95 | array ( | |
96 | 'test' => 'Simple\\20case', | |
97 | 'expected' => 'Simple case', | |
98 | ), | |
99 | array ( | |
100 | 'test' => 'Simple\\ case', | |
101 | 'expected' => 'Simple case', | |
102 | ), | |
103 | array ( | |
104 | 'test' => 'Simple\\ \\63\\61\\73\\65', | |
105 | 'expected' => 'Simple case', | |
106 | ), | |
107 | array ( | |
108 | 'test' => 'Medium\\ ‒\\ case', | |
109 | 'expected' => 'Medium ‒ case', | |
110 | ), | |
111 | array ( | |
112 | 'test' => 'Medium\\20‒\\20case', | |
113 | 'expected' => 'Medium ‒ case', | |
114 | ), | |
115 | array ( | |
116 | 'test' => 'Medium\\20\\E2\\80\\92\\20case', | |
117 | 'expected' => 'Medium ‒ case', | |
118 | ), | |
119 | array ( | |
120 | 'test' => '\\23Harder\\2bcase\\23', | |
121 | 'expected' => '#Harder+case#', | |
122 | ), | |
123 | array ( | |
124 | 'test' => '\\#Harder\\+case\\#', | |
125 | 'expected' => '#Harder+case#', | |
126 | ), | |
127 | array ( | |
128 | 'test' => '\\20Harder\\20(and)\\3b\\20harder\\20case\\20', | |
129 | 'expected' => ' Harder (and); harder case ', | |
130 | ), | |
131 | array ( | |
132 | 'test' => '\\ Harder\\ (and)\\;\\ harder\\ case\\ ', | |
133 | 'expected' => ' Harder (and); harder case ', | |
134 | ), | |
135 | array ( | |
136 | 'test' => 'Really\\20\\5c0\\20(hard)\\20case!\\5c', | |
137 | 'expected' => 'Really \\0 (hard) case!\\', | |
138 | ), | |
139 | array ( | |
140 | 'test' => 'Really\\ \\\\0\\ (hard)\\ case!\\\\', | |
141 | 'expected' => 'Really \\0 (hard) case!\\', | |
142 | ), | |
143 | array ( | |
144 | 'test' => 'James\\20\\22Jim\\22\\20\\3d\\20Smith\\2c\\20III', | |
145 | 'expected' => 'James "Jim" = Smith, III', | |
146 | ), | |
147 | array ( | |
148 | 'test' => 'James\\ \\"Jim\\" \\= Smith\\, III', | |
149 | 'expected' => 'James "Jim" = Smith, III', | |
150 | ), | |
151 | array ( | |
0fe86bbd RT |
152 | 'test' => '\\20\\20\\3cjsmith@example.com\\3e\\20', |
153 | 'expected' => ' <jsmith@example.com> ', | |
cc220325 IA |
154 | ), |
155 | array ( | |
0fe86bbd RT |
156 | 'test' => '\\ \\<jsmith@example.com\\>\\ ', |
157 | 'expected' => ' <jsmith@example.com> ', | |
cc220325 IA |
158 | ), |
159 | array ( | |
160 | 'test' => 'Lu\\C4\\8Di\\C4\\87', | |
161 | 'expected' => 'Lučić', | |
162 | ), | |
163 | ); | |
164 | ||
165 | foreach ($tests as $test) { | |
166 | $this->assertSame($test['expected'], ldap_stripslashes($test['test'])); | |
167 | } | |
168 | } | |
abedeb8c AN |
169 | |
170 | /** | |
171 | * Tests for ldap_normalise_objectclass. | |
172 | * | |
173 | * @dataProvider ldap_normalise_objectclass_provider | |
174 | * @param array $args Arguments passed to ldap_normalise_objectclass | |
175 | * @param string $expected The expected objectclass filter | |
176 | */ | |
177 | public function test_ldap_normalise_objectclass($args, $expected) { | |
178 | $this->assertEquals($expected, call_user_func_array('ldap_normalise_objectclass', $args)); | |
179 | } | |
180 | ||
181 | /** | |
182 | * Data provider for the test_ldap_normalise_objectclass testcase. | |
183 | * | |
184 | * @return array of testcases. | |
185 | */ | |
186 | public function ldap_normalise_objectclass_provider() { | |
187 | return array( | |
188 | 'Empty value' => array( | |
189 | array(null), | |
190 | '(objectClass=*)', | |
191 | ), | |
192 | 'Empty value with different default' => array( | |
193 | array(null, 'lion'), | |
194 | '(objectClass=lion)', | |
195 | ), | |
196 | 'Supplied unwrapped objectClass' => array( | |
197 | array('objectClass=tiger'), | |
198 | '(objectClass=tiger)', | |
199 | ), | |
200 | 'Supplied string value' => array( | |
201 | array('leopard'), | |
202 | '(objectClass=leopard)', | |
203 | ), | |
204 | 'Supplied complex' => array( | |
205 | array('(&(objectClass=cheetah)(enabledMoodleUser=1))'), | |
206 | '(&(objectClass=cheetah)(enabledMoodleUser=1))', | |
207 | ), | |
208 | ); | |
209 | } | |
67bebb69 IA |
210 | |
211 | /** | |
212 | * Tests for ldap_get_entries_moodle. | |
213 | * | |
214 | * NOTE: in order to execute this test you need to set up OpenLDAP server with core, | |
215 | * cosine, nis and internet schemas and add configuration constants to | |
216 | * config.php or phpunit.xml configuration file. The bind users *needs* | |
217 | * permissions to create objects in the LDAP server, under the bind domain. | |
218 | * | |
219 | * define('TEST_LDAPLIB_HOST_URL', 'ldap://127.0.0.1'); | |
220 | * define('TEST_LDAPLIB_BIND_DN', 'cn=someuser,dc=example,dc=local'); | |
221 | * define('TEST_LDAPLIB_BIND_PW', 'somepassword'); | |
222 | * define('TEST_LDAPLIB_DOMAIN', 'dc=example,dc=local'); | |
223 | * | |
224 | */ | |
225 | public function test_ldap_get_entries_moodle() { | |
226 | $this->resetAfterTest(); | |
227 | ||
228 | if (!defined('TEST_LDAPLIB_HOST_URL') or !defined('TEST_LDAPLIB_BIND_DN') or | |
229 | !defined('TEST_LDAPLIB_BIND_PW') or !defined('TEST_LDAPLIB_DOMAIN')) { | |
230 | $this->markTestSkipped('External LDAP test server not configured.'); | |
231 | } | |
232 | ||
233 | // Make sure we can connect the server. | |
234 | $debuginfo = ''; | |
235 | if (!$connection = ldap_connect_moodle(TEST_LDAPLIB_HOST_URL, 3, 'rfc2307', TEST_LDAPLIB_BIND_DN, | |
236 | TEST_LDAPLIB_BIND_PW, LDAP_DEREF_NEVER, $debuginfo, false)) { | |
237 | $this->markTestSkipped('Cannot connect to LDAP test server: '.$debuginfo); | |
238 | } | |
239 | ||
240 | // Create new empty test container. | |
241 | if (!($containerdn = $this->create_test_container($connection, 'moodletest'))) { | |
242 | $this->markTestSkipped('Can not create test LDAP container.'); | |
243 | } | |
244 | ||
245 | // Add all the test objects. | |
246 | $testobjects = $this->get_ldap_get_entries_moodle_test_objects(); | |
247 | if (!$this->add_test_objects($connection, $containerdn, $testobjects)) { | |
248 | $this->markTestSkipped('Can not create LDAP test objects.'); | |
249 | } | |
250 | ||
251 | // Now query about them and compare results. | |
252 | foreach ($testobjects as $object) { | |
253 | $dn = $this->get_object_dn($object, $containerdn); | |
254 | $filter = $object['query']['filter']; | |
255 | $attributes = $object['query']['attributes']; | |
256 | ||
257 | $sr = ldap_read($connection, $dn, $filter, $attributes); | |
258 | if (!$sr) { | |
259 | $this->markTestSkipped('Cannot retrieve test objects from LDAP test server.'); | |
260 | } | |
261 | ||
262 | $entries = ldap_get_entries_moodle($connection, $sr); | |
263 | $actual = array_keys($entries[0]); | |
264 | $expected = $object['expected']; | |
265 | ||
266 | // We need to sort both arrays to be able to compare them, as the LDAP server | |
267 | // might return attributes in any order. | |
268 | sort($expected); | |
269 | sort($actual); | |
270 | $this->assertEquals($expected, $actual); | |
271 | } | |
272 | ||
273 | // Clean up test objects and container. | |
274 | $this->remove_test_objects($connection, $containerdn, $testobjects); | |
275 | $this->remove_test_container($connection, $containerdn); | |
276 | } | |
277 | ||
278 | /** | |
279 | * Provide the array of test objects for the ldap_get_entries_moodle test case. | |
280 | * | |
281 | * @return array of test objects | |
282 | */ | |
283 | protected function get_ldap_get_entries_moodle_test_objects() { | |
284 | $testobjects = array( | |
285 | // Test object 1. | |
286 | array( | |
287 | // Add/remove this object to LDAP directory? There are existing standard LDAP | |
288 | // objects that we might want to test, but that we shouldn't add/remove ourselves. | |
289 | 'addremove' => true, | |
290 | // Relative (to test container) or absolute distinguished name (DN). | |
291 | 'relativedn' => true, | |
292 | // Distinguished name for this object (interpretation depends on 'relativedn'). | |
293 | 'dn' => 'cn=test1', | |
294 | // Values to add to LDAP directory. | |
295 | 'values' => array( | |
296 | 'objectClass' => array('inetOrgPerson', 'organizationalPerson', 'person', 'posixAccount'), | |
297 | 'cn' => 'test1', // We don't care about the actual values, as long as they are unique. | |
298 | 'sn' => 'test1', | |
299 | 'givenName' => 'test1', | |
300 | 'uid' => 'test1', | |
301 | 'uidNumber' => '20001', // Start from 20000, then add test number. | |
302 | 'gidNumber' => '20001', // Start from 20000, then add test number. | |
303 | 'homeDirectory' => '/', | |
304 | 'userPassword' => '*', | |
305 | ), | |
306 | // Attributes to query the object for. | |
307 | 'query' => array( | |
308 | 'filter' => '(objectClass=posixAccount)', | |
309 | 'attributes' => array( | |
310 | 'cn', | |
311 | 'sn', | |
312 | 'givenName', | |
313 | 'uid', | |
314 | 'uidNumber', | |
315 | 'gidNumber', | |
316 | 'homeDirectory', | |
317 | 'userPassword' | |
318 | ), | |
319 | ), | |
320 | // Expected values for the queried attributes' names. | |
321 | 'expected' => array( | |
322 | 'cn', | |
323 | 'sn', | |
324 | 'givenname', | |
325 | 'uid', | |
326 | 'uidnumber', | |
327 | 'gidnumber', | |
328 | 'homedirectory', | |
329 | 'userpassword' | |
330 | ), | |
331 | ), | |
332 | // Test object 2. | |
333 | array( | |
334 | 'addremove' => true, | |
335 | 'relativedn' => true, | |
336 | 'dn' => 'cn=group2', | |
337 | 'values' => array( | |
338 | 'objectClass' => array('top', 'posixGroup'), | |
339 | 'cn' => 'group2', // We don't care about the actual values, as long as they are unique. | |
340 | 'gidNumber' => '20002', // Start from 20000, then add test number. | |
341 | 'memberUid' => '20002', // Start from 20000, then add test number. | |
342 | ), | |
343 | 'query' => array( | |
344 | 'filter' => '(objectClass=posixGroup)', | |
345 | 'attributes' => array( | |
346 | 'cn', | |
347 | 'gidNumber', | |
348 | 'memberUid' | |
349 | ), | |
350 | ), | |
351 | 'expected' => array( | |
352 | 'cn', | |
353 | 'gidnumber', | |
354 | 'memberuid' | |
355 | ), | |
356 | ), | |
357 | // Test object 3. | |
358 | array( | |
359 | 'addremove' => false, | |
360 | 'relativedn' => false, | |
361 | 'dn' => '', // To query the RootDSE, we must specify the empty string as the absolute DN. | |
362 | 'values' => array( | |
363 | ), | |
364 | 'query' => array( | |
365 | 'filter' => '(objectClass=*)', | |
366 | 'attributes' => array( | |
367 | 'supportedControl', | |
368 | 'namingContexts' | |
369 | ), | |
370 | ), | |
371 | 'expected' => array( | |
372 | 'supportedcontrol', | |
373 | 'namingcontexts' | |
374 | ), | |
375 | ), | |
376 | ); | |
377 | ||
378 | return $testobjects; | |
379 | } | |
380 | ||
381 | /** | |
382 | * Create a new container in the LDAP domain, to hold the test objects. The | |
383 | * container is created as a domain component (dc) + organizational unit (ou) object. | |
384 | * | |
385 | * @param object $connection Valid LDAP connection | |
386 | * @param string $container Name of the test container to create. | |
387 | * | |
388 | * @return string or false Distinguished name for the created container, or false on error. | |
389 | */ | |
390 | protected function create_test_container($connection, $container) { | |
391 | $object = array(); | |
392 | $object['objectClass'] = array('dcObject', 'organizationalUnit'); | |
393 | $object['dc'] = $container; | |
394 | $object['ou'] = $container; | |
395 | $containerdn = 'dc='.$container.','.TEST_LDAPLIB_DOMAIN; | |
396 | if (!ldap_add($connection, $containerdn, $object)) { | |
397 | return false; | |
398 | } | |
399 | return $containerdn; | |
400 | } | |
401 | ||
402 | /** | |
403 | * Remove the container in the LDAP domain root that holds the test objects. The container | |
404 | * *must* be empty before trying to remove it. Otherwise this function fails. | |
405 | * | |
406 | * @param object $connection Valid LDAP connection | |
407 | * @param string $containerdn The distinguished of the container to remove. | |
408 | */ | |
409 | protected function remove_test_container($connection, $containerdn) { | |
410 | ldap_delete($connection, $containerdn); | |
411 | } | |
412 | ||
413 | /** | |
414 | * Add the test objects to the test container. | |
415 | * | |
416 | * @param resource $connection Valid LDAP connection | |
417 | * @param string $containerdn The distinguished name of the container for the created objects. | |
418 | * @param array $testobjects Array of the tests objects to create. The structure of | |
419 | * the array elements *must* follow the structure of the value returned | |
420 | * by ldap_get_entries_moodle_test_objects() member function. | |
421 | * | |
422 | * @return boolean True on success, false otherwise. | |
423 | */ | |
424 | protected function add_test_objects($connection, $containerdn, $testobjects) { | |
425 | foreach ($testobjects as $object) { | |
426 | if ($object['addremove'] !== true) { | |
427 | continue; | |
428 | } | |
429 | $dn = $this->get_object_dn($object, $containerdn); | |
430 | $entry = $object['values']; | |
431 | if (!ldap_add($connection, $dn, $entry)) { | |
432 | return false; | |
433 | } | |
434 | } | |
435 | return true; | |
436 | } | |
437 | ||
438 | /** | |
439 | * Remove the test objects from the test container. | |
440 | * | |
441 | * @param resource $connection Valid LDAP connection | |
442 | * @param string $containerdn The distinguished name of the container for the objects to remove. | |
443 | * @param array $testobjects Array of the tests objects to create. The structure of | |
444 | * the array elements *must* follow the structure of the value returned | |
445 | * by ldap_get_entries_moodle_test_objects() member function. | |
446 | * | |
447 | */ | |
448 | protected function remove_test_objects($connection, $containerdn, $testobjects) { | |
449 | foreach ($testobjects as $object) { | |
450 | if ($object['addremove'] !== true) { | |
451 | continue; | |
452 | } | |
453 | $dn = $this->get_object_dn($object, $containerdn); | |
454 | ldap_delete($connection, $dn); | |
455 | } | |
456 | } | |
457 | ||
458 | /** | |
459 | * Get the distinguished name (DN) for a given object. | |
460 | * | |
461 | * @param object $object The LDAP object to calculate the DN for. | |
462 | * @param string $containerdn The DN of the container to use for objects with relative DNs. | |
463 | * | |
464 | * @return string The calculated DN. | |
465 | */ | |
466 | protected function get_object_dn($object, $containerdn) { | |
467 | if ($object['relativedn']) { | |
468 | $dn = $object['dn'].','.$containerdn; | |
469 | } else { | |
470 | $dn = $object['dn']; | |
471 | } | |
472 | return $dn; | |
473 | } | |
cc220325 | 474 | } |