LDAP authentication support (#815)

This commit is contained in:
Kevin Papst
2019-06-07 22:48:39 +02:00
committed by GitHub
parent bcf1ebd778
commit 0c0e9c2f71
99 changed files with 3542 additions and 926 deletions

165
src/Ldap/LdapManager.php Normal file
View File

@@ -0,0 +1,165 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Ldap;
use App\Configuration\LdapConfiguration;
use App\Entity\User;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* Inspired by https://github.com/Maks3w/FR3DLdapBundle @ MIT License
*/
class LdapManager
{
/**
* @var LdapConfiguration
*/
protected $config;
/**
* @var LdapDriver
*/
protected $driver;
/**
* @var array
*/
protected $params = [];
/**
* @var LdapUserHydrator
*/
protected $hydrator;
public function __construct(LdapDriver $driver, LdapUserHydrator $hydrator, LdapConfiguration $config)
{
$this->params = $config->getUserParameters();
$this->config = $config;
$this->driver = $driver;
$this->hydrator = $hydrator;
}
/**
* Only executed for unknown local users.
*
* @param string $username
* @return User|null
* @throws \Exception
*/
public function findUserByUsername(string $username): ?UserInterface
{
return $this->findUserBy([$this->params['usernameAttribute'] => $username]);
}
/**
* @param array $criteria
* @return User|null
* @throws LdapDriverException
*/
public function findUserBy(array $criteria): ?UserInterface
{
$filter = $this->buildFilter($criteria);
$entries = $this->driver->search($this->params['baseDn'], $filter);
if ($entries['count'] > 1) {
throw new LdapDriverException('This search must only return a single user');
}
if (0 === $entries['count']) {
return null;
}
// do not updateUser() here, as this would happen before bind()
return $this->hydrator->hydrate($entries[0]);
}
protected function buildFilter(array $criteria, string $condition = '&'): string
{
$filters = [];
$filters[] = $this->params['filter'];
foreach ($criteria as $key => $value) {
$value = ldap_escape($value, '', LDAP_ESCAPE_FILTER);
$filters[] = sprintf('(%s=%s)', $key, $value);
}
return sprintf('(%s%s)', $condition, implode($filters));
}
public function bind(UserInterface $user, string $password): bool
{
return $this->driver->bind($user, $password);
}
/**
* This method does all the heavy lifting:
* - searching for latest 'dn'
* - syncing user attributes
* - syncing roles
*
* @param User $user
* @throws LdapDriverException
*/
public function updateUser(User $user)
{
$baseDn = $user->getPreferenceValue('ldap.dn');
$filter = '(objectClass=*)';
if (null === $baseDn) {
throw new LdapDriverException('This account is not a registered LDAP user');
}
// always look up the users current DN first, as the cached DN might have been renamed in LDAP
$userFresh = $this->findUserByUsername($user->getUsername());
if (null === $userFresh || null === ($baseDn = $userFresh->getPreferenceValue('ldap.dn'))) {
throw new LdapDriverException(sprintf('Failed fetching user DN for %s', $user->getUsername()));
}
$user->setPreferenceValue('ldap.dn', $baseDn);
$entries = $this->driver->search($baseDn, $filter);
if ($entries['count'] > 1) {
throw new LdapDriverException('This search must only return a single user');
}
if (0 === $entries['count']) {
return;
}
$this->hydrator->hydrateUser($user, $entries[0]);
$roleParameter = $this->config->getRoleParameters();
if (null === $roleParameter['baseDn']) {
return;
}
$param = $roleParameter['usernameAttribute'];
if (!isset($entries[0][$param]) && $param !== 'dn') {
$param = 'dn';
}
$roleValue = $entries[0][$param];
if (is_array($roleValue)) {
$roleValue = $roleValue[0];
}
$roles = $this->getRoles($roleValue, $roleParameter);
if (!empty($roles)) {
$this->hydrator->hydrateRoles($user, $roles);
}
}
protected function getRoles(string $dn, array $roleParameter): array
{
$filter = $roleParameter['filter'] ?? '';
return $this->driver->search(
$roleParameter['baseDn'],
sprintf('(&%s(%s=%s))', $filter, $roleParameter['userDnAttribute'], $dn),
[$roleParameter['nameAttribute']]
);
}
}