diff --git a/application/forms/Config/Authentication/LdapBackendForm.php b/application/forms/Config/Authentication/LdapBackendForm.php
index 867f5658c..0ec2c3e38 100644
--- a/application/forms/Config/Authentication/LdapBackendForm.php
+++ b/application/forms/Config/Authentication/LdapBackendForm.php
@@ -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) {
diff --git a/library/Icinga/Authentication/User/LdapUserBackend.php b/library/Icinga/Authentication/User/LdapUserBackend.php
index 8bc8f0222..9b2012b46 100644
--- a/library/Icinga/Authentication/User/LdapUserBackend.php
+++ b/library/Icinga/Authentication/User/LdapUserBackend.php
@@ -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:
*
* - Connection credentials are correct and the bind is possible
* - At least one user exists
@@ -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;
- }
}
diff --git a/library/Icinga/Authentication/User/UserBackend.php b/library/Icinga/Authentication/User/UserBackend.php
index 8bda07323..3d11289fb 100644
--- a/library/Icinga/Authentication/User/UserBackend.php
+++ b/library/Icinga/Authentication/User/UserBackend.php
@@ -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;
}
diff --git a/modules/setup/application/forms/AdminAccountPage.php b/modules/setup/application/forms/AdminAccountPage.php
index 9b1ed06fc..7378a4095 100644
--- a/modules/setup/application/forms/AdminAccountPage.php
+++ b/modules/setup/application/forms/AdminAccountPage.php
@@ -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(
diff --git a/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php b/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php
index 43b8bc1d2..442770e83 100644
--- a/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php
+++ b/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php
@@ -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));