1<?php
2
3require_once __DIR__ . '/TestBase.php';
4
5/**
6 * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
7 *
8 * @package    Ldap
9 * @subpackage UnitTests
10 * @author     Jan Schneider <jan@horde.org>
11 * @license    http://www.gnu.org/licenses/lgpl-3.0.html LGPL-3.0
12 */
13class Horde_Ldap_LdapTest extends Horde_Ldap_TestBase
14{
15    public static function tearDownAfterClass()
16    {
17        if (!self::$ldapcfg) {
18            return;
19        }
20
21        $clean = array('cn=Horde_Ldap_TestEntry,',
22                       'ou=Horde_Ldap_Test_subdelete,',
23                       'ou=Horde_Ldap_Test_modify,',
24                       'ou=Horde_Ldap_Test_search1,',
25                       'ou=Horde_Ldap_Test_search2,',
26                       'ou=Horde_Ldap_Test_exists,',
27                       'ou=Horde_Ldap_Test_exists_2+l=somewhere,',
28                       'ou=Horde_Ldap_Test_getEntry,',
29                       'ou=Horde_Ldap_Test_move,',
30                       'ou=Horde_Ldap_Test_pool,',
31                       'ou=Horde_Ldap_Test_tgt,');
32        try {
33            $ldap = new Horde_Ldap(self::$ldapcfg['server']);
34            foreach ($clean as $dn) {
35                try {
36                    $ldap->delete($dn . self::$ldapcfg['server']['basedn'], true);
37                } catch (Exception $e) {
38                }
39            }
40        } catch (Exception $e) {
41        }
42    }
43
44    /**
45     * Tests if the server can connect and bind correctly.
46     */
47    public function testConnectAndPrivilegedBind()
48    {
49        // This connect is supposed to fail.
50        $lcfg = array(
51            'hostspec' => 'nonexistant.ldap.horde.org',
52            'timeout' => 1,
53        );
54        try {
55            $ldap = new Horde_Ldap($lcfg);
56            $this->fail('Horde_Ldap_Exception expected.');
57        } catch (Horde_Ldap_Exception $e) {}
58
59        // Failing with multiple hosts.
60        $lcfg = array(
61            'hostspec' => array(
62                'nonexistant1.ldap.horde.org',
63                'nonexistant2.ldap.horde.org'
64            ),
65            'timeout' => 1,
66        );
67        try {
68            $ldap = new Horde_Ldap($lcfg);
69            $this->fail('Horde_Ldap_Exception expected.');
70        } catch (Horde_Ldap_Exception $e) {}
71
72        // Simple working connect and privileged bind.
73        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
74
75        // Working connect and privileged bind with first host down.
76        $lcfg = array(
77            'hostspec' => array(
78                'nonexistant.ldap.horde.org',
79                self::$ldapcfg['server']['hostspec']
80            ),
81            'port'    => self::$ldapcfg['server']['port'],
82            'binddn'  => self::$ldapcfg['server']['binddn'],
83            'bindpw'  => self::$ldapcfg['server']['bindpw'],
84            'timeout' => 1,
85        );
86        $ldap = new Horde_Ldap($lcfg);
87    }
88
89    /**
90     * Tests if the server can connect and bind anonymously, if supported.
91     */
92    public function testConnectAndAnonymousBind()
93    {
94        if (!self::$ldapcfg['capability']['anonymous']) {
95            $this->markTestSkipped('Server does not support anonymous bind');
96        }
97
98        // Simple working connect and anonymous bind.
99        $lcfg = array('hostspec' => self::$ldapcfg['server']['hostspec'],
100                      'port'     => self::$ldapcfg['server']['port']);
101        $ldap = new Horde_Ldap($lcfg);
102    }
103
104    /**
105     * Tests if the server can connect and bind, but not rebind with empty
106     * password.
107     *
108     * @expectedException Horde_Ldap_Exception
109     */
110    public function testConnectAndEmptyRebind()
111    {
112        // Simple working connect and privileged bind.
113        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
114        $ldap->bind(self::$ldapcfg['server']['binddn'], '');
115    }
116
117    /**
118     * Tests startTLS() if server supports it.
119     */
120    public function testStartTLS()
121    {
122        if (!self::$ldapcfg['capability']['tls']) {
123            $this->markTestSkipped('Server does not support TLS');
124        }
125
126        // Simple working connect and privileged bind.
127        $lcfg = array('starttls' => true) + self::$ldapcfg['server'];
128        $ldap = new Horde_Ldap($lcfg);
129    }
130
131    /**
132     * Test if adding and deleting a fresh entry works.
133     */
134    public function testAdd()
135    {
136        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
137
138        // Adding a fresh entry.
139        $cn = 'Horde_Ldap_TestEntry';
140        $dn = 'cn=' . $cn . ',' . self::$ldapcfg['server']['basedn'];
141        $fresh_entry = Horde_Ldap_Entry::createFresh(
142            $dn,
143            array('objectClass' => array('top', 'person'),
144                  'cn'          => $cn,
145                  'sn'          => 'TestEntry'));
146        $this->assertInstanceOf('Horde_Ldap_Entry', $fresh_entry);
147        $ldap->add($fresh_entry);
148
149        // Deleting this entry.
150        $ldap->delete($fresh_entry);
151    }
152
153    /**
154     * Basic deletion is tested in testAdd(), so here we just test if
155     * advanced deletion tasks work properly.
156     */
157    public function testDelete()
158    {
159        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
160
161        // Some parameter checks.
162        try {
163            $ldap->delete(1234);
164            $this->fail('Horde_Ldap_Exception expected.');
165        } catch (Horde_Ldap_Exception $e) {}
166        try {
167            $ldap->delete($ldap);
168            $this->fail('Horde_Ldap_Exception expected.');
169        } catch (Horde_Ldap_Exception $e) {}
170
171        // In order to test subtree deletion, we need some little tree
172        // which we need to establish first.
173        $base   = self::$ldapcfg['server']['basedn'];
174        $testdn = 'ou=Horde_Ldap_Test_subdelete,' . $base;
175
176        $ou = Horde_Ldap_Entry::createFresh(
177            $testdn,
178            array('objectClass' => array('top', 'organizationalUnit'),
179                  'ou' => 'Horde_Ldap_Test_subdelete'));
180        $ou_1 = Horde_Ldap_Entry::createFresh(
181            'ou=test1,' . $testdn,
182            array('objectClass' => array('top', 'organizationalUnit'),
183                  'ou' => 'test1'));
184        $ou_1_l1 = Horde_Ldap_Entry::createFresh(
185            'l=subtest,ou=test1,' . $testdn,
186            array('objectClass' => array('top', 'locality'),
187                  'l' => 'test1'));
188        $ou_2 = Horde_Ldap_Entry::createFresh(
189            'ou=test2,' . $testdn,
190            array('objectClass' => array('top', 'organizationalUnit'),
191                  'ou' => 'test2'));
192        $ou_3 = Horde_Ldap_Entry::createFresh(
193            'ou=test3,' . $testdn,
194            array('objectClass' => array('top', 'organizationalUnit'),
195                  'ou' => 'test3'));
196        $ldap->add($ou);
197        $ldap->add($ou_1);
198        $ldap->add($ou_1_l1);
199        $ldap->add($ou_2);
200        $ldap->add($ou_3);
201        $this->assertTrue($ldap->exists($ou->dn()));
202        $this->assertTrue($ldap->exists($ou_1->dn()));
203        $this->assertTrue($ldap->exists($ou_1_l1->dn()));
204        $this->assertTrue($ldap->exists($ou_2->dn()));
205        $this->assertTrue($ldap->exists($ou_3->dn()));
206        // Tree established now. We can run some tests now :D
207
208        // Try to delete some non existent entry inside that subtree (fails).
209        try {
210            $ldap->delete('cn=not_existent,ou=test1,' . $testdn);
211            $this->fail('Horde_Ldap_Exception expected.');
212        } catch (Horde_Ldap_Exception $e) {
213            $this->assertEquals('LDAP_NO_SUCH_OBJECT', Horde_Ldap::errorName($e->getCode()));
214        }
215
216        // Try to delete main test ou without recursive set (fails too).
217        try {
218            $ldap->delete($testdn);
219            $this->fail('Horde_Ldap_Exception expected.');
220        } catch (Horde_Ldap_Exception $e) {
221            $this->assertEquals('LDAP_NOT_ALLOWED_ON_NONLEAF', Horde_Ldap::errorName($e->getCode()));
222        }
223
224        // Retry with subtree delete, this should work.
225        $ldap->delete($testdn, true);
226
227        // The DN is not allowed to exist anymore.
228        $this->assertFalse($ldap->exists($testdn));
229    }
230
231    /**
232     * Test modify().
233     */
234    public function testModify()
235    {
236        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
237
238        // We need a test entry.
239        $local_entry = Horde_Ldap_Entry::createFresh(
240            'ou=Horde_Ldap_Test_modify,' . self::$ldapcfg['server']['basedn'],
241            array('objectClass'     => array('top', 'organizationalUnit'),
242                  'ou'              => 'Horde_Ldap_Test_modify',
243                  'street'          => 'Beniroad',
244                  'telephoneNumber' => array('1234', '5678'),
245                  'postalcode'      => '12345',
246                  'postalAddress'   => 'someAddress',
247                  'st'              => array('State 1', 'State 2')));
248        $ldap->add($local_entry);
249        $this->assertTrue($ldap->exists($local_entry->dn()));
250
251        // Test invalid actions.
252        try {
253            $ldap->modify($local_entry, array('foo' => 'bar'));
254            $this->fail('Expected exception when passing invalid actions to modify().');
255        } catch (Horde_Ldap_Exception $e) {
256        }
257
258        // Prepare some changes.
259        $changes = array(
260            'add' => array(
261                'businessCategory' => array('foocat', 'barcat'),
262                'description' => 'testval'
263            ),
264            'delete' => array('postalAddress'),
265            'replace' => array('telephoneNumber' => array('345', '567')),
266            'changes' => array(
267                'replace' => array('street' => 'Highway to Hell'),
268                'add' => array('l' => 'someLocality'),
269                'delete' => array(
270                    'postalcode',
271                    'st' => array('State 1'))));
272
273        // Perform those changes.
274        $ldap->modify($local_entry, $changes);
275
276        // Verify correct attribute changes.
277        $actual_entry = $ldap->getEntry($local_entry->dn(),
278                                        array('objectClass', 'ou',
279                                              'postalAddress', 'street',
280                                              'telephoneNumber', 'postalcode',
281                                              'st', 'l', 'businessCategory',
282                                              'description'));
283        $this->assertInstanceOf('Horde_Ldap_Entry', $actual_entry);
284        $expected_attributes = array(
285            'objectClass'      => array('top', 'organizationalUnit'),
286            'ou'               => 'Horde_Ldap_Test_modify',
287            'street'           => 'Highway to Hell',
288            'l'                => 'someLocality',
289            'telephoneNumber'  => array('345', '567'),
290            'businessCategory' => array('foocat', 'barcat'),
291            'description'      => 'testval',
292            'st'               => 'State 2'
293        );
294
295        $local_attributes  = $local_entry->getValues();
296        $actual_attributes = $actual_entry->getValues();
297
298        // To enable easy check, we need to sort the values of the remaining
299        // multival attributes as well as the attribute names.
300        ksort($expected_attributes);
301        ksort($local_attributes);
302        ksort($actual_attributes);
303        sort($expected_attributes['businessCategory']);
304        sort($local_attributes['businessCategory']);
305        sort($actual_attributes['businessCategory']);
306
307        // The attributes must match the expected values.  Both, the entry
308        // inside the directory and our local copy must reflect the same
309        // values.
310        $this->assertEquals($expected_attributes, $actual_attributes, 'The directory entries attributes are not OK!');
311        $this->assertEquals($expected_attributes, $local_attributes, 'The local entries attributes are not OK!');
312    }
313
314    /**
315     * Test search().
316     */
317    public function testSearch()
318    {
319        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
320
321        // Some testdata, so we can test sizelimit.
322        $base = self::$ldapcfg['server']['basedn'];
323        $ou1 = Horde_Ldap_Entry::createFresh(
324            'ou=Horde_Ldap_Test_search1,' . $base,
325            array('objectClass' => array('top','organizationalUnit'),
326                  'ou' => 'Horde_Ldap_Test_search1'));
327        $ou1_1 = Horde_Ldap_Entry::createFresh(
328            'ou=Horde_Ldap_Test_search1_1,' . $ou1->dn(),
329            array('objectClass' => array('top','organizationalUnit'),
330                  'ou' => 'Horde_Ldap_Test_search1_1'));
331        $ou2 = Horde_Ldap_Entry::createFresh(
332            'ou=Horde_Ldap_Test_search2,' . $base,
333            array('objectClass' => array('top','organizationalUnit'),
334                  'ou' => 'Horde_Ldap_Test_search2'));
335        $ldap->add($ou1);
336        $this->assertTrue($ldap->exists($ou1->dn()));
337        $ldap->add($ou1_1);
338        $this->assertTrue($ldap->exists($ou1_1->dn()));
339        $ldap->add($ou2);
340        $this->assertTrue($ldap->exists($ou2->dn()));
341
342
343        // Search for test filter, should at least return our two test entries.
344        $res = $ldap->search(null, '(ou=Horde_Ldap*)',
345                             array('attributes' => '1.1'));
346        $this->assertInstanceOf('Horde_Ldap_Search', $res);
347        $this->assertThat($res->count(), $this->greaterThanOrEqual(2));
348
349        // Same, but with Horde_Ldap_Filter object.
350        $filtero = Horde_Ldap_Filter::create('ou', 'begins', 'Horde_Ldap');
351        $this->assertInstanceOf('Horde_Ldap_Filter', $filtero);
352        $res = $ldap->search(null, $filtero,
353                             array('attributes' => '1.1'));
354        $this->assertInstanceOf('Horde_Ldap_Search', $res);
355        $this->assertThat($res->count(), $this->greaterThanOrEqual(2));
356
357        // Search using default filter for base-onelevel scope, should at least
358        // return our two test entries.
359        $res = $ldap->search(null, null,
360                             array('scope' => 'one', 'attributes' => '1.1'));
361        $this->assertInstanceOf('Horde_Ldap_Search', $res);
362        $this->assertThat($res->count(), $this->greaterThanOrEqual(2));
363
364        // Base-search using custom base (string), should only return the test
365        // entry $ou1 and not the entry below it.
366        $res = $ldap->search($ou1->dn(), null,
367                             array('scope' => 'base', 'attributes' => '1.1'));
368        $this->assertInstanceOf('Horde_Ldap_Search', $res);
369        $this->assertEquals(1, $res->count());
370
371        // Search using custom base, this time using an entry object.  This
372        // tests if passing an entry object as base works, should only return
373        // the test entry $ou1.
374        $res = $ldap->search($ou1, '(ou=*)',
375                             array('scope' => 'base', 'attributes' => '1.1'));
376        $this->assertInstanceOf('Horde_Ldap_Search', $res);
377        $this->assertEquals(1, $res->count());
378
379        // Search using default filter for base-onelevel scope with sizelimit,
380        // should of course return more than one entry, but not more than
381        // sizelimit
382        $res = $ldap->search(
383            null, null,
384            array('scope' => 'one', 'sizelimit' => 1, 'attributes' => '1.1')
385        );
386        $this->assertInstanceOf('Horde_Ldap_Search', $res);
387        $this->assertEquals(1, $res->count());
388        // Sizelimit should be exceeded now.
389        $this->assertTrue($res->sizeLimitExceeded());
390
391        // Bad filter.
392        try {
393            $res = $ldap->search(null, 'somebadfilter',
394                                 array('attributes' => '1.1'));
395            $this->fail('Horde_Ldap_Exception expected.');
396        } catch (Horde_Ldap_Exception $e) {}
397
398        // Bad base.
399        try {
400            $res = $ldap->search('badbase', null,
401                                 array('attributes' => '1.1'));
402            $this->fail('Horde_Ldap_Exception expected.');
403        } catch (Horde_Ldap_Exception $e) {}
404
405        // Nullresult.
406        $res = $ldap->search(null, '(cn=nevermatching_filter)',
407                             array('scope' => 'base', 'attributes' => '1.1'));
408        $this->assertInstanceOf('Horde_Ldap_Search', $res);
409        $this->assertEquals(0, $res->count());
410    }
411
412    /**
413     * Test exists().
414     */
415    public function testExists()
416    {
417        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
418
419        $dn = 'ou=Horde_Ldap_Test_exists,' . self::$ldapcfg['server']['basedn'];
420
421        // Testing not existing DN.
422        $this->assertFalse($ldap->exists($dn));
423
424        // Passing an entry object (should work). exists() should return false,
425        // because we didn't add the test entry yet.
426        $ou1 = Horde_Ldap_Entry::createFresh(
427            $dn,
428            array('objectClass' => array('top', 'organizationalUnit'))
429        );
430
431        $this->assertFalse($ldap->exists($dn));
432        $this->assertFalse($ldap->exists($ou1));
433
434        // Testing not existing DN.
435        $ldap->add($ou1);
436        $this->assertTrue($ldap->exists($dn));
437
438        // Passing an float instead of a string.
439        try {
440            $ldap->exists(1.234);
441            $this->fail('Horde_Ldap_Exception expected.');
442        } catch (Horde_Ldap_Exception $e) {}
443
444        // Testing multivalued RDNs.
445        $dn = 'ou=Horde_Ldap_Test_exists_2+l=somewhere,' . self::$ldapcfg['server']['basedn'];
446        $ou2 = Horde_Ldap_Entry::createFresh(
447            $dn,
448            array('objectClass' => array('top', 'organizationalUnit'))
449        );
450        $this->assertFalse($ldap->exists($dn));
451        $ldap->add($ou2);
452        $this->assertTrue($ldap->exists($dn));
453    }
454
455    /**
456     * Test getEntry().
457     */
458    public function testGetEntry()
459    {
460        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
461        $dn = 'ou=Horde_Ldap_Test_getEntry,' . self::$ldapcfg['server']['basedn'];
462        $entry = Horde_Ldap_Entry::createFresh(
463            $dn,
464            array('objectClass' => array('top', 'organizationalUnit'),
465                  'ou' => 'Horde_Ldap_Test_getEntry'));
466        $ldap->add($entry);
467
468        // Existing DN.
469        $this->assertInstanceOf('Horde_Ldap_Entry', $ldap->getEntry($dn));
470
471        // Not existing DN.
472        try {
473            $ldap->getEntry('cn=notexistent,' . self::$ldapcfg['server']['basedn']);
474            $this->fail('Horde_Ldap_Exception expected.');
475        } catch (Horde_Exception_NotFound $e) {}
476    }
477
478    /**
479     * Test move().
480     */
481    public function testMove()
482    {
483        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
484
485        // For Moving tests, we need some little tree again.
486        $base   = self::$ldapcfg['server']['basedn'];
487        $testdn = 'ou=Horde_Ldap_Test_move,' . $base;
488
489        $ou = Horde_Ldap_Entry::createFresh(
490            $testdn,
491            array('objectClass' => array('top', 'organizationalUnit'),
492                  'ou' => 'Horde_Ldap_Test_move'));
493        $ou_1 = Horde_Ldap_Entry::createFresh(
494            'ou=source,' . $testdn,
495            array('objectClass' => array('top', 'organizationalUnit'),
496                  'ou' => 'source'));
497        $ou_1_l1 = Horde_Ldap_Entry::createFresh(
498            'l=moveitem,ou=source,' . $testdn,
499            array('objectClass' => array('top','locality'),
500                  'l' => 'moveitem',
501                  'description' => 'movetest'));
502        $ou_2 = Horde_Ldap_Entry::createFresh(
503            'ou=target,' . $testdn,
504            array('objectClass' => array('top', 'organizationalUnit'),
505                  'ou' => 'target'));
506        $ou_3 = Horde_Ldap_Entry::createFresh(
507            'ou=target_otherdir,' . $testdn,
508            array('objectClass' => array('top','organizationalUnit'),
509                  'ou' => 'target_otherdir'));
510        $ldap->add($ou);
511        $ldap->add($ou_1);
512        $ldap->add($ou_1_l1);
513        $ldap->add($ou_2);
514        $ldap->add($ou_3);
515        $this->assertTrue($ldap->exists($ou->dn()));
516        $this->assertTrue($ldap->exists($ou_1->dn()));
517        $this->assertTrue($ldap->exists($ou_1_l1->dn()));
518        $this->assertTrue($ldap->exists($ou_2->dn()));
519        $this->assertTrue($ldap->exists($ou_3->dn()));
520        // Tree established.
521
522        // Local rename.
523        $olddn = $ou_1_l1->currentDN();
524        $ldap->move($ou_1_l1, str_replace('moveitem', 'move_item', $ou_1_l1->dn()));
525        $this->assertTrue($ldap->exists($ou_1_l1->dn()));
526        $this->assertFalse($ldap->exists($olddn));
527
528        // Local move.
529        $olddn = $ou_1_l1->currentDN();
530        $ldap->move($ou_1_l1, 'l=move_item,' . $ou_2->dn());
531        $this->assertTrue($ldap->exists($ou_1_l1->dn()));
532        $this->assertFalse($ldap->exists($olddn));
533
534        // Local move backward, with rename. Here we use the DN of the object,
535        // to test DN conversion.
536        // Note that this will outdate the object since it does not has
537        // knowledge about the move.
538        $olddn = $ou_1_l1->currentDN();
539        $newdn = 'l=moveditem,' . $ou_2->dn();
540        $ldap->move($olddn, $newdn);
541        $this->assertTrue($ldap->exists($newdn));
542        $this->assertFalse($ldap->exists($olddn));
543        // Refetch since the object's DN was outdated.
544        $ou_1_l1 = $ldap->getEntry($newdn);
545
546        // Fake-cross directory move using two separate links to the same
547        // directory. This other directory is represented by
548        // ou=target_otherdir.
549        $ldap2 = new Horde_Ldap(self::$ldapcfg['server']);
550        $olddn = $ou_1_l1->currentDN();
551        $ldap->move($ou_1_l1, 'l=movedcrossdir,' . $ou_3->dn(), $ldap2);
552        $this->assertFalse($ldap->exists($olddn));
553        $this->assertTrue($ldap2->exists($ou_1_l1->dn()));
554
555        // Try to move over an existing entry.
556        try {
557            $ldap->move($ou_2, $ou_3->dn(), $ldap2);
558            $this->fail('Horde_Ldap_Exception expected.');
559        } catch (Horde_Ldap_Exception $e) {}
560
561        // Try cross directory move without providing an valid entry but a DN.
562        try {
563            $ldap->move($ou_1_l1->dn(), 'l=movedcrossdir2,'.$ou_2->dn(), $ldap2);
564            $this->fail('Horde_Ldap_Exception expected.');
565        } catch (Horde_Ldap_Exception $e) {}
566
567        // Try passing an invalid entry object.
568        try {
569            $ldap->move($ldap, 'l=move_item,'.$ou_2->dn());
570            $this->fail('Horde_Ldap_Exception expected.');
571        } catch (Horde_Ldap_Exception $e) {}
572
573        // Try passing an invalid LDAP object.
574        try {
575            $ldap->move($ou_1_l1, 'l=move_item,'.$ou_2->dn(), $ou_1);
576            $this->fail('Horde_Ldap_Exception expected.');
577        } catch (Horde_Ldap_Exception $e) {}
578    }
579
580    /**
581     * Test copy().
582     */
583    public function testCopy()
584    {
585        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
586
587        // Some testdata.
588        $base = self::$ldapcfg['server']['basedn'];
589        $ou1 = Horde_Ldap_Entry::createFresh(
590            'ou=Horde_Ldap_Test_pool,' . $base,
591            array('objectClass' => array('top','organizationalUnit'),
592                  'ou' => 'Horde_Ldap_Test_copy'));
593        $ou2 = Horde_Ldap_Entry::createFresh(
594            'ou=Horde_Ldap_Test_tgt,' . $base,
595            array('objectClass' => array('top','organizationalUnit'),
596                  'ou' => 'Horde_Ldap_Test_copy'));
597        $ldap->add($ou1);
598        $this->assertTrue($ldap->exists($ou1->dn()));
599        $ldap->add($ou2);
600        $this->assertTrue($ldap->exists($ou2->dn()));
601
602        $entry = Horde_Ldap_Entry::createFresh(
603            'l=cptest,' . $ou1->dn(),
604            array('objectClass' => array('top','locality'),
605                  'l' => 'cptest'));
606        $ldap->add($entry);
607        $ldap->exists($entry->dn());
608
609        // Copy over the entry to another tree with rename.
610        $entrycp = $ldap->copy($entry, 'l=test_copied,' . $ou2->dn());
611        $this->assertInstanceOf('Horde_Ldap_Entry', $entrycp);
612        $this->assertNotEquals($entry->dn(), $entrycp->dn());
613        $this->assertTrue($ldap->exists($entrycp->dn()));
614
615        // Copy same again (fails, entry exists).
616        try {
617            $entrycp_f = $ldap->copy($entry, 'l=test_copied,' . $ou2->dn());
618            $this->fail('Horde_Ldap_Exception expected.');
619        } catch (Horde_Ldap_Exception $e) {}
620
621        // Use only DNs to copy (fails).
622        try {
623            $entrycp = $ldap->copy($entry->dn(), 'l=test_copied2,' . $ou2->dn());
624            $this->fail('Horde_Ldap_Exception expected.');
625        } catch (Horde_Ldap_Exception $e) {}
626    }
627
628    /**
629     * Tests retrieval of root DSE object.
630     */
631    public function testRootDSE()
632    {
633        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
634        $this->assertInstanceOf('Horde_Ldap_RootDse', $ldap->rootDSE());
635    }
636
637    /**
638     * Tests retrieval of schema through LDAP object.
639     */
640    public function testSchema()
641    {
642        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
643        $this->assertInstanceOf('Horde_Ldap_Schema', $ldap->schema());
644    }
645
646    /**
647     * Test getLink().
648     */
649    public function testGetLink()
650    {
651        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
652        $this->assertTrue(is_resource($ldap->getLink()));
653    }
654
655    public function testQuoteDN()
656    {
657        $this->assertEquals(
658            'cn=John Smith,dc=example,dc=com',
659            Horde_Ldap::quoteDN(
660                array(
661                    array('cn', 'John Smith'),
662                    array('dc', 'example'),
663                    array('dc', 'com')
664                )
665            )
666        );
667        $this->assertEquals(
668            'cn=John+sn=Smith+o=Acme Inc.,dc=example,dc=com',
669            Horde_Ldap::quoteDN(
670                array(
671                    array(
672                        array('cn', 'John'),
673                        array('sn', 'Smith'),
674                        array('o', 'Acme Inc.'),
675                    ),
676                    array('dc', 'example'),
677                    array('dc', 'com')
678                )
679            )
680        );
681        $this->assertEquals(
682            'cn=John+sn=Smith+o=Acme Inc.',
683            Horde_Ldap::quoteDN(
684                array(
685                    array(
686                        array('cn', 'John'),
687                        array('sn', 'Smith'),
688                        array('o', 'Acme Inc.'),
689                    ),
690                )
691            )
692        );
693    }
694}
695