From 39a74c4f3d3fb7859ced908316b17b13d6ef56ca Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 11 Mar 2015 09:52:14 +0100 Subject: [PATCH] LDAP-Auth backend config: Add support for custom LDAP filter rules refs #8365 --- .../Config/Authentication/LdapBackendForm.php | 58 +++++++++++++++---- .../Backend/LdapUserBackend.php | 29 ++++++++-- library/Icinga/Authentication/UserBackend.php | 2 + .../application/forms/AdminAccountPage.php | 3 +- .../Setup/Steps/AuthenticationStep.php | 8 ++- 5 files changed, 81 insertions(+), 19 deletions(-) diff --git a/application/forms/Config/Authentication/LdapBackendForm.php b/application/forms/Config/Authentication/LdapBackendForm.php index 4c7a027a7..838be7776 100644 --- a/application/forms/Config/Authentication/LdapBackendForm.php +++ b/application/forms/Config/Authentication/LdapBackendForm.php @@ -55,7 +55,7 @@ class LdapBackendForm extends Form 'required' => true, 'label' => $this->translate('Backend Name'), 'description' => $this->translate( - 'The name of this authentication provider that is used to differentiate it from others' + 'The name of this authentication provider that is used to differentiate it from others.' ) ) ); @@ -64,8 +64,10 @@ class LdapBackendForm extends Form 'resource', array( 'required' => true, - 'label' => $this->translate('LDAP Resource'), - 'description' => $this->translate('The resource to use for authenticating with this provider'), + 'label' => $this->translate('LDAP Connection'), + 'description' => $this->translate( + 'The LDAP connection to use for authenticating with this provider.' + ), 'multiOptions' => false === empty($this->resources) ? array_combine($this->resources, $this->resources) : array() @@ -77,10 +79,40 @@ class LdapBackendForm extends Form array( 'required' => true, 'label' => $this->translate('LDAP User Object Class'), - 'description' => $this->translate('The object class used for storing users on the ldap server'), + 'description' => $this->translate('The object class used for storing users on the LDAP server.'), 'value' => 'inetOrgPerson' ) ); + $this->addElement( + 'text', + 'filter', + array( + 'allowEmpty' => true, + 'label' => $this->translate('LDAP Filter'), + 'description' => $this->translate( + 'An additional filter to use when looking up users using the specified connection. ' + . 'Leave empty to not to use any additional filter rules.' + ), + 'requirement' => $this->translate( + 'The filter needs to be expressed as standard LDAP expression, without' + . ' outer parentheses. (e.g. &(foo=bar)(bar=foo) or foo=bar)' + ), + 'validators' => array( + array( + 'Callback', + false, + array( + 'callback' => function ($v) { + return strpos($v, '(') !== 0; + }, + 'messages' => array( + 'callbackValue' => $this->translate('The filter must not be wrapped in parantheses.') + ) + ) + ) + ) + ) + ); $this->addElement( 'text', 'user_name_attribute', @@ -88,7 +120,7 @@ class LdapBackendForm extends Form 'required' => true, 'label' => $this->translate('LDAP User Name Attribute'), 'description' => $this->translate( - 'The attribute name used for storing the user name on the ldap server' + 'The attribute name used for storing the user name on the LDAP server.' ), 'value' => 'uid' ) @@ -106,10 +138,10 @@ class LdapBackendForm extends Form 'base_dn', array( 'required' => false, - 'label' => $this->translate('Base DN'), + 'label' => $this->translate('LDAP Base DN'), 'description' => $this->translate( - 'The path where users can be found on the ldap server. Leave ' . - 'empty to select all users available on the specified resource.' + 'The path where users can be found on the LDAP server. Leave ' . + 'empty to select all users available using the specified connection.' ) ) ); @@ -142,11 +174,17 @@ class LdapBackendForm extends Form ResourceFactory::createResource($form->getResourceConfig()), $form->getElement('user_class')->getValue(), $form->getElement('user_name_attribute')->getValue(), - $form->getElement('base_dn')->getValue() + $form->getElement('base_dn')->getValue(), + $form->getElement('filter')->getValue() ); $ldapUserBackend->assertAuthenticationPossible(); } catch (AuthenticationException $e) { - $form->addError($e->getMessage()); + if (($previous = $e->getPrevious()) !== null) { + $form->addError($previous->getMessage()); + } else { + $form->addError($e->getMessage()); + } + return false; } catch (Exception $e) { $form->addError(sprintf($form->translate('Unable to validate authentication: %s'), $e->getMessage())); diff --git a/library/Icinga/Authentication/Backend/LdapUserBackend.php b/library/Icinga/Authentication/Backend/LdapUserBackend.php index 016512ab4..211f05711 100644 --- a/library/Icinga/Authentication/Backend/LdapUserBackend.php +++ b/library/Icinga/Authentication/Backend/LdapUserBackend.php @@ -9,6 +9,7 @@ use Icinga\Protocol\Ldap\Query; use Icinga\Protocol\Ldap\Connection; use Icinga\Exception\AuthenticationException; use Icinga\Protocol\Ldap\Exception as LdapException; +use Icinga\Protocol\Ldap\Expression; class LdapUserBackend extends UserBackend { @@ -25,14 +26,23 @@ class LdapUserBackend extends UserBackend protected $userNameAttribute; + protected $customFilter; + protected $groupOptions; - public function __construct(Connection $conn, $userClass, $userNameAttribute, $baseDn, $groupOptions = null) - { + public function __construct( + Connection $conn, + $userClass, + $userNameAttribute, + $baseDn, + $cutomFilter, + $groupOptions = null + ) { $this->conn = $conn; - $this->baseDn = trim($baseDn) !== '' ? $baseDn : $conn->getDN(); + $this->baseDn = trim($baseDn) ?: $conn->getDN(); $this->userClass = $userClass; $this->userNameAttribute = $userNameAttribute; + $this->customFilter = trim($cutomFilter); $this->groupOptions = $groupOptions; } @@ -43,12 +53,18 @@ class LdapUserBackend extends UserBackend */ protected function selectUsers() { - return $this->conn->select()->setBase($this->baseDn)->from( + $query = $this->conn->select()->setBase($this->baseDn)->from( $this->userClass, array( $this->userNameAttribute ) ); + + if ($this->customFilter) { + $query->addFilter(new Expression($this->customFilter)); + } + + return $query; } /** @@ -88,9 +104,10 @@ class LdapUserBackend extends UserBackend if ($result === null) { throw new AuthenticationException( - 'No objects with objectClass="%s" in DN="%s" found.', + 'No objects with objectClass="%s" in DN="%s" found. (Filter: %s)', $this->userClass, - $this->baseDn + $this->baseDn, + $this->customFilter ?: 'None' ); } diff --git a/library/Icinga/Authentication/UserBackend.php b/library/Icinga/Authentication/UserBackend.php index cb6eb458b..e5a090bb5 100644 --- a/library/Icinga/Authentication/UserBackend.php +++ b/library/Icinga/Authentication/UserBackend.php @@ -103,6 +103,7 @@ abstract class UserBackend implements Countable $backendConfig->get('user_class', 'user'), $backendConfig->get('user_name_attribute', 'sAMAccountName'), $backendConfig->get('base_dn', $resource->getDN()), + $backendConfig->get('filter'), $groupOptions ); break; @@ -130,6 +131,7 @@ abstract class UserBackend implements Countable $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 da9e90a14..d0766ed3c 100644 --- a/modules/setup/application/forms/AdminAccountPage.php +++ b/modules/setup/application/forms/AdminAccountPage.php @@ -272,7 +272,8 @@ class AdminAccountPage extends Form ResourceFactory::createResource(new ConfigObject($this->resourceConfig)), $this->backendConfig['user_class'], $this->backendConfig['user_name_attribute'], - $this->backendConfig['base_dn'] + $this->backendConfig['base_dn'], + $this->backendConfig['filter'] ); } else { throw new LogicException( diff --git a/modules/setup/library/Setup/Steps/AuthenticationStep.php b/modules/setup/library/Setup/Steps/AuthenticationStep.php index 495a42469..9de0c9867 100644 --- a/modules/setup/library/Setup/Steps/AuthenticationStep.php +++ b/modules/setup/library/Setup/Steps/AuthenticationStep.php @@ -126,11 +126,15 @@ class AuthenticationStep extends Step . '' . ($authType === 'ldap' ? ( '' - . '' . t('User Object Class') . '' + . '' . mt('setup', 'User Object Class') . '' . '' . $this->data['backendConfig']['user_class'] . '' . '' . '' - . '' . t('User Name Attribute') . '' + . '' . mt('setup', 'Custom Filter') . '' + . '' . trim($this->data['backendConfig']['filter']) ?: t('None', 'auth.ldap.filter') . '' + . '' + . '' + . '' . mt('setup', 'User Name Attribute') . '' . '' . $this->data['backendConfig']['user_name_attribute'] . '' . '' ) : ($authType === 'external' ? (