Merge branch 'MDL-58454-master' of git://github.com/junpataleta/moodle
[moodle.git] / lib / tests / ldaplib_test.php
blob57909b9e27bbb2166e3ca5900bb13428c8a2b662
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.
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/>.
17 /**
18 * ldap tests.
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
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
29 require_once($CFG->libdir . '/ldaplib.php');
31 class core_ldaplib_testcase extends advanced_testcase {
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.
37 $tests = array(
38 array (
39 'test' => 'Simplest',
40 'expected' => 'Simplest',
42 array (
43 'test' => 'Simple case',
44 'expected' => 'Simple\\20case',
46 array (
47 'test' => 'Medium ‒ case',
48 'expected' => 'Medium\\20‒\\20case',
50 array (
51 'test' => '#Harder+case#',
52 'expected' => '\\23Harder\\2bcase\\23',
54 array (
55 'test' => ' Harder (and); harder case ',
56 'expected' => '\\20Harder\\20(and)\\3b\\20harder\\20case\\20',
58 array (
59 'test' => 'Really \\0 (hard) case!\\',
60 'expected' => 'Really\\20\\5c0\\20(hard)\\20case!\\5c',
62 array (
63 'test' => 'James "Jim" = Smith, III',
64 'expected' => 'James\\20\\22Jim\22\\20\\3d\\20Smith\\2c\\20III',
66 array (
67 'test' => ' <jsmith@example.com> ',
68 'expected' => '\\20\\20\\3cjsmith@example.com\\3e\\20',
73 foreach ($tests as $test) {
74 $this->assertSame($test['expected'], ldap_addslashes($test['test']));
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.
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.
90 $tests = array(
91 array (
92 'test' => 'Simplest',
93 'expected' => 'Simplest',
95 array (
96 'test' => 'Simple\\20case',
97 'expected' => 'Simple case',
99 array (
100 'test' => 'Simple\\ case',
101 'expected' => 'Simple case',
103 array (
104 'test' => 'Simple\\ \\63\\61\\73\\65',
105 'expected' => 'Simple case',
107 array (
108 'test' => 'Medium\\ \\ case',
109 'expected' => 'Medium ‒ case',
111 array (
112 'test' => 'Medium\\20‒\\20case',
113 'expected' => 'Medium ‒ case',
115 array (
116 'test' => 'Medium\\20\\E2\\80\\92\\20case',
117 'expected' => 'Medium ‒ case',
119 array (
120 'test' => '\\23Harder\\2bcase\\23',
121 'expected' => '#Harder+case#',
123 array (
124 'test' => '\\#Harder\\+case\\#',
125 'expected' => '#Harder+case#',
127 array (
128 'test' => '\\20Harder\\20(and)\\3b\\20harder\\20case\\20',
129 'expected' => ' Harder (and); harder case ',
131 array (
132 'test' => '\\ Harder\\ (and)\\;\\ harder\\ case\\ ',
133 'expected' => ' Harder (and); harder case ',
135 array (
136 'test' => 'Really\\20\\5c0\\20(hard)\\20case!\\5c',
137 'expected' => 'Really \\0 (hard) case!\\',
139 array (
140 'test' => 'Really\\ \\\\0\\ (hard)\\ case!\\\\',
141 'expected' => 'Really \\0 (hard) case!\\',
143 array (
144 'test' => 'James\\20\\22Jim\\22\\20\\3d\\20Smith\\2c\\20III',
145 'expected' => 'James "Jim" = Smith, III',
147 array (
148 'test' => 'James\\ \\"Jim\\" \\= Smith\\, III',
149 'expected' => 'James "Jim" = Smith, III',
151 array (
152 'test' => '\\20\\20\\3cjsmith@example.com\\3e\\20',
153 'expected' => ' <jsmith@example.com> ',
155 array (
156 'test' => '\\ \\<jsmith@example.com\\>\\ ',
157 'expected' => ' <jsmith@example.com> ',
159 array (
160 'test' => 'Lu\\C4\\8Di\\C4\\87',
161 'expected' => 'Lučić',
165 foreach ($tests as $test) {
166 $this->assertSame($test['expected'], ldap_stripslashes($test['test']));
171 * Tests for ldap_normalise_objectclass.
173 * @dataProvider ldap_normalise_objectclass_provider
174 * @param array $args Arguments passed to ldap_normalise_objectclass
175 * @param string $expected The expected objectclass filter
177 public function test_ldap_normalise_objectclass($args, $expected) {
178 $this->assertEquals($expected, call_user_func_array('ldap_normalise_objectclass', $args));
182 * Data provider for the test_ldap_normalise_objectclass testcase.
184 * @return array of testcases.
186 public function ldap_normalise_objectclass_provider() {
187 return array(
188 'Empty value' => array(
189 array(null),
190 '(objectClass=*)',
192 'Empty value with different default' => array(
193 array(null, 'lion'),
194 '(objectClass=lion)',
196 'Supplied unwrapped objectClass' => array(
197 array('objectClass=tiger'),
198 '(objectClass=tiger)',
200 'Supplied string value' => array(
201 array('leopard'),
202 '(objectClass=leopard)',
204 'Supplied complex' => array(
205 array('(&(objectClass=cheetah)(enabledMoodleUser=1))'),
206 '(&(objectClass=cheetah)(enabledMoodleUser=1))',
212 * Tests for ldap_get_entries_moodle.
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.
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');
225 public function test_ldap_get_entries_moodle() {
226 $this->resetAfterTest();
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.');
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);
240 // Create new empty test container.
241 if (!($containerdn = $this->create_test_container($connection, 'moodletest'))) {
242 $this->markTestSkipped('Can not create test LDAP container.');
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.');
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'];
257 $sr = ldap_read($connection, $dn, $filter, $attributes);
258 if (!$sr) {
259 $this->markTestSkipped('Cannot retrieve test objects from LDAP test server.');
262 $entries = ldap_get_entries_moodle($connection, $sr);
263 $actual = array_keys($entries[0]);
264 $expected = $object['expected'];
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);
273 // Clean up test objects and container.
274 $this->remove_test_objects($connection, $containerdn, $testobjects);
275 $this->remove_test_container($connection, $containerdn);
279 * Provide the array of test objects for the ldap_get_entries_moodle test case.
281 * @return array of test objects
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' => '*',
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'
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'
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.
343 'query' => array(
344 'filter' => '(objectClass=posixGroup)',
345 'attributes' => array(
346 'cn',
347 'gidNumber',
348 'memberUid'
351 'expected' => array(
352 'cn',
353 'gidnumber',
354 'memberuid'
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(
364 'query' => array(
365 'filter' => '(objectClass=*)',
366 'attributes' => array(
367 'supportedControl',
368 'namingContexts'
371 'expected' => array(
372 'supportedcontrol',
373 'namingcontexts'
378 return $testobjects;
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.
385 * @param object $connection Valid LDAP connection
386 * @param string $container Name of the test container to create.
388 * @return string or false Distinguished name for the created container, or false on error.
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;
399 return $containerdn;
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.
406 * @param object $connection Valid LDAP connection
407 * @param string $containerdn The distinguished of the container to remove.
409 protected function remove_test_container($connection, $containerdn) {
410 ldap_delete($connection, $containerdn);
414 * Add the test objects to the test container.
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.
422 * @return boolean True on success, false otherwise.
424 protected function add_test_objects($connection, $containerdn, $testobjects) {
425 foreach ($testobjects as $object) {
426 if ($object['addremove'] !== true) {
427 continue;
429 $dn = $this->get_object_dn($object, $containerdn);
430 $entry = $object['values'];
431 if (!ldap_add($connection, $dn, $entry)) {
432 return false;
435 return true;
439 * Remove the test objects from the test container.
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.
448 protected function remove_test_objects($connection, $containerdn, $testobjects) {
449 foreach ($testobjects as $object) {
450 if ($object['addremove'] !== true) {
451 continue;
453 $dn = $this->get_object_dn($object, $containerdn);
454 ldap_delete($connection, $dn);
459 * Get the distinguished name (DN) for a given object.
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.
464 * @return string The calculated DN.
466 protected function get_object_dn($object, $containerdn) {
467 if ($object['relativedn']) {
468 $dn = $object['dn'].','.$containerdn;
469 } else {
470 $dn = $object['dn'];
472 return $dn;