diff options
author | Frederic Guillot <fred@kanboard.net> | 2015-09-06 14:28:06 -0400 |
---|---|---|
committer | Frederic Guillot <fred@kanboard.net> | 2015-09-06 14:28:06 -0400 |
commit | b0994ba68e7cbaa077d81006fb0f25bcbd049353 (patch) | |
tree | 218b4a1e289f1e6d05ca774486d3d259a4e5dfa7 | |
parent | 3c0b56bc0214d2b0ad561a9001240ae73dea8e64 (diff) |
Add unit tests for LDAP and ReverseProxy auth
-rw-r--r-- | Makefile | 11 | ||||
-rw-r--r-- | app/Auth/Ldap.php | 76 | ||||
-rw-r--r-- | app/Auth/ReverseProxy.php | 1 | ||||
-rw-r--r-- | tests/units/Auth/LdapTest.php | 377 | ||||
-rw-r--r-- | tests/units/Auth/ReverseProxyTest.php | 37 | ||||
-rw-r--r-- | tests/units/Base.php | 3 |
6 files changed, 455 insertions, 50 deletions
@@ -77,4 +77,15 @@ archive: @ cd ${dst} && if [ -L kanboard-latest.zip ]; then unlink kanboard-latest.zip; ln -s kanboard-${version}.zip kanboard-latest.zip; fi @ rm -rf ${BUILD_DIR}/kanboard +test-sqlite: + @ phpunit -c tests/units.sqlite.xml + +test-mysql: + @ phpunit -c tests/units.mysql.xml + +test-postgres: + @ phpunit -c tests/units.postgres.xml + +unittest: test-sqlite test-mysql test-postgres + .PHONY: all diff --git a/app/Auth/Ldap.php b/app/Auth/Ldap.php index 262c5cd7..cc1b3f95 100644 --- a/app/Auth/Ldap.php +++ b/app/Auth/Ldap.php @@ -109,9 +109,11 @@ class Ldap extends Base * LDAP connection * * @access public + * @param string $ldap_hostname + * @param integer $ldap_port * @return resource|boolean */ - public function connect() + public function connect($ldap_hostname = LDAP_SERVER, $ldap_port = LDAP_PORT) { if (! function_exists('ldap_connect')) { $this->logger->error('The PHP LDAP extension is required'); @@ -123,12 +125,13 @@ class Ldap extends Base putenv('LDAPTLS_REQCERT=never'); } - $ldap = ldap_connect(LDAP_SERVER, LDAP_PORT); + $ldap = ldap_connect($ldap_hostname, $ldap_port); if ($ldap === false) { $this->logger->error('Unable to connect to the LDAP server: "'.LDAP_SERVER.'"'); return false; } + ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3); ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0); ldap_set_option($ldap, LDAP_OPT_NETWORK_TIMEOUT, 1); @@ -143,7 +146,7 @@ class Ldap extends Base } /** - * LDAP bind + * LDAP authentication * * @access public * @param resource $ldap @@ -179,33 +182,34 @@ class Ldap extends Base /** * LDAP user lookup * - * @access private - * @param resource $ldap LDAP connection - * @param string $username Username - * @param string $password Password + * @access public + * @param resource $ldap + * @param string $username + * @param string $password + * @param string $base_dn + * @param string $user_pattern + * @param array $attributes * @return boolean|array */ - private function search($ldap, $username, $password) + public function search($ldap, $username, $password, $base_dn = LDAP_ACCOUNT_BASE, $user_pattern = LDAP_USER_PATTERN, array $attributes = array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL)) { - $sr = @ldap_search($ldap, LDAP_ACCOUNT_BASE, sprintf(LDAP_USER_PATTERN, $username), array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL)); + $sr = ldap_search($ldap, $base_dn, sprintf($user_pattern, $username), $attributes); if ($sr === false) { return false; } - $info = ldap_get_entries($ldap, $sr); + $entries = ldap_get_entries($ldap, $sr); - // User not found - if (count($info) === 0 || $info['count'] == 0) { + if ($entries === false || count($entries) === 0 || $entries['count'] == 0) { return false; } - // We got our user - if (@ldap_bind($ldap, $info[0]['dn'], $password)) { + if (@ldap_bind($ldap, $entries[0]['dn'], $password)) { return array( 'username' => $username, - 'name' => $this->getFromInfo($info, LDAP_ACCOUNT_FULLNAME), - 'email' => $this->getFromInfo($info, LDAP_ACCOUNT_EMAIL), + 'name' => $this->getEntry($entries, LDAP_ACCOUNT_FULLNAME), + 'email' => $this->getEntry($entries, LDAP_ACCOUNT_EMAIL), ); } @@ -215,24 +219,26 @@ class Ldap extends Base /** * Retrieve info on LDAP user * - * @param string $username Username - * @param string $email Email address + * @access public + * @param string $username Username + * @param string $email Email address + * @return boolean|array */ public function lookup($username = null, $email = null) { $query = $this->getQuery($username, $email); - if ($query === false) { + if ($query === '') { return false; } // Connect and attempt anonymous bind $ldap = $this->connect(); - if (! is_resource($ldap) || ! $this->bind($ldap, null, null)) { + if ($ldap === false || ! $this->bind($ldap, null, null, 'anonymous')) { return false; } // Try to find user - $sr = @ldap_search($ldap, LDAP_ACCOUNT_BASE, $query, array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL, LDAP_ACCOUNT_ID)); + $sr = ldap_search($ldap, LDAP_ACCOUNT_BASE, $query, array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL, LDAP_ACCOUNT_ID)); if ($sr === false) { return false; } @@ -250,17 +256,19 @@ class Ldap extends Base } return array( - 'username' => $this->getFromInfo($info, LDAP_ACCOUNT_ID, $username), - 'name' => $this->getFromInfo($info, LDAP_ACCOUNT_FULLNAME), - 'email' => $this->getFromInfo($info, LDAP_ACCOUNT_EMAIL, $email), + 'username' => $this->getEntry($info, LDAP_ACCOUNT_ID, $username), + 'name' => $this->getEntry($info, LDAP_ACCOUNT_FULLNAME), + 'email' => $this->getEntry($info, LDAP_ACCOUNT_EMAIL, $email), ); } /** * Get the LDAP query to find a user * - * @param string $username Username - * @param string $email Email address + * @access private + * @param string $username Username + * @param string $email Email address + * @return string */ private function getQuery($username, $email) { @@ -273,21 +281,21 @@ class Ldap extends Base else if ($email) { return '('.LDAP_ACCOUNT_EMAIL.'='.$email.')'; } - else { - return false; - } + + return ''; } /** * Return a value from the LDAP info * - * @param array $info LDAP info - * @param string $key Key - * @param string $default Default value if key not set in entry + * @access private + * @param array $entries LDAP entries + * @param string $key Key + * @param string $default Default value if key not set in entry * @return string */ - private function getFromInfo($info, $key, $default = '') + private function getEntry(array $entries, $key, $default = '') { - return isset($info[0][$key][0]) ? $info[0][$key][0] : $default; + return isset($entries[0][$key][0]) ? $entries[0][$key][0] : $default; } } diff --git a/app/Auth/ReverseProxy.php b/app/Auth/ReverseProxy.php index c8fd5eec..7818254c 100644 --- a/app/Auth/ReverseProxy.php +++ b/app/Auth/ReverseProxy.php @@ -28,7 +28,6 @@ class ReverseProxy extends Base public function authenticate() { if (isset($_SERVER[REVERSE_PROXY_USER_HEADER])) { - $login = $_SERVER[REVERSE_PROXY_USER_HEADER]; $user = $this->user->getByUsername($login); diff --git a/tests/units/Auth/LdapTest.php b/tests/units/Auth/LdapTest.php index 8d470d88..5c453b39 100644 --- a/tests/units/Auth/LdapTest.php +++ b/tests/units/Auth/LdapTest.php @@ -13,25 +13,40 @@ function ldap_set_option() { } -function ldap_bind($ldap, $ldap_username, $ldap_password) +function ldap_bind($link_identifier, $bind_rdn, $bind_password) { - return LdapTest::$functions->ldap_bind($ldap, $ldap_username, $ldap_password); + return LdapTest::$functions->ldap_bind($link_identifier, $bind_rdn, $bind_password); +} + +function ldap_search($link_identifier, $base_dn, $filter, array $attributes) +{ + return LdapTest::$functions->ldap_search($link_identifier, $base_dn, $filter, $attributes); +} + +function ldap_get_entries($link_identifier, $result_identifier) +{ + return LdapTest::$functions->ldap_get_entries($link_identifier, $result_identifier); } class LdapTest extends \Base { public static $functions; + private $ldap; public function setUp() { parent::setup(); + $this->ldap = $ldap = new Ldap($this->container); + self::$functions = $this ->getMockBuilder('stdClass') ->setMethods(array( 'ldap_connect', 'ldap_set_option', 'ldap_bind', + 'ldap_search', + 'ldap_get_entries', )) ->getMock(); } @@ -53,8 +68,7 @@ class LdapTest extends \Base ) ->will($this->returnValue('my_ldap_resource')); - $ldap = new Ldap($this->container); - $this->assertNotFalse($ldap->connect()); + $this->assertNotFalse($this->ldap->connect('my_ldap_server')); } public function testConnectFailure() @@ -68,8 +82,7 @@ class LdapTest extends \Base ) ->will($this->returnValue(false)); - $ldap = new Ldap($this->container); - $this->assertFalse($ldap->connect()); + $this->assertFalse($this->ldap->connect('my_ldap_server')); } public function testBindAnonymous() @@ -84,8 +97,7 @@ class LdapTest extends \Base ) ->will($this->returnValue(true)); - $ldap = new Ldap($this->container); - $this->assertTrue($ldap->bind('my_ldap_connection', 'my_user', 'my_password', 'anonymous')); + $this->assertTrue($this->ldap->bind('my_ldap_connection', 'my_user', 'my_password', 'anonymous')); } public function testBindUser() @@ -100,8 +112,7 @@ class LdapTest extends \Base ) ->will($this->returnValue(true)); - $ldap = new Ldap($this->container); - $this->assertTrue($ldap->bind('my_ldap_connection', 'my_user', 'my_password', 'user', 'uid=%s', 'something')); + $this->assertTrue($this->ldap->bind('my_ldap_connection', 'my_user', 'my_password', 'user', 'uid=%s', 'something')); } public function testBindProxy() @@ -116,7 +127,349 @@ class LdapTest extends \Base ) ->will($this->returnValue(true)); - $ldap = new Ldap($this->container); - $this->assertTrue($ldap->bind('my_ldap_connection', 'my_user', 'my_password', 'proxy', 'someone', 'something')); + $this->assertTrue($this->ldap->bind('my_ldap_connection', 'my_user', 'my_password', 'proxy', 'someone', 'something')); + } + + public function testSearchSuccess() + { + $entries = array( + 'count' => 1, + 0 => array( + 'count' => 2, + 'dn' => 'uid=my_user,ou=People,dc=kanboard,dc=local', + 'displayname' => array( + 'count' => 1, + 0 => 'My user', + ), + 'mail' => array( + 'count' => 2, + 0 => 'user1@localhost', + 1 => 'user2@localhost', + ), + 0 => 'displayname', + 1 => 'mail', + ) + ); + + $expected = array( + 'username' => 'my_user', + 'name' => 'My user', + 'email' => 'user1@localhost', + ); + + self::$functions + ->expects($this->at(0)) + ->method('ldap_search') + ->with( + $this->equalTo('my_ldap_connection'), + $this->equalTo('ou=People,dc=kanboard,dc=local'), + $this->equalTo('uid=my_user'), + $this->equalTo(array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL)) + ) + ->will($this->returnValue('my_result_identifier')); + + self::$functions + ->expects($this->at(1)) + ->method('ldap_get_entries') + ->with( + $this->equalTo('my_ldap_connection'), + $this->equalTo('my_result_identifier') + ) + ->will($this->returnValue($entries)); + + self::$functions + ->expects($this->at(2)) + ->method('ldap_bind') + ->with( + $this->equalTo('my_ldap_connection'), + $this->equalTo('uid=my_user,ou=People,dc=kanboard,dc=local'), + $this->equalTo('my_password') + ) + ->will($this->returnValue(true)); + + $this->assertEquals($expected, $this->ldap->search('my_ldap_connection', 'my_user', 'my_password', 'ou=People,dc=kanboard,dc=local', 'uid=%s')); + } + + public function testSearchWithBadPassword() + { + $entries = array( + 'count' => 1, + 0 => array( + 'count' => 2, + 'dn' => 'uid=my_user,ou=People,dc=kanboard,dc=local', + 'displayname' => array( + 'count' => 1, + 0 => 'My user', + ), + 'mail' => array( + 'count' => 2, + 0 => 'user1@localhost', + 1 => 'user2@localhost', + ), + 0 => 'displayname', + 1 => 'mail', + ) + ); + + self::$functions + ->expects($this->at(0)) + ->method('ldap_search') + ->with( + $this->equalTo('my_ldap_connection'), + $this->equalTo('ou=People,dc=kanboard,dc=local'), + $this->equalTo('uid=my_user'), + $this->equalTo(array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL)) + ) + ->will($this->returnValue('my_result_identifier')); + + self::$functions + ->expects($this->at(1)) + ->method('ldap_get_entries') + ->with( + $this->equalTo('my_ldap_connection'), + $this->equalTo('my_result_identifier') + ) + ->will($this->returnValue($entries)); + + self::$functions + ->expects($this->at(2)) + ->method('ldap_bind') + ->with( + $this->equalTo('my_ldap_connection'), + $this->equalTo('uid=my_user,ou=People,dc=kanboard,dc=local'), + $this->equalTo('my_password') + ) + ->will($this->returnValue(false)); + + $this->assertFalse($this->ldap->search('my_ldap_connection', 'my_user', 'my_password', 'ou=People,dc=kanboard,dc=local', 'uid=%s')); + } + + public function testSearchWithUserNotFound() + { + self::$functions + ->expects($this->at(0)) + ->method('ldap_search') + ->with( + $this->equalTo('my_ldap_connection'), + $this->equalTo('ou=People,dc=kanboard,dc=local'), + $this->equalTo('uid=my_user'), + $this->equalTo(array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL)) + ) + ->will($this->returnValue('my_result_identifier')); + + self::$functions + ->expects($this->at(1)) + ->method('ldap_get_entries') + ->with( + $this->equalTo('my_ldap_connection'), + $this->equalTo('my_result_identifier') + ) + ->will($this->returnValue(array())); + + $this->assertFalse($this->ldap->search('my_ldap_connection', 'my_user', 'my_password', 'ou=People,dc=kanboard,dc=local', 'uid=%s')); + } + + public function testSuccessfulAuthentication() + { + $this->container['userSession'] = $this + ->getMockBuilder('\Model\UserSession') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('refresh')) + ->getMock(); + + $this->container['user'] = $this + ->getMockBuilder('\Model\User') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('getByUsername')) + ->getMock(); + + $ldap = $this + ->getMockBuilder('\Auth\Ldap') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('findUser')) + ->getMock(); + + $ldap + ->expects($this->once()) + ->method('findUser') + ->with( + $this->equalTo('user'), + $this->equalTo('password') + ) + ->will($this->returnValue(array('username' => 'user', 'name' => 'My user', 'email' => 'user@here'))); + + $this->container['user'] + ->expects($this->once()) + ->method('getByUsername') + ->with( + $this->equalTo('user') + ) + ->will($this->returnValue(array('id' => 2, 'username' => 'user', 'is_ldap_user' => 1))); + + $this->container['userSession'] + ->expects($this->once()) + ->method('refresh'); + + $this->assertTrue($ldap->authenticate('user', 'password')); + } + + public function testAuthenticationWithExistingLocalUser() + { + $this->container['userSession'] = $this + ->getMockBuilder('\Model\UserSession') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('refresh')) + ->getMock(); + + $this->container['user'] = $this + ->getMockBuilder('\Model\User') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('getByUsername')) + ->getMock(); + + $ldap = $this + ->getMockBuilder('\Auth\Ldap') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('findUser')) + ->getMock(); + + $ldap + ->expects($this->once()) + ->method('findUser') + ->with( + $this->equalTo('user'), + $this->equalTo('password') + ) + ->will($this->returnValue(array('username' => 'user', 'name' => 'My user', 'email' => 'user@here'))); + + $this->container['user'] + ->expects($this->once()) + ->method('getByUsername') + ->with( + $this->equalTo('user') + ) + ->will($this->returnValue(array('id' => 2, 'username' => 'user', 'is_ldap_user' => 0))); + + $this->container['userSession'] + ->expects($this->never()) + ->method('refresh'); + + $this->assertFalse($ldap->authenticate('user', 'password')); + } + + public function testAuthenticationWithAutomaticAccountCreation() + { + $this->container['userSession'] = $this + ->getMockBuilder('\Model\UserSession') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('refresh')) + ->getMock(); + + $this->container['user'] = $this + ->getMockBuilder('\Model\User') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('getByUsername')) + ->getMock(); + + $ldap = $this + ->getMockBuilder('\Auth\Ldap') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('findUser', 'createUser')) + ->getMock(); + + $ldap + ->expects($this->at(0)) + ->method('findUser') + ->with( + $this->equalTo('user'), + $this->equalTo('password') + ) + ->will($this->returnValue(array('username' => 'user', 'name' => 'My user', 'email' => 'user@here'))); + + $ldap + ->expects($this->at(1)) + ->method('createUser') + ->with( + $this->equalTo('user'), + $this->equalTo('My user'), + $this->equalTo('user@here') + ) + ->will($this->returnValue(true)); + + $this->container['user'] + ->expects($this->at(0)) + ->method('getByUsername') + ->with( + $this->equalTo('user') + ) + ->will($this->returnValue(null)); + + $this->container['user'] + ->expects($this->at(1)) + ->method('getByUsername') + ->with( + $this->equalTo('user') + ) + ->will($this->returnValue(array('id' => 2, 'username' => 'user', 'is_ldap_user' => 1))); + + $this->container['userSession'] + ->expects($this->once()) + ->method('refresh'); + + $this->assertTrue($ldap->authenticate('user', 'password')); + } + + public function testAuthenticationWithAutomaticAccountCreationFailed() + { + $this->container['userSession'] = $this + ->getMockBuilder('\Model\UserSession') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('refresh')) + ->getMock(); + + $this->container['user'] = $this + ->getMockBuilder('\Model\User') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('getByUsername')) + ->getMock(); + + $ldap = $this + ->getMockBuilder('\Auth\Ldap') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('findUser', 'createUser')) + ->getMock(); + + $ldap + ->expects($this->at(0)) + ->method('findUser') + ->with( + $this->equalTo('user'), + $this->equalTo('password') + ) + ->will($this->returnValue(array('username' => 'user', 'name' => 'My user', 'email' => 'user@here'))); + + $ldap + ->expects($this->at(1)) + ->method('createUser') + ->with( + $this->equalTo('user'), + $this->equalTo('My user'), + $this->equalTo('user@here') + ) + ->will($this->returnValue(false)); + + $this->container['user'] + ->expects($this->once()) + ->method('getByUsername') + ->with( + $this->equalTo('user') + ) + ->will($this->returnValue(null)); + + $this->container['userSession'] + ->expects($this->never()) + ->method('refresh'); + + $this->assertFalse($ldap->authenticate('user', 'password')); } } diff --git a/tests/units/Auth/ReverseProxyTest.php b/tests/units/Auth/ReverseProxyTest.php new file mode 100644 index 00000000..bbab7c0d --- /dev/null +++ b/tests/units/Auth/ReverseProxyTest.php @@ -0,0 +1,37 @@ +<?php + +require_once __DIR__.'/../Base.php'; + +use Auth\ReverseProxy; +use Model\User; + +class ReverseProxyTest extends Base +{ + public function setUp() + { + parent::setup(); + $_SERVER = array(); + } + + public function testFailedAuthentication() + { + $auth = new ReverseProxy($this->container); + $this->assertFalse($auth->authenticate()); + } + + public function testSuccessfulAuthentication() + { + $_SERVER[REVERSE_PROXY_USER_HEADER] = 'my_user'; + + $a = new ReverseProxy($this->container); + $u = new User($this->container); + + $this->assertTrue($a->authenticate()); + + $user = $u->getByUsername('my_user'); + $this->assertNotEmpty($user); + $this->assertEquals(0, $user['is_admin']); + $this->assertEquals(1, $user['is_ldap_user']); + $this->assertEquals(1, $user['disable_login_form']); + } +} diff --git a/tests/units/Base.php b/tests/units/Base.php index a48ae5ec..0a045a09 100644 --- a/tests/units/Base.php +++ b/tests/units/Base.php @@ -1,9 +1,6 @@ <?php require __DIR__.'/../../vendor/autoload.php'; - -define('LDAP_SERVER', 'my_ldap_server'); - require __DIR__.'/../../app/constants.php'; use Symfony\Component\EventDispatcher\EventDispatcher; |