Improve LDAP auth

This commit is contained in:
Frederic Guillot
2015-10-11 14:44:16 -04:00
parent cf6d4d1c0a
commit dc0749ecce
4 changed files with 86 additions and 65 deletions

View File

@@ -30,6 +30,17 @@ class Ldap extends Base
return LDAP_SERVER; return LDAP_SERVER;
} }
/**
* Get LDAP bind type
*
* @access public
* @return integer
*/
public function getLdapBindType()
{
return LDAP_BIND_TYPE;
}
/** /**
* Get LDAP server port * Get LDAP server port
* *
@@ -265,7 +276,7 @@ class Ldap extends Base
public function connect() public function connect()
{ {
if (! function_exists('ldap_connect')) { if (! function_exists('ldap_connect')) {
$this->logger->error('The PHP LDAP extension is required'); $this->logger->error('LDAP: The PHP LDAP extension is required');
return false; return false;
} }
@@ -277,7 +288,7 @@ class Ldap extends Base
$ldap = ldap_connect($this->getLdapServer(), $this->getLdapPort()); $ldap = ldap_connect($this->getLdapServer(), $this->getLdapPort());
if ($ldap === false) { if ($ldap === false) {
$this->logger->error('Unable to connect to the LDAP server'); $this->logger->error('LDAP: Unable to connect to the LDAP server');
return false; return false;
} }
@@ -287,7 +298,7 @@ class Ldap extends Base
ldap_set_option($ldap, LDAP_OPT_TIMELIMIT, 1); ldap_set_option($ldap, LDAP_OPT_TIMELIMIT, 1);
if (LDAP_START_TLS && ! @ldap_start_tls($ldap)) { if (LDAP_START_TLS && ! @ldap_start_tls($ldap)) {
$this->logger->error('Unable to use ldap_start_tls()'); $this->logger->error('LDAP: Unable to use ldap_start_tls()');
return false; return false;
} }
@@ -301,16 +312,15 @@ class Ldap extends Base
* @param resource $ldap * @param resource $ldap
* @param string $username * @param string $username
* @param string $password * @param string $password
* @param string $ldap_type
* @return boolean * @return boolean
*/ */
public function bind($ldap, $username, $password, $ldap_type = LDAP_BIND_TYPE) public function bind($ldap, $username, $password)
{ {
if ($ldap_type === 'user') { if ($this->getLdapBindType() === 'user') {
$ldap_username = $this->getLdapUserPattern($username); $ldap_username = sprintf($this->getLdapUsername(), $username);
$ldap_password = $password; $ldap_password = $password;
} }
else if ($ldap_type === 'proxy') { else if ($this->getLdapBindType() === 'proxy') {
$ldap_username = $this->getLdapUsername(); $ldap_username = $this->getLdapUsername();
$ldap_password = $this->getLdapPassword(); $ldap_password = $this->getLdapPassword();
} }
@@ -320,6 +330,8 @@ class Ldap extends Base
} }
if (! @ldap_bind($ldap, $ldap_username, $ldap_password)) { if (! @ldap_bind($ldap, $ldap_username, $ldap_password)) {
$this->logger->error('LDAP: Unable to bind to server with: '.$ldap_username);
$this->logger->error('LDAP: bind type='.$this->getLdapBindType());
return false; return false;
} }
@@ -337,8 +349,11 @@ class Ldap extends Base
*/ */
public function getProfile($ldap, $username, $password) public function getProfile($ldap, $username, $password)
{ {
$entries = $this->executeQuery($ldap, $this->getLdapUserPattern($username)); $user_pattern = $this->getLdapUserPattern($username);
$entries = $this->executeQuery($ldap, $user_pattern);
if ($entries === false) { if ($entries === false) {
$this->logger->error('LDAP: Unable to get user profile: '.$user_pattern);
return false; return false;
} }
@@ -346,6 +361,10 @@ class Ldap extends Base
return $this->prepareProfile($ldap, $entries, $username); return $this->prepareProfile($ldap, $entries, $username);
} }
if (DEBUG) {
$this->logger->debug('LDAP: wrong password for '.$entries[0]['dn']);
}
return false; return false;
} }
@@ -442,7 +461,7 @@ class Ldap extends Base
*/ */
private function executeQuery($ldap, $query) private function executeQuery($ldap, $query)
{ {
$sr = ldap_search($ldap, $this->getLdapBaseDn(), $query, $this->getProfileAttributes()); $sr = @ldap_search($ldap, $this->getLdapBaseDn(), $query, $this->getProfileAttributes());
if ($sr === false) { if ($sr === false) {
return false; return false;
} }

View File

@@ -72,20 +72,20 @@ define('LDAP_SERVER', '');
// LDAP server port (389 by default) // LDAP server port (389 by default)
define('LDAP_PORT', 389); define('LDAP_PORT', 389);
// By default, require certificate to be verified for ldaps:// style URL. Set to false to skip the verification. // By default, require certificate to be verified for ldaps:// style URL. Set to false to skip the verification
define('LDAP_SSL_VERIFY', true); define('LDAP_SSL_VERIFY', true);
// Enable LDAP START_TLS // Enable LDAP START_TLS
define('LDAP_START_TLS', false); define('LDAP_START_TLS', false);
// LDAP bind type: "anonymous", "user" (use the given user/password from the form) and "proxy" (a specific user to browse the LDAP directory) // LDAP bind type: "anonymous", "user" or "proxy"
define('LDAP_BIND_TYPE', 'anonymous'); define('LDAP_BIND_TYPE', 'anonymous');
// LDAP username to connect with. null for anonymous bind (by default). // LDAP username to use with proxy mode
// Or for user bind type, you can use a pattern: %s@kanboard.local // LDAP username pattern to use with user mode
define('LDAP_USERNAME', null); define('LDAP_USERNAME', null);
// LDAP password to connect with. null for anonymous bind (by default). // LDAP password to use for proxy mode
define('LDAP_PASSWORD', null); define('LDAP_PASSWORD', null);
// LDAP account base, i.e. root of all user account // LDAP account base, i.e. root of all user account
@@ -97,10 +97,10 @@ define('LDAP_ACCOUNT_BASE', '');
// Example for OpenLDAP: 'uid=%s' // Example for OpenLDAP: 'uid=%s'
define('LDAP_USER_PATTERN', ''); define('LDAP_USER_PATTERN', '');
// Name of an attribute of the user account object which should be used as the full name of the user. // Name of an attribute of the user account object which should be used as the full name of the user
define('LDAP_ACCOUNT_FULLNAME', 'displayname'); define('LDAP_ACCOUNT_FULLNAME', 'displayname');
// Name of an attribute of the user account object which should be used as the email of the user. // Name of an attribute of the user account object which should be used as the email of the user
define('LDAP_ACCOUNT_EMAIL', 'mail'); define('LDAP_ACCOUNT_EMAIL', 'mail');
// Name of an attribute of the user account object which should be used as the id of the user. (optional) // Name of an attribute of the user account object which should be used as the id of the user. (optional)

View File

@@ -4,7 +4,7 @@ LDAP authentication
Requirements Requirements
------------ ------------
- LDAP extension for PHP - PHP LDAP extension enabled
- LDAP server: - LDAP server:
- OpenLDAP - OpenLDAP
- Microsoft Active Directory - Microsoft Active Directory
@@ -23,8 +23,6 @@ When the LDAP authentication is activated, the login process work like that:
- LDAP users have no local passwords - LDAP users have no local passwords
- LDAP users can't modify their password with the user interface - LDAP users can't modify their password with the user interface
- By default, all LDAP users have no admin privileges
- To become administrator, a LDAP user must be promoted by another administrator
The full name and the email address are automatically fetched from the LDAP server. The full name and the email address are automatically fetched from the LDAP server.
@@ -36,9 +34,9 @@ This file must be stored in the root directory of Kanboard.
### LDAP bind type ### LDAP bind type
There is 3 possible ways to browse the LDAP directory: There are 3 possible ways to browse the LDAP directory:
#### Anonymous browsing #### Anonymous mode
```php ```php
define('LDAP_BIND_TYPE', 'anonymous'); define('LDAP_BIND_TYPE', 'anonymous');
@@ -48,10 +46,9 @@ define('LDAP_PASSWORD', null);
This is the default value but some LDAP servers don't allow that. This is the default value but some LDAP servers don't allow that.
#### Proxy user #### Proxy mode
A specific user is used to browse the LDAP directory. A specific user is used to browse the LDAP directory:
By example, Novell eDirectory use that method.
```php ```php
define('LDAP_BIND_TYPE', 'proxy'); define('LDAP_BIND_TYPE', 'proxy');
@@ -59,33 +56,28 @@ define('LDAP_USERNAME', 'my proxy user');
define('LDAP_PASSWORD', 'my proxy password'); define('LDAP_PASSWORD', 'my proxy password');
``` ```
#### User credentials #### User mode
This method uses the credentials provided by the end-user.
This method use the credentials provided by the end-user.
By example, Microsoft Active Directory doesn't allow anonymous browsing by default and if you don't want to use a proxy user you can use this method. By example, Microsoft Active Directory doesn't allow anonymous browsing by default and if you don't want to use a proxy user you can use this method.
```php ```php
define('LDAP_BIND_TYPE', 'user'); define('LDAP_BIND_TYPE', 'user');
define('LDAP_USERNAME', '%s@mydomain.local'); define('LDAP_USERNAME', '%s@kanboard.local');
define('LDAP_PASSWORD', null); define('LDAP_PASSWORD', null);
``` ```
Here, the `LDAP_USERNAME` is use to define a replacement pattern: In this case, the constant `LDAP_USERNAME` is used as a pattern to the ldap username, examples:
```php - `%s@kanboard.local` will be replaced by `my_user@kanboard.local`
define('LDAP_USERNAME', '%s@mydomain.local'); - `KANBOARD\\%s` will be replaced by `KANBOARD\my_user`
// Another way to do the same:
define('LDAP_USERNAME', 'MYDOMAIN\\%s');
```
### Example for Microsoft Active Directory ### Example for Microsoft Active Directory
Let's say we have a domain `KANBOARD` (kanboard.local) and the primary controller is `myserver.kanboard.local`. Let's say we have a domain `KANBOARD` (kanboard.local) and the primary controller is `myserver.kanboard.local`.
Microsoft Active Directory doesn't allow anonymous binding by default.
First example with a proxy user: First example with proxy mode:
```php ```php
<?php <?php
@@ -93,7 +85,6 @@ First example with a proxy user:
// Enable LDAP authentication (false by default) // Enable LDAP authentication (false by default)
define('LDAP_AUTH', true); define('LDAP_AUTH', true);
// Credentials to be allowed to browse the LDAP directory
define('LDAP_BIND_TYPE', 'proxy'); define('LDAP_BIND_TYPE', 'proxy');
define('LDAP_USERNAME', 'administrator@kanboard.local'); define('LDAP_USERNAME', 'administrator@kanboard.local');
define('LDAP_PASSWORD', 'my super secret password'); define('LDAP_PASSWORD', 'my super secret password');
@@ -104,11 +95,9 @@ define('LDAP_SERVER', 'myserver.kanboard.local');
// LDAP properties // LDAP properties
define('LDAP_ACCOUNT_BASE', 'CN=Users,DC=kanboard,DC=local'); define('LDAP_ACCOUNT_BASE', 'CN=Users,DC=kanboard,DC=local');
define('LDAP_USER_PATTERN', '(&(objectClass=user)(sAMAccountName=%s))'); define('LDAP_USER_PATTERN', '(&(objectClass=user)(sAMAccountName=%s))');
define('LDAP_ACCOUNT_FULLNAME', 'displayname');
define('LDAP_ACCOUNT_EMAIL', 'mail');
``` ```
Another way with no proxy user: Second example with user mode:
```php ```php
<?php <?php
@@ -116,9 +105,8 @@ Another way with no proxy user:
// Enable LDAP authentication (false by default) // Enable LDAP authentication (false by default)
define('LDAP_AUTH', true); define('LDAP_AUTH', true);
// Credentials to be allowed to browse the LDAP directory
define('LDAP_BIND_TYPE', 'user'); define('LDAP_BIND_TYPE', 'user');
define('LDAP_USERNAME', '%s@kanboard.local'); // or 'KANBOARD\\%s' define('LDAP_USERNAME', '%s@kanboard.local');
define('LDAP_PASSWORD', null); define('LDAP_PASSWORD', null);
// LDAP server hostname // LDAP server hostname
@@ -127,15 +115,13 @@ define('LDAP_SERVER', 'myserver.kanboard.local');
// LDAP properties // LDAP properties
define('LDAP_ACCOUNT_BASE', 'CN=Users,DC=kanboard,DC=local'); define('LDAP_ACCOUNT_BASE', 'CN=Users,DC=kanboard,DC=local');
define('LDAP_USER_PATTERN', '(&(objectClass=user)(sAMAccountName=%s))'); define('LDAP_USER_PATTERN', '(&(objectClass=user)(sAMAccountName=%s))');
define('LDAP_ACCOUNT_FULLNAME', 'displayname');
define('LDAP_ACCOUNT_EMAIL', 'mail');
``` ```
### Example for OpenLDAP ### Example for OpenLDAP
Our LDAP server is `myserver.example.com` and all users are stored in the hierarchy `ou=People,dc=example,dc=com`. Our LDAP server is `myserver.example.com` and all users are stored under `ou=People,dc=example,dc=com`.
For this example with use the anonymous binding. For this example we use the anonymous binding.
```php ```php
<?php <?php
@@ -149,11 +135,9 @@ define('LDAP_SERVER', 'myserver.example.com');
// LDAP properties // LDAP properties
define('LDAP_ACCOUNT_BASE', 'ou=People,dc=example,dc=com'); define('LDAP_ACCOUNT_BASE', 'ou=People,dc=example,dc=com');
define('LDAP_USER_PATTERN', 'uid=%s'); define('LDAP_USER_PATTERN', 'uid=%s');
define('LDAP_ACCOUNT_FULLNAME', 'displayname');
define('LDAP_ACCOUNT_EMAIL', 'mail');
``` ```
The `%s` is replaced by the username for the parameter `LDAP_USER_PATTERN`, so you can define a custom Distinguished Name (example: ` (&(objectClass=user)(uid=%s)(!(ou:dn::=trainees)))`). The `%s` is replaced by the username for the parameter `LDAP_USER_PATTERN`, so you can define a custom Distinguished Name: ` (&(objectClass=user)(uid=%s)(!(ou:dn::=trainees)))`.
### Disable automatic account creation ### Disable automatic account creation
@@ -168,7 +152,7 @@ Just change the value of `LDAP_ACCOUNT_CREATION` to `false`:
define('LDAP_ACCOUNT_CREATION', false); define('LDAP_ACCOUNT_CREATION', false);
``` ```
### SELinux on RHEL-based like CentOS ### SELinux restrictions
If SELinux is enabled, you have to allow Apache to reach out your LDAP server. If SELinux is enabled, you have to allow Apache to reach out your LDAP server.
@@ -189,20 +173,19 @@ define('LDAP_SERVER', '');
// LDAP server port (389 by default) // LDAP server port (389 by default)
define('LDAP_PORT', 389); define('LDAP_PORT', 389);
// By default, require certificate to be verified for ldaps:// style URL. Set to false to skip the verification. // By default, require certificate to be verified for ldaps:// style URL. Set to false to skip the verification
define('LDAP_SSL_VERIFY', true); define('LDAP_SSL_VERIFY', true);
// Enable LDAP START_TLS // Enable LDAP START_TLS
define('LDAP_START_TLS', false); define('LDAP_START_TLS', false);
// LDAP bind type: "anonymous", "user" (use the given user/password from the form) and "proxy" (a specific user to browse the LDAP directory) // LDAP bind type: "anonymous", "user" or "proxy"
define('LDAP_BIND_TYPE', 'anonymous'); define('LDAP_BIND_TYPE', 'anonymous');
// LDAP username to connect with. null for anonymous bind (by default). // LDAP username to connect with. null for anonymous bind (default).
// Or for user bind type, you can use a pattern like that %s@kanboard.local
define('LDAP_USERNAME', null); define('LDAP_USERNAME', null);
// LDAP password to connect with. null for anonymous bind (by default). // LDAP password to connect with. null for anonymous bind (default).
define('LDAP_PASSWORD', null); define('LDAP_PASSWORD', null);
// LDAP account base, i.e. root of all user account // LDAP account base, i.e. root of all user account
@@ -223,7 +206,7 @@ define('LDAP_ACCOUNT_EMAIL', 'mail');
// Name of an attribute of the user account object which should be used as the id of the user. // Name of an attribute of the user account object which should be used as the id of the user.
// Example for ActiveDirectory: 'samaccountname' // Example for ActiveDirectory: 'samaccountname'
// Example for OpenLDAP: 'uid' // Example for OpenLDAP: 'uid'
define('LDAP_ACCOUNT_ID', 'samaccountname'); define('LDAP_ACCOUNT_ID', '');
// LDAP Attribute for group membership // LDAP Attribute for group membership
define('LDAP_ACCOUNT_MEMBEROF', 'memberof'); define('LDAP_ACCOUNT_MEMBEROF', 'memberof');

View File

@@ -116,7 +116,16 @@ class LdapTest extends \Base
public function testBindAnonymous() public function testBindAnonymous()
{ {
$ldap = new Ldap($this->container); $ldap = $this
->getMockBuilder('\Auth\Ldap')
->setConstructorArgs(array($this->container))
->setMethods(array('getLdapBindType'))
->getMock();
$ldap
->expects($this->any())
->method('getLdapBindType')
->will($this->returnValue('anonymous'));
self::$functions self::$functions
->expects($this->once()) ->expects($this->once())
@@ -128,7 +137,7 @@ class LdapTest extends \Base
) )
->will($this->returnValue(true)); ->will($this->returnValue(true));
$this->assertTrue($ldap->bind('my_ldap_connection', 'my_user', 'my_password', 'anonymous')); $this->assertTrue($ldap->bind('my_ldap_connection', 'my_user', 'my_password'));
} }
public function testBindUser() public function testBindUser()
@@ -136,14 +145,19 @@ class LdapTest extends \Base
$ldap = $this $ldap = $this
->getMockBuilder('\Auth\Ldap') ->getMockBuilder('\Auth\Ldap')
->setConstructorArgs(array($this->container)) ->setConstructorArgs(array($this->container))
->setMethods(array('getLdapUserPattern')) ->setMethods(array('getLdapUsername', 'getLdapBindType'))
->getMock(); ->getMock();
$ldap $ldap
->expects($this->once()) ->expects($this->once())
->method('getLdapUserPattern') ->method('getLdapUsername')
->will($this->returnValue('uid=my_user')); ->will($this->returnValue('uid=my_user'));
$ldap
->expects($this->any())
->method('getLdapBindType')
->will($this->returnValue('user'));
self::$functions self::$functions
->expects($this->once()) ->expects($this->once())
->method('ldap_bind') ->method('ldap_bind')
@@ -154,7 +168,7 @@ class LdapTest extends \Base
) )
->will($this->returnValue(true)); ->will($this->returnValue(true));
$this->assertTrue($ldap->bind('my_ldap_connection', 'my_user', 'my_password', 'user')); $this->assertTrue($ldap->bind('my_ldap_connection', 'my_user', 'my_password'));
} }
public function testBindProxy() public function testBindProxy()
@@ -162,7 +176,7 @@ class LdapTest extends \Base
$ldap = $this $ldap = $this
->getMockBuilder('\Auth\Ldap') ->getMockBuilder('\Auth\Ldap')
->setConstructorArgs(array($this->container)) ->setConstructorArgs(array($this->container))
->setMethods(array('getLdapUsername', 'getLdapPassword')) ->setMethods(array('getLdapUsername', 'getLdapPassword', 'getLdapBindType'))
->getMock(); ->getMock();
$ldap $ldap
@@ -175,6 +189,11 @@ class LdapTest extends \Base
->method('getLdapPassword') ->method('getLdapPassword')
->will($this->returnValue('something')); ->will($this->returnValue('something'));
$ldap
->expects($this->any())
->method('getLdapBindType')
->will($this->returnValue('proxy'));
self::$functions self::$functions
->expects($this->once()) ->expects($this->once())
->method('ldap_bind') ->method('ldap_bind')
@@ -185,7 +204,7 @@ class LdapTest extends \Base
) )
->will($this->returnValue(true)); ->will($this->returnValue(true));
$this->assertTrue($ldap->bind('my_ldap_connection', 'my_user', 'my_password', 'proxy')); $this->assertTrue($ldap->bind('my_ldap_connection', 'my_user', 'my_password'));
} }
public function testSearchSuccess() public function testSearchSuccess()