Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 
11 efrain 34
    public function test_ldap_addslashes(): void {
1 efrain 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
 
11 efrain 79
    public function test_ldap_stripslashes(): void {
1 efrain 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
     */
11 efrain 178
    public function test_ldap_normalise_objectclass($args, $expected): void {
1 efrain 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
     */
11 efrain 226
    public function test_ldap_get_entries_moodle(): void {
1 efrain 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
}