mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-04-08 17:15:08 +02:00
LdapUserBackend: Extend Repository and implement UserBackendInterface
refs #8826
This commit is contained in:
parent
e74194c18e
commit
c441117324
application/forms/Config/Authentication
library/Icinga/Authentication/User
modules/setup/application/forms
test/php/application/forms/Config/Authentication
@ -170,13 +170,8 @@ class LdapBackendForm extends Form
|
||||
public static function isValidAuthenticationBackend(Form $form)
|
||||
{
|
||||
try {
|
||||
$ldapUserBackend = new LdapUserBackend(
|
||||
ResourceFactory::createResource($form->getResourceConfig()),
|
||||
$form->getElement('user_class')->getValue(),
|
||||
$form->getElement('user_name_attribute')->getValue(),
|
||||
$form->getElement('base_dn')->getValue(),
|
||||
$form->getElement('filter')->getValue()
|
||||
);
|
||||
$ldapUserBackend = new LdapUserBackend(ResourceFactory::createResource($form->getResourceConfig()));
|
||||
$ldapUserBackend->setConfig(new ConfigObject($form->getValues()));
|
||||
$ldapUserBackend->assertAuthenticationPossible();
|
||||
} catch (AuthenticationException $e) {
|
||||
if (($previous = $e->getPrevious()) !== null) {
|
||||
|
@ -3,29 +3,55 @@
|
||||
|
||||
namespace Icinga\Authentication\User;
|
||||
|
||||
use Icinga\User;
|
||||
use Icinga\Protocol\Ldap\Query;
|
||||
use Icinga\Protocol\Ldap\Connection;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Exception\AuthenticationException;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Repository\Repository;
|
||||
use Icinga\Repository\RepositoryQuery;
|
||||
use Icinga\Protocol\Ldap\Exception as LdapException;
|
||||
use Icinga\Protocol\Ldap\Expression;
|
||||
use Icinga\User;
|
||||
|
||||
class LdapUserBackend extends UserBackend
|
||||
class LdapUserBackend extends Repository implements UserBackendInterface
|
||||
{
|
||||
/**
|
||||
* Connection to the LDAP server
|
||||
* The base DN to use for a query
|
||||
*
|
||||
* @var Connection
|
||||
* @var string
|
||||
*/
|
||||
protected $conn;
|
||||
|
||||
protected $baseDn;
|
||||
|
||||
/**
|
||||
* The objectClass where look for users
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $userClass;
|
||||
|
||||
/**
|
||||
* The attribute name where to find a user's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $userNameAttribute;
|
||||
|
||||
protected $customFilter;
|
||||
/**
|
||||
* The custom LDAP filter to apply on search queries
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $filter;
|
||||
|
||||
/**
|
||||
* The default sort rules to be applied on a query
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sortRules = array(
|
||||
'user_name' => array(
|
||||
'order' => 'asc'
|
||||
)
|
||||
);
|
||||
|
||||
protected $groupOptions;
|
||||
|
||||
@ -41,20 +67,115 @@ class LdapUserBackend extends UserBackend
|
||||
'samaccountname' => 'sAMAccountName'
|
||||
);
|
||||
|
||||
public function __construct(
|
||||
Connection $conn,
|
||||
$userClass,
|
||||
$userNameAttribute,
|
||||
$baseDn,
|
||||
$cutomFilter,
|
||||
$groupOptions = null
|
||||
) {
|
||||
$this->conn = $conn;
|
||||
$this->baseDn = trim($baseDn) ?: $conn->getDN();
|
||||
$this->userClass = $this->getNormedAttribute($userClass);
|
||||
/**
|
||||
* Set the base DN to use for a query
|
||||
*
|
||||
* @param string $baseDn
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setBaseDn($baseDn)
|
||||
{
|
||||
if (($baseDn = trim($baseDn))) {
|
||||
$this->baseDn = $baseDn;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base DN to use for a query
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBaseDn()
|
||||
{
|
||||
return $this->baseDn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the objectClass where to look for users
|
||||
*
|
||||
* Sets also the base table name for the underlying repository.
|
||||
*
|
||||
* @param string $userClass
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setUserClass($userClass)
|
||||
{
|
||||
$this->baseTable = $this->userClass = $this->getNormedAttribute($userClass);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the objectClass where to look for users
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserClass()
|
||||
{
|
||||
return $this->userClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the attribute name where to find a user's name
|
||||
*
|
||||
* @param string $userNameAttribute
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setUserNameAttribute($userNameAttribute)
|
||||
{
|
||||
$this->userNameAttribute = $this->getNormedAttribute($userNameAttribute);
|
||||
$this->customFilter = trim($cutomFilter);
|
||||
$this->groupOptions = $groupOptions;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the attribute name where to find a user's name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserNameAttribute()
|
||||
{
|
||||
return $this->userNameAttribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the custom LDAP filter to apply on search queries
|
||||
*
|
||||
* @param string $filter
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFilter($filter)
|
||||
{
|
||||
if (($filter = trim($filter))) {
|
||||
$this->filter = $filter;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the custom LDAP filter to apply on search queries
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFilter()
|
||||
{
|
||||
return $this->filter;
|
||||
}
|
||||
|
||||
public function setGroupOptions(array $options)
|
||||
{
|
||||
$this->groupOptions = $options;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getGroupOptions()
|
||||
{
|
||||
return $this->groupOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,45 +196,69 @@ class LdapUserBackend extends UserBackend
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query to select all usernames
|
||||
* Apply the given configuration to this backend
|
||||
*
|
||||
* @return Query
|
||||
* @param ConfigObject $config
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function selectUsers()
|
||||
public function setConfig(ConfigObject $config)
|
||||
{
|
||||
$query = $this->conn->select()->setBase($this->baseDn)->from(
|
||||
$this->userClass,
|
||||
array(
|
||||
$this->userNameAttribute
|
||||
)
|
||||
);
|
||||
return $this
|
||||
->setBaseDn($config->base_dn)
|
||||
->setUserClass($config->user_class)
|
||||
->setUserNameAttribute($config->user_name_attribute)
|
||||
->setFilter($config->filter);
|
||||
}
|
||||
|
||||
if ($this->customFilter) {
|
||||
$query->addFilter(new Expression($this->customFilter));
|
||||
/**
|
||||
* Return a new query for the given columns
|
||||
*
|
||||
* @param array $columns The desired columns, if null all columns will be queried
|
||||
*
|
||||
* @return RepositoryQuery
|
||||
*/
|
||||
public function select(array $columns = null)
|
||||
{
|
||||
$this->initializeQueryColumns();
|
||||
|
||||
$query = parent::select($columns);
|
||||
$query->getQuery()->setBase($this->baseDn);
|
||||
if ($this->filter) {
|
||||
$query->getQuery()->where(new Expression($this->filter));
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query filtered by the given username
|
||||
* Initialize this repository's query columns
|
||||
*
|
||||
* @param string $username
|
||||
*
|
||||
* @return Query
|
||||
* @throws ProgrammingError In case either $this->userNameAttribute or $this->userClass has not been set yet
|
||||
*/
|
||||
protected function selectUser($username)
|
||||
protected function initializeQueryColumns()
|
||||
{
|
||||
return $this->selectUsers()->setUsePagedResults(false)->where(
|
||||
$this->userNameAttribute,
|
||||
str_replace('*', '', $username)
|
||||
);
|
||||
if ($this->queryColumns === null) {
|
||||
if ($this->userClass === null) {
|
||||
throw new ProgrammingError('It is required to set the objectClass where to look for users first');
|
||||
}
|
||||
if ($this->userNameAttribute === null) {
|
||||
throw new ProgrammingError('It is required to set a attribute name where to find a user\'s name first');
|
||||
}
|
||||
|
||||
$this->queryColumns[$this->userClass] = array(
|
||||
'user_name' => $this->userNameAttribute,
|
||||
'is_active' => 'unknown', // msExchUserAccountControl == 2/512/514? <- AD LDAP
|
||||
'created_at' => 'whenCreated', // That's AD LDAP,
|
||||
'last_modified' => 'whenChanged' // what's OpenLDAP?
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Probe the backend to test if authentication is possible
|
||||
*
|
||||
* Try to bind to the backend and query all available users to check if:
|
||||
* Try to bind to the backend and fetch a single user to check if:
|
||||
* <ul>
|
||||
* <li>Connection credentials are correct and the bind is possible</li>
|
||||
* <li>At least one user exists</li>
|
||||
@ -125,23 +270,23 @@ class LdapUserBackend extends UserBackend
|
||||
public function assertAuthenticationPossible()
|
||||
{
|
||||
try {
|
||||
$result = $this->selectUsers()->fetchRow();
|
||||
$result = $this->select()->fetchRow();
|
||||
} catch (LdapException $e) {
|
||||
throw new AuthenticationException('Connection not possible.', $e);
|
||||
}
|
||||
|
||||
if ($result === null) {
|
||||
throw new AuthenticationException(
|
||||
'No objects with objectClass="%s" in DN="%s" found. (Filter: %s)',
|
||||
'No objects with objectClass "%s" in DN "%s" found. (Filter: %s)',
|
||||
$this->userClass,
|
||||
$this->baseDn,
|
||||
$this->customFilter ?: 'None'
|
||||
$this->baseDn ?: $this->ds->getDn(),
|
||||
$this->filter ?: 'None'
|
||||
);
|
||||
}
|
||||
|
||||
if (! isset($result->{$this->userNameAttribute})) {
|
||||
if (! isset($result->user_name)) {
|
||||
throw new AuthenticationException(
|
||||
'UserNameAttribute "%s" not existing in objectClass="%s"',
|
||||
'UserNameAttribute "%s" not existing in objectClass "%s"',
|
||||
$this->userNameAttribute,
|
||||
$this->userClass
|
||||
);
|
||||
@ -163,7 +308,7 @@ class LdapUserBackend extends UserBackend
|
||||
return array();
|
||||
}
|
||||
|
||||
$q = $this->conn->select()
|
||||
$result = $this->ds->select()
|
||||
->setBase($this->groupOptions['group_base_dn'])
|
||||
->from(
|
||||
$this->groupOptions['group_class'],
|
||||
@ -172,12 +317,10 @@ class LdapUserBackend extends UserBackend
|
||||
->where(
|
||||
$this->groupOptions['group_member_attribute'],
|
||||
$dn
|
||||
);
|
||||
|
||||
$result = $this->conn->fetchAll($q);
|
||||
)
|
||||
->fetchAll();
|
||||
|
||||
$groups = array();
|
||||
|
||||
foreach ($result as $group) {
|
||||
$groups[] = $group->{$this->groupOptions['group_attribute']};
|
||||
}
|
||||
@ -186,40 +329,30 @@ class LdapUserBackend extends UserBackend
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given user credentials are valid
|
||||
* Authenticate the given user
|
||||
*
|
||||
* @param User $user
|
||||
* @param string $password
|
||||
* @param boolean $healthCheck Assert that authentication is possible at all
|
||||
* @param User $user
|
||||
* @param string $password
|
||||
*
|
||||
* @return bool
|
||||
* @return bool True on success, false on failure
|
||||
*
|
||||
* @throws AuthenticationException In case an error occured or the health check has failed
|
||||
* @throws AuthenticationException In case authentication is not possible due to an error
|
||||
*/
|
||||
public function authenticate(User $user, $password, $healthCheck = false)
|
||||
public function authenticate(User $user, $password)
|
||||
{
|
||||
if ($healthCheck) {
|
||||
try {
|
||||
$this->assertAuthenticationPossible();
|
||||
} catch (AuthenticationException $e) {
|
||||
throw new AuthenticationException(
|
||||
'Authentication against backend "%s" not possible.',
|
||||
$this->getName(),
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$userDn = $this->conn->fetchDN($this->selectUser($user->getUsername()));
|
||||
$userDn = $this
|
||||
->select()
|
||||
->where('user_name', str_replace('*', '', $user->getUsername()))
|
||||
->getQuery()
|
||||
->setUsePagedResults(false)
|
||||
->fetchDn();
|
||||
|
||||
if ($userDn === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$authenticated = $this->conn->testCredentials(
|
||||
$userDn,
|
||||
$password
|
||||
);
|
||||
$authenticated = $this->ds->testCredentials($userDn, $password);
|
||||
if ($authenticated) {
|
||||
$groups = $this->getGroups($userDn);
|
||||
if ($groups !== null) {
|
||||
@ -237,35 +370,4 @@ class LdapUserBackend extends UserBackend
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of users available
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return $this->selectUsers()->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the names of all available users
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listUsers()
|
||||
{
|
||||
$users = array();
|
||||
foreach ($this->selectUsers()->fetchAll() as $row) {
|
||||
if (is_array($row->{$this->userNameAttribute})) {
|
||||
foreach ($row->{$this->userNameAttribute} as $col) {
|
||||
$users[] = $col;
|
||||
}
|
||||
} else {
|
||||
$users[] = $row->{$this->userNameAttribute};
|
||||
}
|
||||
}
|
||||
|
||||
return $users;
|
||||
}
|
||||
}
|
||||
|
@ -160,49 +160,30 @@ class UserBackend
|
||||
$backend = new DbUserBackend($resource);
|
||||
break;
|
||||
case 'msldap':
|
||||
$groupOptions = array(
|
||||
$backend = new LdapUserBackend($resource);
|
||||
$backend->setBaseDn($backendConfig->base_dn);
|
||||
$backend->setUserClass($backendConfig->get('user_class', 'user'));
|
||||
$backend->setUserNameAttribute($backendConfig->get('user_name_attribute', 'sAMAccountName'));
|
||||
$backend->setFilter($backendConfig->filter);
|
||||
$backend->setGroupOptions(array(
|
||||
'group_base_dn' => $backendConfig->get('group_base_dn', $resource->getDN()),
|
||||
'group_attribute' => $backendConfig->get('group_attribute', 'sAMAccountName'),
|
||||
'group_member_attribute' => $backendConfig->get('group_member_attribute', 'member'),
|
||||
'group_class' => $backendConfig->get('group_class', 'group')
|
||||
);
|
||||
$backend = new LdapUserBackend(
|
||||
$resource,
|
||||
$backendConfig->get('user_class', 'user'),
|
||||
$backendConfig->get('user_name_attribute', 'sAMAccountName'),
|
||||
$backendConfig->get('base_dn', $resource->getDN()),
|
||||
$backendConfig->get('filter'),
|
||||
$groupOptions
|
||||
);
|
||||
));
|
||||
break;
|
||||
case 'ldap':
|
||||
if ($backendConfig->user_class === null) {
|
||||
throw new ConfigurationError(
|
||||
'Authentication configuration for backend "%s" is missing the \'user_class\' directive',
|
||||
$name
|
||||
);
|
||||
}
|
||||
if ($backendConfig->user_name_attribute === null) {
|
||||
throw new ConfigurationError(
|
||||
'Authentication configuration for backend "%s" is'
|
||||
. ' missing the \'user_name_attribute\' directive',
|
||||
$name
|
||||
);
|
||||
}
|
||||
$groupOptions = array(
|
||||
$backend = new LdapUserBackend($resource);
|
||||
$backend->setBaseDn($backendConfig->base_dn);
|
||||
$backend->setUserClass($backendConfig->get('user_class', 'inetOrgPerson'));
|
||||
$backend->setUserNameAttribute($backendConfig->get('user_name_attribute', 'uid'));
|
||||
$backend->setFilter($backendConfig->filter);
|
||||
$backend->setGroupOptions(array(
|
||||
'group_base_dn' => $backendConfig->group_base_dn,
|
||||
'group_attribute' => $backendConfig->group_attribute,
|
||||
'group_member_attribute' => $backendConfig->group_member_attribute,
|
||||
'group_class' => $backendConfig->group_class
|
||||
);
|
||||
$backend = new LdapUserBackend(
|
||||
$resource,
|
||||
$backendConfig->user_class,
|
||||
$backendConfig->user_name_attribute,
|
||||
$backendConfig->get('base_dn', $resource->getDN()),
|
||||
$backendConfig->get('filter'),
|
||||
$groupOptions
|
||||
);
|
||||
));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -268,13 +268,8 @@ class AdminAccountPage extends Form
|
||||
if ($this->backendConfig['backend'] === 'db') {
|
||||
$backend = new DbUserBackend(ResourceFactory::createResource(new ConfigObject($this->resourceConfig)));
|
||||
} elseif ($this->backendConfig['backend'] === 'ldap') {
|
||||
$backend = new LdapUserBackend(
|
||||
ResourceFactory::createResource(new ConfigObject($this->resourceConfig)),
|
||||
$this->backendConfig['user_class'],
|
||||
$this->backendConfig['user_name_attribute'],
|
||||
$this->backendConfig['base_dn'],
|
||||
$this->backendConfig['filter']
|
||||
);
|
||||
$backend = new LdapUserBackend(ResourceFactory::createResource(new ConfigObject($this->resourceConfig)));
|
||||
$backend->setConfig($this->backendConfig);
|
||||
} else {
|
||||
throw new LogicException(
|
||||
sprintf(
|
||||
|
@ -29,7 +29,8 @@ class LdapBackendFormTest extends BaseTestCase
|
||||
{
|
||||
$this->setUpResourceFactoryMock();
|
||||
Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend')
|
||||
->shouldReceive('assertAuthenticationPossible')->andReturnNull();
|
||||
->shouldReceive('assertAuthenticationPossible')->andReturnNull()
|
||||
->shouldReceive('setConfig')->andReturnNull();
|
||||
|
||||
// Passing array(null) is required to make Mockery call the constructor...
|
||||
$form = Mockery::mock('Icinga\Forms\Config\Authentication\LdapBackendForm[getView]', array(null));
|
||||
|
Loading…
x
Reference in New Issue
Block a user