Last active
June 30, 2017 06:59
-
-
Save heiglandreas/ff0d2f2d22d4bf1913168485a142fab8 to your computer and use it in GitHub Desktop.
Use Zend-Ldap as authentication backend when you want to authenticate using different attributes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use Zend\Authentication\AuthenticationService; | |
use Acme\Authentication\Adapter\Ldap as LdapAdapter; | |
// Retrieve the username and pasword from the request somehow. | |
$username = /* ... */; | |
$password = /* ... */; | |
$auth = new AuthenticationService(); | |
$ldapConfig = [ | |
'server1' => [ | |
'host' => 's0.foo.net', | |
'accountDomainName' => 'foo.net', | |
'accountDomainNameShort' => 'FOO', | |
'accountCanonicalForm' => 3, | |
'username' => 'CN=user1,DC=foo,DC=net', | |
'password' => 'pass1', | |
'baseDn' => 'OU=Sales,DC=foo,DC=net', | |
'userFilter'. => '(|(uid=%1$s)(mail=%1$s))' | |
'bindRequiresDn' => true, | |
], | |
'server2' => [ | |
'host' => 'dc1.w.net', | |
'useStartTls' => true, | |
'accountDomainName' => 'w.net', | |
'accountDomainNameShort' => 'W', | |
'accountCanonicalForm' => 3, | |
'baseDn' => 'CN=Users,DC=w,DC=net', | |
], | |
]; | |
$adapter = new LdapAdapter($ldapConfig, $username, $password); | |
$result = $auth->authenticate($adapter); | |
// Messages from position 2 and up are informational messages from the LDAP | |
// server: | |
foreach ($result->getMessages() as $i => $message) { | |
if ($i < 2) { | |
continue; | |
} | |
// Potentially log the $message | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Acme\Authentication\Adapter; | |
use Zend\Authentication\Adapter\Ldap as LdapAdapter; | |
class Ldap extends LdapAdpater | |
{ | |
public function authenticate() | |
{ | |
$messages = []; | |
$messages[0] = ''; // reserved | |
$messages[1] = ''; // reserved | |
$username = $this->identity; | |
$password = $this->credential; | |
if (!$username) { | |
$code = AuthenticationResult::FAILURE_IDENTITY_NOT_FOUND; | |
$messages[0] = 'A username is required'; | |
return new AuthenticationResult($code, '', $messages); | |
} | |
if (!$password) { | |
/* A password is required because some servers will | |
* treat an empty password as an anonymous bind. | |
*/ | |
$code = AuthenticationResult::FAILURE_CREDENTIAL_INVALID; | |
$messages[0] = 'A password is required'; | |
return new AuthenticationResult($code, '', $messages); | |
} | |
$ldap = $this->getLdap(); | |
$code = AuthenticationResult::FAILURE; | |
$messages[0] = "Authority not found: $username"; | |
$failedAuthorities = []; | |
/* Iterate through each server and try to authenticate the supplied | |
* credentials against it. | |
*/ | |
foreach ($this->options as $options) { | |
if (!is_array($options)) { | |
throw new Exception\InvalidArgumentException('Adapter options array not an array'); | |
} | |
$adapterOptions = $this->prepareOptions($ldap, $options); | |
$dname = ''; | |
try { | |
if ($messages[1]) { | |
$messages[] = $messages[1]; | |
} | |
$messages[1] = ''; | |
$messages[] = $this->optionsToString($options); | |
$canonicalName = $ldap->getCanonicalAccountName($username); | |
$ldap->bind($options['username'], $options['password']); | |
$result = $ldap->search(sprintf( | |
$options['filter'], | |
$this->username | |
), $options['baseDn']); | |
if ($result->count() < 1) { | |
continue; | |
} | |
if ($result->count() > 1) { | |
// There was more than one returned result, so the username was not unique and we can't know whom to | |
// authenticate… Let the fallback handle that! | |
continue(); | |
} | |
$dn = $result->getFirst()['dn']; | |
$groupResult = $this->checkGroupMembership($ldap, $canonicalName, $dn, $adapterOptions); | |
if ($groupResult === true) { | |
$this->authenticatedDn = $dn; | |
$messages[0] = ''; | |
$messages[1] = ''; | |
$messages[] = "$canonicalName authentication successful"; | |
if ($requireRebind === true) { | |
// rebinding with authenticated user | |
$ldap->bind($dn, $password); | |
} | |
return new AuthenticationResult(AuthenticationResult::SUCCESS, $canonicalName, $messages); | |
} else { | |
$messages[0] = 'Account is not a member of the specified group'; | |
$messages[1] = $groupResult; | |
$failedAuthorities[$dname] = $groupResult; | |
} | |
} catch (LdapException $zle) { | |
/* LDAP based authentication is notoriously difficult to diagnose. Therefore | |
* we bend over backwards to capture and record every possible bit of | |
* information when something goes wrong. | |
*/ | |
$err = $zle->getCode(); | |
if ($err == LdapException::LDAP_X_DOMAIN_MISMATCH) { | |
/* This error indicates that the domain supplied in the | |
* username did not match the domains in the server options | |
* and therefore we should just skip to the next set of | |
* server options. | |
*/ | |
continue; | |
} elseif ($err == LdapException::LDAP_NO_SUCH_OBJECT) { | |
$code = AuthenticationResult::FAILURE_IDENTITY_NOT_FOUND; | |
$messages[0] = "Account not found: $username"; | |
$failedAuthorities[$dname] = $zle->getMessage(); | |
} elseif ($err == LdapException::LDAP_INVALID_CREDENTIALS) { | |
$code = AuthenticationResult::FAILURE_CREDENTIAL_INVALID; | |
$messages[0] = 'Invalid credentials'; | |
$failedAuthorities[$dname] = $zle->getMessage(); | |
} else { | |
$line = $zle->getLine(); | |
$messages[] = $zle->getFile() . "($line): " . $zle->getMessage(); | |
$messages[] = preg_replace( | |
'/\b'.preg_quote(substr($password, 0, 15), '/').'\b/', | |
'*****', | |
$zle->getTraceAsString() | |
); | |
$messages[0] = 'An unexpected failure occurred'; | |
} | |
$messages[1] = $zle->getMessage(); | |
} | |
} | |
$msg = isset($messages[1]) ? $messages[1] : $messages[0]; | |
$messages[] = "$username authentication failed: $msg"; | |
return new AuthenticationResult($code, $username, $messages); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment