diff --git a/.travis.yml b/.travis.yml index 3be694e..6a74bdb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,8 +36,16 @@ matrix: - php: 7.2 - php: nightly +before_install: + - mkdir /tmp/slapd + - slapd -f .travis/ldap/conf/slapd.conf -h ldap://localhost:3389 & + # This sleep is required to ensure the slapd process is running before loading the data in + - sleep 3 + - ldapadd -h localhost:3389 -D cn=admin,dc=joomla,dc=org -w joomla -f .travis/ldap/data/base.ldif + - ldapadd -h localhost:3389 -D cn=admin,dc=joomla,dc=org -w joomla -f .travis/ldap/data/fixtures.ldif + before_script: - # The Trusty build is compiled with LDAP support + # The Trusty build is compiled with LDAP support - if [[ $TRAVIS_PHP_VERSION = 5.3 ]]; then phpenv config-add .travis/phpenv/ldap.ini; fi - composer self-update - composer update $COMPOSER_FLAGS diff --git a/.travis/ldap/conf/slapd.conf b/.travis/ldap/conf/slapd.conf new file mode 100644 index 0000000..4b31a69 --- /dev/null +++ b/.travis/ldap/conf/slapd.conf @@ -0,0 +1,17 @@ +# See slapd.conf(5) for details on configuration options. +include /etc/ldap/schema/core.schema +include /etc/ldap/schema/cosine.schema +include /etc/ldap/schema/inetorgperson.schema +include /etc/ldap/schema/nis.schema + +pidfile /tmp/slapd/slapd.pid +argsfile /tmp/slapd/slapd.args + +modulepath /usr/lib/openldap + +database ldif +directory /tmp/slapd + +suffix "dc=joomla,dc=org" +rootdn "cn=admin,dc=joomla,dc=org" +rootpw {SSHA}kmwklAp1MLJT2ULJC3s9Ry3vo7XUrqj4 diff --git a/.travis/ldap/data/base.ldif b/.travis/ldap/data/base.ldif new file mode 100644 index 0000000..021d094 --- /dev/null +++ b/.travis/ldap/data/base.ldif @@ -0,0 +1,4 @@ +dn: dc=joomla,dc=org +objectClass: dcObject +objectClass: organizationalUnit +ou: Organization diff --git a/.travis/ldap/data/fixtures.ldif b/.travis/ldap/data/fixtures.ldif new file mode 100644 index 0000000..f06e4f5 --- /dev/null +++ b/.travis/ldap/data/fixtures.ldif @@ -0,0 +1,24 @@ +dn: cn=Michael Babker,dc=joomla,dc=org +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Michael Babker +sn: mbabker +mail: michael.babker@joomla.org +ou: People +ou: Maintainers +givenName: Michael Babker +description: Framework Maintainer, CMS Release Lead, Production Department Coordinator + +dn: ou=Components,dc=joomla,dc=org +objectclass: organizationalunit +ou: Components + +dn: ou=Ldap,ou=Components,dc=joomla,dc=org +objectclass: organizationalunit +ou: Ldap + +dn: ou=Ldap scoping,ou=Ldap,ou=Components,dc=joomla,dc=org +objectclass: organizationalunit +ou: Ldap scoping diff --git a/Tests/LdapClientTest.php b/Tests/LdapClientTest.php index 2d17287..d1f966b 100644 --- a/Tests/LdapClientTest.php +++ b/Tests/LdapClientTest.php @@ -7,17 +7,16 @@ namespace Joomla\Ldap\Tests; use Joomla\Ldap\LdapClient; +use Joomla\Registry\Registry; use PHPUnit\Framework\TestCase; /** - * Test class for LdapClient. - * - * @since 1.0 + * Test class for Joomla\Ldap\LdapClient. */ class LdapClientTest extends TestCase { /** - * @var LdapClient + * @var LdapClient */ protected $object; @@ -25,234 +24,516 @@ class LdapClientTest extends TestCase * Sets up the fixture, for example, opens a network connection. * This method is called before a test is executed. * - * @return void + * @return void */ protected function setUp() { parent::setUp(); - $this->object = new LdapClient; + $v3 = getenv('LDAP_V3'); + + $data = array( + 'host' => getenv('LDAP_HOST') ?: '127.0.0.1', + 'port' => getenv('LDAP_PORT') ?: '3389', + 'use_ldapV3' => $v3 === false ? true : (bool) $v3, + ); + + $this->object = new LdapClient(new Registry($data)); } /** - * Test... - * - * @todo Implement testConnect(). + * Tears down the fixture, for example, close a network connection. + * This method is called after a test is executed. * - * @return void + * @return void */ - public function testConnect() + protected function tearDown() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + unset($this->object); + + parent::tearDown(); } /** - * Test... - * - * @todo Implement testClose(). - * - * @return void + * @covers Joomla\Ldap\Ldap::connect */ - public function testClose() + public function testTheConnectionIsOpened() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + $this->assertTrue($this->object->connect()); } /** - * Test... - * - * @todo Implement testSetDn(). + * @testdox The DN is set when there is no user DN * - * @return void + * @covers Joomla\Ldap\Ldap::setDn + * @uses Joomla\Ldap\Ldap::getDn */ - public function testSetDn() + public function testTheDnIsSetWhenThereIsNoUserDn() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + $dn = 'cn=admin,dc=joomla,dc=org'; + + $this->object->setDn($dn); + + $this->assertSame($dn, $this->object->getDn()); } /** - * Test... - * - * @todo Implement testGetDn(). + * @testdox The DN is set when there is a user DN * - * @return void + * @covers Joomla\Ldap\Ldap::setDn + * @uses Joomla\Ldap\Ldap::getDn */ - public function testGetDn() + public function testTheDnIsSetWhenThereIsAUserDn() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + $this->object->users_dn = 'cn=[username],dc=joomla,dc=org'; + + $this->object->setDn('admin'); + + $this->assertSame('cn=admin,dc=joomla,dc=org', $this->object->getDn()); } /** - * Test... - * - * @todo Implement testAnonymous_bind(). + * @testdox The DN is retrieved * - * @return void + * @covers Joomla\Ldap\Ldap::getDn */ - public function testAnonymous_bind() + public function testTheDnIsRetrieved() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + $this->assertNull($this->object->getDn()); } /** - * Test... - * - * @todo Implement testBind(). + * @testdox The connection is bound to the LDAP server anonymously * - * @return void + * @covers Joomla\Ldap\Ldap::anonymous_bind */ - public function testBind() + public function testAnonymousBinding() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + if (!$this->object->connect()) + { + $this->markTestSkipped('Could not connect to LDAP server'); + } + + $this->assertTrue($this->object->anonymous_bind(), 'LDAP connection failed: ' . $this->object->getErrorMsg()); } /** - * Test... + * @testdox The connection is bound to the LDAP server * - * @todo Implement testSimple_search(). - * - * @return void + * @covers Joomla\Ldap\Ldap::bind + * @uses Joomla\Ldap\Ldap::connect + * @uses Joomla\Ldap\Ldap::setDn */ - public function testSimple_search() + public function testBinding() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + if (!$this->object->connect()) + { + $this->markTestSkipped('Could not connect to LDAP server'); + } + + $this->object->users_dn = 'cn=[username],dc=joomla,dc=org'; + + $this->assertTrue($this->object->bind('admin', 'joomla'), 'LDAP connection failed: ' . $this->object->getErrorMsg()); } /** - * Test... + * @testdox A simple search is performed * - * @todo Implement testSearch(). + * @covers Joomla\Ldap\Ldap::simple_search + * @uses Joomla\Ldap\Ldap::bind + * @uses Joomla\Ldap\Ldap::connect + * @uses Joomla\Ldap\Ldap::search + * @uses Joomla\Ldap\Ldap::setDn + */ + public function testSimpleSearch() + { + if (!$this->object->connect()) + { + $this->markTestSkipped('Could not connect to LDAP server'); + } + + $this->object->base_dn = 'dc=joomla,dc=org'; + $this->object->users_dn = 'cn=[username],dc=joomla,dc=org'; + + if (!$this->object->bind('admin', 'joomla')) + { + $this->markTestSkipped('Could not bind to LDAP server'); + } + + $this->assertCount(1, $this->object->simple_search('objectclass=person'), 'The search did not return the expected number of results'); + } + + /** + * @testdox A search is performed without an override of the base DN * - * @return void + * @covers Joomla\Ldap\Ldap::search + * @uses Joomla\Ldap\Ldap::bind + * @uses Joomla\Ldap\Ldap::connect + * @uses Joomla\Ldap\Ldap::setDn */ - public function testSearch() + public function testSearchWithoutDnOverride() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + if (!$this->object->connect()) + { + $this->markTestSkipped('Could not connect to LDAP server'); + } + + $this->object->base_dn = 'dc=joomla,dc=org'; + $this->object->users_dn = 'cn=[username],dc=joomla,dc=org'; + + if (!$this->object->bind('admin', 'joomla')) + { + $this->markTestSkipped('Could not bind to LDAP server'); + } + + $this->assertCount(1, $this->object->search(array('(objectclass=person)')), 'The search did not return the expected number of results'); } /** - * Test... + * @testdox A search is performed with an override of the base DN * - * @todo Implement testReplace(). + * @covers Joomla\Ldap\Ldap::search + * @uses Joomla\Ldap\Ldap::bind + * @uses Joomla\Ldap\Ldap::connect + * @uses Joomla\Ldap\Ldap::setDn + */ + public function testSearchWithDnOverride() + { + if (!$this->object->connect()) + { + $this->markTestSkipped('Could not connect to LDAP server'); + } + + $this->object->users_dn = 'cn=[username],dc=joomla,dc=org'; + + if (!$this->object->bind('admin', 'joomla')) + { + $this->markTestSkipped('Could not bind to LDAP server'); + } + + $this->assertCount(1, $this->object->search(array('(objectclass=person)'), 'dc=joomla,dc=org'), 'The search did not return the expected number of results'); + } + + /** + * @testdox An attribute is replaced for the given user DN * - * @return void + * @covers Joomla\Ldap\Ldap::replace + * @uses Joomla\Ldap\Ldap::bind + * @uses Joomla\Ldap\Ldap::connect + * @uses Joomla\Ldap\Ldap::setDn */ public function testReplace() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + if (!$this->object->connect()) + { + $this->markTestSkipped('Could not connect to LDAP server'); + } + + $this->object->base_dn = 'dc=joomla,dc=org'; + $this->object->users_dn = 'cn=[username],dc=joomla,dc=org'; + + if (!$this->object->bind('admin', 'joomla')) + { + $this->markTestSkipped('Could not bind to LDAP server'); + } + + $this->assertTrue($this->object->replace('cn=Michael Babker,dc=joomla,dc=org', array('mail' => 'michael@joomla.org')), 'The attribute was not replaced'); + + // Reset + $this->object->replace('cn=Michael Babker,dc=joomla,dc=org', array('mail' => 'michael.babker@joomla.org')); } /** - * Test... - * - * @todo Implement testModify(). + * @testdox An attribute is modified for the given user DN * - * @return void + * @covers Joomla\Ldap\Ldap::modify + * @uses Joomla\Ldap\Ldap::bind + * @uses Joomla\Ldap\Ldap::connect + * @uses Joomla\Ldap\Ldap::setDn */ public function testModify() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + if (!$this->object->connect()) + { + $this->markTestSkipped('Could not connect to LDAP server'); + } + + $this->object->base_dn = 'dc=joomla,dc=org'; + $this->object->users_dn = 'cn=[username],dc=joomla,dc=org'; + + if (!$this->object->bind('admin', 'joomla')) + { + $this->markTestSkipped('Could not bind to LDAP server'); + } + + $this->assertTrue($this->object->modify('cn=Michael Babker,dc=joomla,dc=org', array('mail' => 'michael@joomla.org')), 'The attribute was not modified'); + + // Reset + $this->object->modify('cn=Michael Babker,dc=joomla,dc=org', array('mail' => 'michael.babker@joomla.org')); } /** - * Test... + * @testdox An attribute is removed from the given user DN * - * @todo Implement testRemove(). - * - * @return void + * @covers Joomla\Ldap\Ldap::remove + * @uses Joomla\Ldap\Ldap::add + * @uses Joomla\Ldap\Ldap::bind + * @uses Joomla\Ldap\Ldap::connect + * @uses Joomla\Ldap\Ldap::setDn */ public function testRemove() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + if (!$this->object->connect()) + { + $this->markTestSkipped('Could not connect to LDAP server'); + } + + $this->object->base_dn = 'dc=joomla,dc=org'; + $this->object->users_dn = 'cn=[username],dc=joomla,dc=org'; + + if (!$this->object->bind('admin', 'joomla')) + { + $this->markTestSkipped('Could not bind to LDAP server'); + } + + $this->assertTrue($this->object->remove('cn=Michael Babker,dc=joomla,dc=org', array('mail' => 'michael.babker@joomla.org')), 'The attribute was not removed'); + + // Reset + $this->object->add('cn=Michael Babker,dc=joomla,dc=org', array('mail' => 'michael.babker@joomla.org')); } /** - * Test... + * @testdox An attribute is compared for a given value to the user DN * - * @todo Implement testCompare(). - * - * @return void + * @covers Joomla\Ldap\Ldap::compare + * @uses Joomla\Ldap\Ldap::bind + * @uses Joomla\Ldap\Ldap::connect + * @uses Joomla\Ldap\Ldap::setDn */ public function testCompare() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + if (!$this->object->connect()) + { + $this->markTestSkipped('Could not connect to LDAP server'); + } + + $this->object->base_dn = 'dc=joomla,dc=org'; + $this->object->users_dn = 'cn=[username],dc=joomla,dc=org'; + + if (!$this->object->bind('admin', 'joomla')) + { + $this->markTestSkipped('Could not bind to LDAP server'); + } + + $this->assertTrue($this->object->compare('cn=Michael Babker,dc=joomla,dc=org', 'mail', 'michael.babker@joomla.org'), 'The attribute value is not in the expected state'); } /** - * Test... - * - * @todo Implement testRead(). + * @testdox A DN is read from the server and the attributes returned * - * @return void + * @covers Joomla\Ldap\Ldap::read + * @uses Joomla\Ldap\Ldap::bind + * @uses Joomla\Ldap\Ldap::connect + * @uses Joomla\Ldap\Ldap::setDn */ public function testRead() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + if (!$this->object->connect()) + { + $this->markTestSkipped('Could not connect to LDAP server'); + } + + $this->object->base_dn = 'dc=joomla,dc=org'; + $this->object->users_dn = 'cn=[username],dc=joomla,dc=org'; + + if (!$this->object->bind('admin', 'joomla')) + { + $this->markTestSkipped('Could not bind to LDAP server'); + } + + $result = $this->object->read('(objectclass=person),cn=Michael Babker,dc=joomla,dc=org'); + + $this->assertSame(1, $result['count'], 'The expected number of entries were not read'); } /** - * Test... - * - * @todo Implement testDelete(). + * @testdox An entry is removed from the server based on the given DN * - * @return void + * @covers Joomla\Ldap\Ldap::delete + * @uses Joomla\Ldap\Ldap::bind + * @uses Joomla\Ldap\Ldap::connect + * @uses Joomla\Ldap\Ldap::create + * @uses Joomla\Ldap\Ldap::setDn */ public function testDelete() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + if (!$this->object->connect()) + { + $this->markTestSkipped('Could not connect to LDAP server'); + } + + $this->object->base_dn = 'dc=joomla,dc=org'; + $this->object->users_dn = 'cn=[username],dc=joomla,dc=org'; + + if (!$this->object->bind('admin', 'joomla')) + { + $this->markTestSkipped('Could not bind to LDAP server'); + } + + $this->assertTrue($this->object->delete('cn=Michael Babker,dc=joomla,dc=org'), 'The entry was not deleted'); + + // Reset + $this->object->create( + 'cn=Michael Babker,dc=joomla,dc=org', + array( + 'objectClass' => array( + 'inetOrgPerson', + 'organizationalPerson', + 'person', + 'top', + ), + 'cn' => array( + 'Michael Babker', + ), + 'sn' => array( + 'mbabker', + ), + 'mail' => array( + 'michael.babker@joomla.org', + ), + 'ou' => array( + 'People', + 'Maintainers', + ), + 'givenName' => array( + 'Michael Babker', + ), + 'description' => array( + 'Framework Maintainer, CMS Release Lead, Production Department Coordinator', + ), + ) + ); } /** - * Test... - * - * @todo Implement testCreate(). + * @testdox An entry is created on the server based for the given DN * - * @return void + * @covers Joomla\Ldap\Ldap::create + * @uses Joomla\Ldap\Ldap::bind + * @uses Joomla\Ldap\Ldap::connect + * @uses Joomla\Ldap\Ldap::delete + * @uses Joomla\Ldap\Ldap::setDn */ public function testCreate() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + if (!$this->object->connect()) + { + $this->markTestSkipped('Could not connect to LDAP server'); + } + + $this->object->base_dn = 'dc=joomla,dc=org'; + $this->object->users_dn = 'cn=[username],dc=joomla,dc=org'; + + if (!$this->object->bind('admin', 'joomla')) + { + $this->markTestSkipped('Could not bind to LDAP server'); + } + + $this->assertTrue( + $this->object->create( + 'cn=George Wilson,dc=joomla,dc=org', + array( + 'objectClass' => array( + 'inetOrgPerson', + 'organizationalPerson', + 'person', + 'top', + ), + 'cn' => array( + 'George Wilson', + ), + 'sn' => array( + 'wilsonge', + ), + 'mail' => array( + 'george.wilson@joomla.org', + ), + 'ou' => array( + 'People', + 'Maintainers', + ), + 'givenName' => array( + 'George Wilson', + ), + 'description' => array( + 'Framework Team Lead', + ), + ) + ), + 'The entry was not created' + ); + + // Reset + $this->object->delete('cn=George Wilson,dc=joomla,dc=org'); } /** - * Test... - * - * @todo Implement testAdd(). + * @testdox An attribute is added to the given user DN * - * @return void + * @covers Joomla\Ldap\Ldap::add + * @uses Joomla\Ldap\Ldap::remove + * @uses Joomla\Ldap\Ldap::bind + * @uses Joomla\Ldap\Ldap::connect + * @uses Joomla\Ldap\Ldap::setDn */ public function testAdd() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + if (!$this->object->connect()) + { + $this->markTestSkipped('Could not connect to LDAP server'); + } + + $this->object->base_dn = 'dc=joomla,dc=org'; + $this->object->users_dn = 'cn=[username],dc=joomla,dc=org'; + + if (!$this->object->bind('admin', 'joomla')) + { + $this->markTestSkipped('Could not bind to LDAP server'); + } + + $this->assertTrue($this->object->add('cn=Michael Babker,dc=joomla,dc=org', array('mail' => 'michael.babker@gmail.com')), 'The attribute was not added'); + + // Reset + $this->object->remove('cn=Michael Babker,dc=joomla,dc=org', array('mail' => 'michael.babker@gmail.com')); } /** - * Test... - * - * @todo Implement testRename(). + * @testdox An entry is renamed on the server based for the given DN * - * @return void + * @covers Joomla\Ldap\Ldap::rename + * @uses Joomla\Ldap\Ldap::bind + * @uses Joomla\Ldap\Ldap::connect + * @uses Joomla\Ldap\Ldap::setDn */ public function testRename() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + if (!$this->object->connect()) + { + $this->markTestSkipped('Could not connect to LDAP server'); + } + + $this->object->base_dn = 'dc=joomla,dc=org'; + $this->object->users_dn = 'cn=[username],dc=joomla,dc=org'; + + if (!$this->object->bind('admin', 'joomla')) + { + $this->markTestSkipped('Could not bind to LDAP server'); + } + + $this->assertTrue($this->object->rename('cn=Michael Babker,dc=joomla,dc=org', 'cn=Michael', null, true), 'The entry was not renamed'); + + // Reset + $this->object->rename('cn=Michael', 'cn=Michael Babker,dc=joomla,dc=org', null, true); } /** diff --git a/composer.json b/composer.json index eb76b4c..c54ba12 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "ext-ldap": "*" }, "require-dev": { + "joomla/registry": "^1.4.5|~2.0", "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0", "squizlabs/php_codesniffer": "1.*", "symfony/polyfill-php56": "~1.0" @@ -27,6 +28,7 @@ "Joomla\\Ldap\\Tests\\": "Tests/" } }, + "minimum-stability": "dev", "extra": { "branch-alias": { "dev-master": "1.x-dev" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 2278bfb..2c94dd1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,5 +1,11 @@ + + + + + + Tests diff --git a/src/LdapClient.php b/src/LdapClient.php index 8ac6fcd..437bd31 100644 --- a/src/LdapClient.php +++ b/src/LdapClient.php @@ -1,6 +1,6 @@ close(); + } + + /** + * Connect to an LDAP server + * + * @return boolean * * @since 1.0 */ @@ -139,35 +181,27 @@ public function connect() $this->resource = @ ldap_connect($this->host, $this->port); - if ($this->resource) + if (!$this->resource) { - if ($this->use_ldapV3) - { - if (!@ldap_set_option($this->resource, LDAP_OPT_PROTOCOL_VERSION, 3)) - { - return false; - } - } - - if (!@ldap_set_option($this->resource, LDAP_OPT_REFERRALS, (int) $this->no_referrals)) - { - return false; - } + return false; + } - if ($this->negotiate_tls) - { - if (!@ldap_start_tls($this->resource)) - { - return false; - } - } + if ($this->use_ldapV3 && !@ldap_set_option($this->resource, LDAP_OPT_PROTOCOL_VERSION, 3)) + { + return false; + } - return true; + if (!@ldap_set_option($this->resource, LDAP_OPT_REFERRALS, (int) $this->no_referrals)) + { + return false; } - else + + if ($this->negotiate_tls && !@ldap_start_tls($this->resource)) { return false; } + + return true; } /** @@ -179,7 +213,14 @@ public function connect() */ public function close() { - @ ldap_close($this->resource); + if ($this->isConnected()) + { + $this->unbind(); + + @ldap_close($this->resource); + } + + $this->resource = null; } /** @@ -209,9 +250,9 @@ public function setDn($username, $nosub = 0) } /** - * Get the DN + * Get the configured DN * - * @return string The current dn + * @return string * * @since 1.0 */ @@ -223,15 +264,23 @@ public function getDn() /** * Anonymously binds to LDAP directory * - * @return array + * @return boolean * * @since 1.0 */ public function anonymous_bind() { - $bindResult = @ldap_bind($this->resource); + if (!$this->isConnected()) + { + if (!$this->connect()) + { + return false; + } + } + + $this->isBound = @ldap_bind($this->resource); - return $bindResult; + return $this->isBound; } /** @@ -247,6 +296,14 @@ public function anonymous_bind() */ public function bind($username = null, $password = null, $nosub = 0) { + if (!$this->isConnected()) + { + if (!$this->connect()) + { + return false; + } + } + if (is_null($username)) { $username = $this->username; @@ -258,9 +315,27 @@ public function bind($username = null, $password = null, $nosub = 0) } $this->setDn($username, $nosub); - $bindResult = @ldap_bind($this->resource, $this->getDn(), $password); - return $bindResult; + $this->isBound = @ldap_bind($this->resource, $this->getDn(), $password); + + return $this->isBound; + } + + /** + * Unbinds from the LDAP directory + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + public function unbind() + { + if ($this->isBound && $this->resource && is_resource($this->resource)) + { + return @ldap_unbind($this->resource); + } + + return true; } /** @@ -270,7 +345,7 @@ public function bind($username = null, $password = null, $nosub = 0) * * @return array Search results * - * @since 1.0 + * @since 1.0 */ public function simple_search($search) { @@ -299,6 +374,11 @@ public function search(array $filters, $dnoverride = null, array $attributes = a { $result = array(); + if (!$this->isBound || !$this->isConnected()) + { + return $result; + } + if ($dnoverride) { $dn = $dnoverride; @@ -308,13 +388,11 @@ public function search(array $filters, $dnoverride = null, array $attributes = a $dn = $this->base_dn; } - $resource = $this->resource; - foreach ($filters as $search_filter) { - $search_result = @ldap_search($resource, $dn, $search_filter, $attributes); + $search_result = @ldap_search($this->resource, $dn, $search_filter, $attributes); - if ($search_result && ($count = @ldap_count_entries($resource, $search_result)) > 0) + if ($search_result && ($count = @ldap_count_entries($this->resource, $search_result)) > 0) { for ($i = 0; $i < $count; $i++) { @@ -322,15 +400,15 @@ public function search(array $filters, $dnoverride = null, array $attributes = a if (!$i) { - $firstentry = @ldap_first_entry($resource, $search_result); + $firstentry = @ldap_first_entry($this->resource, $search_result); } else { - $firstentry = @ldap_next_entry($resource, $firstentry); + $firstentry = @ldap_next_entry($this->resource, $firstentry); } // Load user-specified attributes - $result_array = @ldap_get_attributes($resource, $firstentry); + $result_array = @ldap_get_attributes($this->resource, $firstentry); // LDAP returns an array of arrays, fit this into attributes result array foreach ($result_array as $ki => $ai) @@ -347,7 +425,7 @@ public function search(array $filters, $dnoverride = null, array $attributes = a } } - $result[$i]['dn'] = @ldap_get_dn($resource, $firstentry); + $result[$i]['dn'] = @ldap_get_dn($this->resource, $firstentry); } } } @@ -356,152 +434,192 @@ public function search(array $filters, $dnoverride = null, array $attributes = a } /** - * Replace an entry and return a true or false result + * Replace attribute values with new ones * * @param string $dn The DN which contains the attribute you want to replace * @param string $attribute The attribute values you want to replace * - * @return mixed result of comparison (true, false, -1 on error) + * @return boolean * * @since 1.0 */ public function replace($dn, $attribute) { + if (!$this->isBound || !$this->isConnected()) + { + return false; + } + return @ldap_mod_replace($this->resource, $dn, $attribute); } /** - * Modifies an entry and return a true or false result + * Modify an LDAP entry * * @param string $dn The DN which contains the attribute you want to modify * @param string $attribute The attribute values you want to modify * - * @return mixed result of comparison (true, false, -1 on error) + * @return boolean * * @since 1.0 */ public function modify($dn, $attribute) { + if (!$this->isBound || !$this->isConnected()) + { + return false; + } + return @ldap_modify($this->resource, $dn, $attribute); } /** - * Removes attribute value from given dn and return a true or false result + * Delete attribute values from current attributes * * @param string $dn The DN which contains the attribute you want to remove * @param string $attribute The attribute values you want to remove * - * @return mixed result of comparison (true, false, -1 on error) + * @return boolean * * @since 1.0 */ public function remove($dn, $attribute) { - $resource = $this->resource; + if (!$this->isBound || !$this->isConnected()) + { + return false; + } - return @ldap_mod_del($resource, $dn, $attribute); + return @ldap_mod_del($this->resource, $dn, $attribute); } /** - * Compare an entry and return a true or false result + * Compare value of attribute found in entry specified with DN * * @param string $dn The DN which contains the attribute you want to compare * @param string $attribute The attribute whose value you want to compare * @param string $value The value you want to check against the LDAP attribute * - * @return mixed result of comparison (true, false, -1 on error) + * @return boolean|integer Boolean result of the comparison or -1 on error * * @since 1.0 */ public function compare($dn, $attribute, $value) { + if (!$this->isBound || !$this->isConnected()) + { + return false; + } + return @ldap_compare($this->resource, $dn, $attribute, $value); } /** - * Read all or specified attributes of given dn + * Read attributes of a given DN * * @param string $dn The DN of the object you want to read * - * @return mixed array of attributes or -1 on error + * @return array|boolean Array of attributes for the given DN or boolean false on failure * * @since 1.0 */ public function read($dn) { + if (!$this->isBound || !$this->isConnected()) + { + return false; + } + $base = substr($dn, strpos($dn, ',') + 1); $cn = substr($dn, 0, strpos($dn, ',')); $result = @ldap_read($this->resource, $base, $cn); - if ($result) + if ($result === false) { - return @ldap_get_entries($this->resource, $result); - } - else - { - return $result; + return false; } + + return @ldap_get_entries($this->resource, $result); } /** - * Deletes a given DN from the tree + * Delete an entry from a directory * * @param string $dn The DN of the object you want to delete * - * @return boolean Result of operation + * @return boolean * * @since 1.0 */ public function delete($dn) { + if (!$this->isBound || !$this->isConnected()) + { + return false; + } + return @ldap_delete($this->resource, $dn); } /** - * Create a new DN + * Add entries to LDAP directory * * @param string $dn The DN where you want to put the object * @param array $entries An array of arrays describing the object to add * - * @return boolean Result of operation + * @return boolean * * @since 1.0 */ public function create($dn, array $entries) { + if (!$this->isBound || !$this->isConnected()) + { + return false; + } + return @ldap_add($this->resource, $dn, $entries); } /** - * Add an attribute to the given DN - * Note: DN has to exist already + * Add attribute values to current attributes * * @param string $dn The DN of the entry to add the attribute * @param array $entry An array of arrays with attributes to add * - * @return boolean Result of operation + * @return boolean * * @since 1.0 */ public function add($dn, array $entry) { + if (!$this->isBound || !$this->isConnected()) + { + return false; + } + return @ldap_mod_add($this->resource, $dn, $entry); } /** - * Rename the entry + * Modify the name of an entry * * @param string $dn The DN of the entry at the moment * @param string $newdn The DN of the entry should be (only cn=newvalue) * @param string $newparent The full DN of the parent (null by default) * @param boolean $deleteolddn Delete the old values (default) * - * @return boolean Result of operation + * @return boolean * * @since 1.0 */ public function rename($dn, $newdn, $newparent, $deleteolddn) { + if (!$this->isBound || !$this->isConnected()) + { + return false; + } + return @ldap_rename($this->resource, $dn, $newdn, $newparent, $deleteolddn); } @@ -522,23 +640,40 @@ public function escape($value, $ignore = '', $flags = 0) } /** - * Returns the error message + * Return the LDAP error message of the last LDAP command * - * @return string error message + * @return string * * @since 1.0 */ public function getErrorMsg() { + if (!$this->isBound || !$this->isConnected()) + { + return ''; + } + return @ldap_error($this->resource); } + /** + * Check if the connection is established + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + public function isConnected() + { + return $this->resource && is_resource($this->resource); + } + /** * Converts a dot notation IP address to net address (e.g. for Netware, etc) * * @param string $ip IP Address (e.g. xxx.xxx.xxx.xxx) * - * @return string Net address + * @return string * * @since 1.0 */ @@ -615,7 +750,9 @@ public static function LdapNetAddr($networkaddress) 'TCP6', 'Reserved (12)', 'URL', - 'Count'); + 'Count' + ); + $len = strlen($networkaddress); if ($len > 0) @@ -652,7 +789,7 @@ public static function LdapNetAddr($networkaddress) * @param string $password Clear text password to encrypt * @param string $type Type of password hash, either md5 or SHA * - * @return string Encrypted password + * @return string * * @since 1.0 */ @@ -661,15 +798,11 @@ public static function generatePassword($password, $type = 'md5') switch (strtolower($type)) { case 'sha': - $userpassword = '{SHA}' . base64_encode(pack('H*', sha1($password))); - break; + return '{SHA}' . base64_encode(pack('H*', sha1($password))); case 'md5': default: - $userpassword = '{MD5}' . base64_encode(pack('H*', md5($password))); - break; + return '{MD5}' . base64_encode(pack('H*', md5($password))); } - - return $userpassword; } }