MDL-79059 core: Use full name as alt text for user picture links
[moodle.git] / lib / tests / ldaplib_test.php
blobc0e33b1bd3f45688ba0e24a290fc69d9257c0385
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 namespace core;
19 defined('MOODLE_INTERNAL') || die();
21 global $CFG;
22 require_once($CFG->libdir . '/ldaplib.php');
24 /**
25 * ldap tests.
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
32 class ldaplib_test extends \advanced_testcase {
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.
38 $tests = array(
39 array (
40 'test' => 'Simplest',
41 'expected' => 'Simplest',
43 array (
44 'test' => 'Simple case',
45 'expected' => 'Simple\\20case',
47 array (
48 'test' => 'Medium ‒ case',
49 'expected' => 'Medium\\20‒\\20case',
51 array (
52 'test' => '#Harder+case#',
53 'expected' => '\\23Harder\\2bcase\\23',
55 array (
56 'test' => ' Harder (and); harder case ',
57 'expected' => '\\20Harder\\20(and)\\3b\\20harder\\20case\\20',
59 array (
60 'test' => 'Really \\0 (hard) case!\\',
61 'expected' => 'Really\\20\\5c0\\20(hard)\\20case!\\5c',
63 array (
64 'test' => 'James "Jim" = Smith, III',
65 'expected' => 'James\\20\\22Jim\22\\20\\3d\\20Smith\\2c\\20III',
67 array (
68 'test' => ' <jsmith@example.com> ',
69 'expected' => '\\20\\20\\3cjsmith@example.com\\3e\\20',
74 foreach ($tests as $test) {
75 $this->assertSame($test['expected'], ldap_addslashes($test['test']));
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.
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.
91 $tests = array(
92 array (
93 'test' => 'Simplest',
94 'expected' => 'Simplest',
96 array (
97 'test' => 'Simple\\20case',
98 'expected' => 'Simple case',
100 array (
101 'test' => 'Simple\\ case',
102 'expected' => 'Simple case',
104 array (
105 'test' => 'Simple\\ \\63\\61\\73\\65',
106 'expected' => 'Simple case',
108 array (
109 'test' => 'Medium\\ \\ case',
110 'expected' => 'Medium ‒ case',
112 array (
113 'test' => 'Medium\\20‒\\20case',
114 'expected' => 'Medium ‒ case',
116 array (
117 'test' => 'Medium\\20\\E2\\80\\92\\20case',
118 'expected' => 'Medium ‒ case',
120 array (
121 'test' => '\\23Harder\\2bcase\\23',
122 'expected' => '#Harder+case#',
124 array (
125 'test' => '\\#Harder\\+case\\#',
126 'expected' => '#Harder+case#',
128 array (
129 'test' => '\\20Harder\\20(and)\\3b\\20harder\\20case\\20',
130 'expected' => ' Harder (and); harder case ',
132 array (
133 'test' => '\\ Harder\\ (and)\\;\\ harder\\ case\\ ',
134 'expected' => ' Harder (and); harder case ',
136 array (
137 'test' => 'Really\\20\\5c0\\20(hard)\\20case!\\5c',
138 'expected' => 'Really \\0 (hard) case!\\',
140 array (
141 'test' => 'Really\\ \\\\0\\ (hard)\\ case!\\\\',
142 'expected' => 'Really \\0 (hard) case!\\',
144 array (
145 'test' => 'James\\20\\22Jim\\22\\20\\3d\\20Smith\\2c\\20III',
146 'expected' => 'James "Jim" = Smith, III',
148 array (
149 'test' => 'James\\ \\"Jim\\" \\= Smith\\, III',
150 'expected' => 'James "Jim" = Smith, III',
152 array (
153 'test' => '\\20\\20\\3cjsmith@example.com\\3e\\20',
154 'expected' => ' <jsmith@example.com> ',
156 array (
157 'test' => '\\ \\<jsmith@example.com\\>\\ ',
158 'expected' => ' <jsmith@example.com> ',
160 array (
161 'test' => 'Lu\\C4\\8Di\\C4\\87',
162 'expected' => 'Lučić',
166 foreach ($tests as $test) {
167 $this->assertSame($test['expected'], ldap_stripslashes($test['test']));
172 * Tests for ldap_normalise_objectclass.
174 * @dataProvider ldap_normalise_objectclass_provider
175 * @param array $args Arguments passed to ldap_normalise_objectclass
176 * @param string $expected The expected objectclass filter
178 public function test_ldap_normalise_objectclass($args, $expected) {
179 $this->assertEquals($expected, call_user_func_array('ldap_normalise_objectclass', $args));
183 * Data provider for the test_ldap_normalise_objectclass testcase.
185 * @return array of testcases.
187 public function ldap_normalise_objectclass_provider() {
188 return array(
189 'Empty value' => array(
190 array(null),
191 '(objectClass=*)',
193 'Empty value with different default' => array(
194 array(null, 'lion'),
195 '(objectClass=lion)',
197 'Supplied unwrapped objectClass' => array(
198 array('objectClass=tiger'),
199 '(objectClass=tiger)',
201 'Supplied string value' => array(
202 array('leopard'),
203 '(objectClass=leopard)',
205 'Supplied complex' => array(
206 array('(&(objectClass=cheetah)(enabledMoodleUser=1))'),
207 '(&(objectClass=cheetah)(enabledMoodleUser=1))',
213 * Tests for ldap_get_entries_moodle.
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.
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');
226 public function test_ldap_get_entries_moodle() {
227 $this->resetAfterTest();
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.');
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);
241 // Create new empty test container.
242 if (!($containerdn = $this->create_test_container($connection, 'moodletest'))) {
243 $this->markTestSkipped('Can not create test LDAP container.');
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.');
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'];
258 $sr = ldap_read($connection, $dn, $filter, $attributes);
259 if (!$sr) {
260 $this->markTestSkipped('Cannot retrieve test objects from LDAP test server.');
263 $entries = ldap_get_entries_moodle($connection, $sr);
264 $actual = array_keys($entries[0]);
265 $expected = $object['expected'];
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);
274 // Clean up test objects and container.
275 $this->remove_test_objects($connection, $containerdn, $testobjects);
276 $this->remove_test_container($connection, $containerdn);
280 * Provide the array of test objects for the ldap_get_entries_moodle test case.
282 * @return array of test objects
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' => '*',
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'
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'
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.
344 'query' => array(
345 'filter' => '(objectClass=posixGroup)',
346 'attributes' => array(
347 'cn',
348 'gidNumber',
349 'memberUid'
352 'expected' => array(
353 'cn',
354 'gidnumber',
355 'memberuid'
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(
365 'query' => array(
366 'filter' => '(objectClass=*)',
367 'attributes' => array(
368 'supportedControl',
369 'namingContexts'
372 'expected' => array(
373 'supportedcontrol',
374 'namingcontexts'
379 return $testobjects;
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.
386 * @param object $connection Valid LDAP connection
387 * @param string $container Name of the test container to create.
389 * @return string or false Distinguished name for the created container, or false on error.
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;
400 return $containerdn;
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.
407 * @param object $connection Valid LDAP connection
408 * @param string $containerdn The distinguished of the container to remove.
410 protected function remove_test_container($connection, $containerdn) {
411 ldap_delete($connection, $containerdn);
415 * Add the test objects to the test container.
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.
423 * @return boolean True on success, false otherwise.
425 protected function add_test_objects($connection, $containerdn, $testobjects) {
426 foreach ($testobjects as $object) {
427 if ($object['addremove'] !== true) {
428 continue;
430 $dn = $this->get_object_dn($object, $containerdn);
431 $entry = $object['values'];
432 if (!ldap_add($connection, $dn, $entry)) {
433 return false;
436 return true;
440 * Remove the test objects from the test container.
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.
449 protected function remove_test_objects($connection, $containerdn, $testobjects) {
450 foreach ($testobjects as $object) {
451 if ($object['addremove'] !== true) {
452 continue;
454 $dn = $this->get_object_dn($object, $containerdn);
455 ldap_delete($connection, $dn);
460 * Get the distinguished name (DN) for a given object.
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.
465 * @return string The calculated DN.
467 protected function get_object_dn($object, $containerdn) {
468 if ($object['relativedn']) {
469 $dn = $object['dn'].','.$containerdn;
470 } else {
471 $dn = $object['dn'];
473 return $dn;