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