Merge branch 'feature/custom-ldap-filter-8365'

resolves #8365
This commit is contained in:
Johannes Meyer 2015-03-11 09:56:31 +01:00
commit 4a5e6bfc7c
7 changed files with 133 additions and 24 deletions

View File

@ -55,7 +55,7 @@ class LdapBackendForm extends Form
'required' => true, 'required' => true,
'label' => $this->translate('Backend Name'), 'label' => $this->translate('Backend Name'),
'description' => $this->translate( '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', 'resource',
array( array(
'required' => true, 'required' => true,
'label' => $this->translate('LDAP Resource'), 'label' => $this->translate('LDAP Connection'),
'description' => $this->translate('The resource to use for authenticating with this provider'), 'description' => $this->translate(
'The LDAP connection to use for authenticating with this provider.'
),
'multiOptions' => false === empty($this->resources) 'multiOptions' => false === empty($this->resources)
? array_combine($this->resources, $this->resources) ? array_combine($this->resources, $this->resources)
: array() : array()
@ -77,10 +79,40 @@ class LdapBackendForm extends Form
array( array(
'required' => true, 'required' => true,
'label' => $this->translate('LDAP User Object Class'), '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' '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( $this->addElement(
'text', 'text',
'user_name_attribute', 'user_name_attribute',
@ -88,7 +120,7 @@ class LdapBackendForm extends Form
'required' => true, 'required' => true,
'label' => $this->translate('LDAP User Name Attribute'), 'label' => $this->translate('LDAP User Name Attribute'),
'description' => $this->translate( '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' 'value' => 'uid'
) )
@ -106,10 +138,10 @@ class LdapBackendForm extends Form
'base_dn', 'base_dn',
array( array(
'required' => false, 'required' => false,
'label' => $this->translate('Base DN'), 'label' => $this->translate('LDAP Base DN'),
'description' => $this->translate( 'description' => $this->translate(
'The path where users can be found on the ldap server. Leave ' . 'The path where users can be found on the LDAP server. Leave ' .
'empty to select all users available on the specified resource.' 'empty to select all users available using the specified connection.'
) )
) )
); );
@ -142,11 +174,17 @@ class LdapBackendForm extends Form
ResourceFactory::createResource($form->getResourceConfig()), ResourceFactory::createResource($form->getResourceConfig()),
$form->getElement('user_class')->getValue(), $form->getElement('user_class')->getValue(),
$form->getElement('user_name_attribute')->getValue(), $form->getElement('user_name_attribute')->getValue(),
$form->getElement('base_dn')->getValue() $form->getElement('base_dn')->getValue(),
$form->getElement('filter')->getValue()
); );
$ldapUserBackend->assertAuthenticationPossible(); $ldapUserBackend->assertAuthenticationPossible();
} catch (AuthenticationException $e) { } catch (AuthenticationException $e) {
$form->addError($e->getMessage()); if (($previous = $e->getPrevious()) !== null) {
$form->addError($previous->getMessage());
} else {
$form->addError($e->getMessage());
}
return false; return false;
} catch (Exception $e) { } catch (Exception $e) {
$form->addError(sprintf($form->translate('Unable to validate authentication: %s'), $e->getMessage())); $form->addError(sprintf($form->translate('Unable to validate authentication: %s'), $e->getMessage()));

View File

@ -9,6 +9,7 @@ use Icinga\Protocol\Ldap\Query;
use Icinga\Protocol\Ldap\Connection; use Icinga\Protocol\Ldap\Connection;
use Icinga\Exception\AuthenticationException; use Icinga\Exception\AuthenticationException;
use Icinga\Protocol\Ldap\Exception as LdapException; use Icinga\Protocol\Ldap\Exception as LdapException;
use Icinga\Protocol\Ldap\Expression;
class LdapUserBackend extends UserBackend class LdapUserBackend extends UserBackend
{ {
@ -25,14 +26,23 @@ class LdapUserBackend extends UserBackend
protected $userNameAttribute; protected $userNameAttribute;
protected $customFilter;
protected $groupOptions; 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->conn = $conn;
$this->baseDn = trim($baseDn) !== '' ? $baseDn : $conn->getDN(); $this->baseDn = trim($baseDn) ?: $conn->getDN();
$this->userClass = $userClass; $this->userClass = $userClass;
$this->userNameAttribute = $userNameAttribute; $this->userNameAttribute = $userNameAttribute;
$this->customFilter = trim($cutomFilter);
$this->groupOptions = $groupOptions; $this->groupOptions = $groupOptions;
} }
@ -43,12 +53,18 @@ class LdapUserBackend extends UserBackend
*/ */
protected function selectUsers() protected function selectUsers()
{ {
return $this->conn->select()->setBase($this->baseDn)->from( $query = $this->conn->select()->setBase($this->baseDn)->from(
$this->userClass, $this->userClass,
array( array(
$this->userNameAttribute $this->userNameAttribute
) )
); );
if ($this->customFilter) {
$query->addFilter(new Expression($this->customFilter));
}
return $query;
} }
/** /**
@ -88,9 +104,10 @@ class LdapUserBackend extends UserBackend
if ($result === null) { if ($result === null) {
throw new AuthenticationException( 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->userClass,
$this->baseDn $this->baseDn,
$this->customFilter ?: 'None'
); );
} }

View File

@ -103,6 +103,7 @@ abstract class UserBackend implements Countable
$backendConfig->get('user_class', 'user'), $backendConfig->get('user_class', 'user'),
$backendConfig->get('user_name_attribute', 'sAMAccountName'), $backendConfig->get('user_name_attribute', 'sAMAccountName'),
$backendConfig->get('base_dn', $resource->getDN()), $backendConfig->get('base_dn', $resource->getDN()),
$backendConfig->get('filter'),
$groupOptions $groupOptions
); );
break; break;
@ -130,6 +131,7 @@ abstract class UserBackend implements Countable
$backendConfig->user_class, $backendConfig->user_class,
$backendConfig->user_name_attribute, $backendConfig->user_name_attribute,
$backendConfig->get('base_dn', $resource->getDN()), $backendConfig->get('base_dn', $resource->getDN()),
$backendConfig->get('filter'),
$groupOptions $groupOptions
); );
break; break;

View File

@ -0,0 +1,30 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Protocol\Ldap;
class Expression
{
protected $value;
public function __construct($value)
{
$this->value = $value;
}
public function setValue($value)
{
$this->value = $value;
return $this;
}
public function getValue()
{
return $this->value;
}
public function __toString()
{
return (string) $this->getValue();
}
}

View File

@ -306,6 +306,19 @@ class Query
return $paginator; return $paginator;
} }
/**
* Add a filter expression to this query
*
* @param Expression $expression
*
* @return Query
*/
public function addFilter(Expression $expression)
{
$this->filters[] = $expression;
return $this;
}
/** /**
* Returns the LDAP filter that will be applied * Returns the LDAP filter that will be applied
* *
@ -318,11 +331,15 @@ class Query
throw new Exception('Object class is mandatory'); throw new Exception('Object class is mandatory');
} }
foreach ($this->filters as $key => $value) { foreach ($this->filters as $key => $value) {
$parts[] = sprintf( if ($value instanceof Expression) {
'%s=%s', $parts[] = (string) $value;
LdapUtils::quoteForSearch($key), } else {
LdapUtils::quoteForSearch($value, true) $parts[] = sprintf(
); '%s=%s',
LdapUtils::quoteForSearch($key),
LdapUtils::quoteForSearch($value, true)
);
}
} }
if (count($parts) > 1) { if (count($parts) > 1) {
return '(&(' . implode(')(', $parts) . '))'; return '(&(' . implode(')(', $parts) . '))';

View File

@ -272,7 +272,8 @@ class AdminAccountPage extends Form
ResourceFactory::createResource(new ConfigObject($this->resourceConfig)), ResourceFactory::createResource(new ConfigObject($this->resourceConfig)),
$this->backendConfig['user_class'], $this->backendConfig['user_class'],
$this->backendConfig['user_name_attribute'], $this->backendConfig['user_name_attribute'],
$this->backendConfig['base_dn'] $this->backendConfig['base_dn'],
$this->backendConfig['filter']
); );
} else { } else {
throw new LogicException( throw new LogicException(

View File

@ -126,11 +126,15 @@ class AuthenticationStep extends Step
. '</tr>' . '</tr>'
. ($authType === 'ldap' ? ( . ($authType === 'ldap' ? (
'<tr>' '<tr>'
. '<td><strong>' . t('User Object Class') . '</strong></td>' . '<td><strong>' . mt('setup', 'User Object Class') . '</strong></td>'
. '<td>' . $this->data['backendConfig']['user_class'] . '</td>' . '<td>' . $this->data['backendConfig']['user_class'] . '</td>'
. '</tr>' . '</tr>'
. '<tr>' . '<tr>'
. '<td><strong>' . t('User Name Attribute') . '</strong></td>' . '<td><strong>' . mt('setup', 'Custom Filter') . '</strong></td>'
. '<td>' . trim($this->data['backendConfig']['filter']) ?: t('None', 'auth.ldap.filter') . '</td>'
. '</tr>'
. '<tr>'
. '<td><strong>' . mt('setup', 'User Name Attribute') . '</strong></td>'
. '<td>' . $this->data['backendConfig']['user_name_attribute'] . '</td>' . '<td>' . $this->data['backendConfig']['user_name_attribute'] . '</td>'
. '</tr>' . '</tr>'
) : ($authType === 'external' ? ( ) : ($authType === 'external' ? (