Skip to content

Instantly share code, notes, and snippets.

@heiglandreas
Last active June 30, 2017 06:59
Show Gist options
  • Save heiglandreas/ff0d2f2d22d4bf1913168485a142fab8 to your computer and use it in GitHub Desktop.
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
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
}
<?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