summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:46:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:46:43 +0000
commit3e02d5aff85babc3ffbfcf52313f2108e313aa23 (patch)
treeb01f3923360c20a6a504aff42d45670c58af3ec5 /library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php
parentInitial commit. (diff)
downloadicingaweb2-upstream.tar.xz
icingaweb2-upstream.zip
Adding upstream version 2.12.1.upstream/2.12.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php')
-rw-r--r--library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php945
1 files changed, 945 insertions, 0 deletions
diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php
new file mode 100644
index 0000000..e78242e
--- /dev/null
+++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php
@@ -0,0 +1,945 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication\UserGroup;
+
+use Exception;
+use Icinga\Authentication\User\UserBackend;
+use Icinga\Authentication\User\LdapUserBackend;
+use Icinga\Application\Logger;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\Inspectable;
+use Icinga\Data\Inspection;
+use Icinga\Exception\AuthenticationException;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Exception\QueryException;
+use Icinga\Protocol\Ldap\LdapException;
+use Icinga\Protocol\Ldap\LdapUtils;
+use Icinga\Repository\LdapRepository;
+use Icinga\Repository\RepositoryQuery;
+use Icinga\User;
+
+class LdapUserGroupBackend extends LdapRepository implements Inspectable, UserGroupBackendInterface
+{
+ /**
+ * The user backend being associated with this user group backend
+ *
+ * @var LdapUserBackend
+ */
+ protected $userBackend;
+
+ /**
+ * The base DN to use for a user query
+ *
+ * @var string
+ */
+ protected $userBaseDn;
+
+ /**
+ * The base DN to use for a group query
+ *
+ * @var string
+ */
+ protected $groupBaseDn;
+
+ /**
+ * The objectClass where look for users
+ *
+ * @var string
+ */
+ protected $userClass;
+
+ /**
+ * The objectClass where look for groups
+ *
+ * @var string
+ */
+ protected $groupClass;
+
+ /**
+ * The attribute name where to find a user's name
+ *
+ * @var string
+ */
+ protected $userNameAttribute;
+
+ /**
+ * The attribute name where to find a group's name
+ *
+ * @var string
+ */
+ protected $groupNameAttribute;
+
+ /**
+ * The attribute name where to find a group's member
+ *
+ * @var string
+ */
+ protected $groupMemberAttribute;
+
+ /**
+ * Whether the attribute name where to find a group's member holds ambiguous values
+ *
+ * @var bool
+ */
+ protected $ambiguousMemberAttribute;
+
+ /**
+ * The custom LDAP filter to apply on a user query
+ *
+ * @var string
+ */
+ protected $userFilter;
+
+ /**
+ * The custom LDAP filter to apply on a group query
+ *
+ * @var string
+ */
+ protected $groupFilter;
+
+ /**
+ * ActiveDirectory nested group on the user?
+ *
+ * @var bool
+ */
+ protected $nestedGroupSearch;
+
+ /**
+ * The domain the backend is responsible for
+ *
+ * @var string
+ */
+ protected $domain;
+
+ /**
+ * The columns which are not permitted to be queried
+ *
+ * @var array
+ */
+ protected $blacklistedQueryColumns = array('group', 'user');
+
+ /**
+ * The search columns being provided
+ *
+ * @var array
+ */
+ protected $searchColumns = array('group', 'user');
+
+ /**
+ * The default sort rules to be applied on a query
+ *
+ * @var array
+ */
+ protected $sortRules = array(
+ 'group_name' => array(
+ 'order' => 'asc'
+ )
+ );
+
+ /**
+ * Set the user backend to be associated with this user group backend
+ *
+ * @param LdapUserBackend $backend
+ *
+ * @return $this
+ */
+ public function setUserBackend(LdapUserBackend $backend)
+ {
+ $this->userBackend = $backend;
+ return $this;
+ }
+
+ /**
+ * Return the user backend being associated with this user group backend
+ *
+ * @return LdapUserBackend
+ */
+ public function getUserBackend()
+ {
+ return $this->userBackend;
+ }
+
+ /**
+ * Set the base DN to use for a user query
+ *
+ * @param string $baseDn
+ *
+ * @return $this
+ */
+ public function setUserBaseDn($baseDn)
+ {
+ if ($baseDn && ($baseDn = trim($baseDn))) {
+ $this->userBaseDn = $baseDn;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the base DN to use for a user query
+ *
+ * @return string
+ */
+ public function getUserBaseDn()
+ {
+ return $this->userBaseDn;
+ }
+
+ /**
+ * Set the base DN to use for a group query
+ *
+ * @param string $baseDn
+ *
+ * @return $this
+ */
+ public function setGroupBaseDn($baseDn)
+ {
+ if ($baseDn && ($baseDn = trim($baseDn))) {
+ $this->groupBaseDn = $baseDn;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the base DN to use for a group query
+ *
+ * @return string
+ */
+ public function getGroupBaseDn()
+ {
+ return $this->groupBaseDn;
+ }
+
+ /**
+ * Set the objectClass where to look for users
+ *
+ * @param string $userClass
+ *
+ * @return $this
+ */
+ public function setUserClass($userClass)
+ {
+ $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 objectClass where to look for groups
+ *
+ * @param string $groupClass
+ *
+ * @return $this
+ */
+ public function setGroupClass($groupClass)
+ {
+ $this->groupClass = $this->getNormedAttribute($groupClass);
+ return $this;
+ }
+
+ /**
+ * Return the objectClass where to look for groups
+ *
+ * @return string
+ */
+ public function getGroupClass()
+ {
+ return $this->groupClass;
+ }
+
+ /**
+ * 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);
+ return $this;
+ }
+
+ /**
+ * Return the attribute name where to find a user's name
+ *
+ * @return string
+ */
+ public function getUserNameAttribute()
+ {
+ return $this->userNameAttribute;
+ }
+
+ /**
+ * Set the attribute name where to find a group's name
+ *
+ * @param string $groupNameAttribute
+ *
+ * @return $this
+ */
+ public function setGroupNameAttribute($groupNameAttribute)
+ {
+ $this->groupNameAttribute = $this->getNormedAttribute($groupNameAttribute);
+ return $this;
+ }
+
+ /**
+ * Return the attribute name where to find a group's name
+ *
+ * @return string
+ */
+ public function getGroupNameAttribute()
+ {
+ return $this->groupNameAttribute;
+ }
+
+ /**
+ * Set the attribute name where to find a group's member
+ *
+ * @param string $groupMemberAttribute
+ *
+ * @return $this
+ */
+ public function setGroupMemberAttribute($groupMemberAttribute)
+ {
+ $this->groupMemberAttribute = $this->getNormedAttribute($groupMemberAttribute);
+ return $this;
+ }
+
+ /**
+ * Return the attribute name where to find a group's member
+ *
+ * @return string
+ */
+ public function getGroupMemberAttribute()
+ {
+ return $this->groupMemberAttribute;
+ }
+
+ /**
+ * Set the custom LDAP filter to apply on a user query
+ *
+ * @param string $filter
+ *
+ * @return $this
+ */
+ public function setUserFilter($filter)
+ {
+ if ($filter && ($filter = trim($filter))) {
+ if ($filter[0] === '(') {
+ $filter = substr($filter, 1, -1);
+ }
+
+ $this->userFilter = $filter;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the custom LDAP filter to apply on a user query
+ *
+ * @return string
+ */
+ public function getUserFilter()
+ {
+ return $this->userFilter;
+ }
+
+ /**
+ * Set the custom LDAP filter to apply on a group query
+ *
+ * @param string $filter
+ *
+ * @return $this
+ */
+ public function setGroupFilter($filter)
+ {
+ if ($filter && ($filter = trim($filter))) {
+ $this->groupFilter = $filter;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the custom LDAP filter to apply on a group query
+ *
+ * @return string
+ */
+ public function getGroupFilter()
+ {
+ return $this->groupFilter;
+ }
+
+ /**
+ * Set nestedGroupSearch for the group query
+ *
+ * @param bool $enable
+ *
+ * @return $this
+ */
+ public function setNestedGroupSearch($enable = true)
+ {
+ $this->nestedGroupSearch = $enable;
+ return $this;
+ }
+
+ /**
+ * Get nestedGroupSearch for the group query
+ *
+ * @return bool
+ */
+ public function getNestedGroupSearch()
+ {
+ return $this->nestedGroupSearch;
+ }
+
+ /**
+ * Get the domain the backend is responsible for
+ *
+ * If the LDAP group backend is linked with a LDAP user backend,
+ * the domain of the user backend will be returned.
+ *
+ * @return string
+ */
+ public function getDomain()
+ {
+ return $this->userBackend !== null ? $this->userBackend->getDomain() : $this->domain;
+ }
+
+ /**
+ * Set the domain the backend is responsible for
+ *
+ * If the LDAP group backend is linked with a LDAP user backend,
+ * the domain of the user backend will be used nonetheless.
+ *
+ * @param string $domain
+ *
+ * @return $this
+ */
+ public function setDomain($domain)
+ {
+ if ($domain && ($domain = trim($domain))) {
+ $this->domain = $domain;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return whether the attribute name where to find a group's member holds ambiguous values
+ *
+ * This tries to detect if the member attribute of groups contain:
+ *
+ * full DN -> distinguished name of another object
+ * other -> ambiguous field referencing the member by userNameAttribute
+ *
+ * @return bool
+ *
+ * @throws ProgrammingError In case either $this->groupClass or $this->groupMemberAttribute
+ * has not been set yet
+ */
+ protected function isMemberAttributeAmbiguous()
+ {
+ if ($this->ambiguousMemberAttribute === null) {
+ if ($this->groupClass === null) {
+ throw new ProgrammingError(
+ 'It is required to set the objectClass where to look for groups first'
+ );
+ } elseif ($this->groupMemberAttribute === null) {
+ throw new ProgrammingError(
+ 'It is required to set a attribute name where to find a group\'s members first'
+ );
+ }
+
+ $sampleValues = $this->ds
+ ->select()
+ ->from($this->groupClass, array($this->groupMemberAttribute))
+ ->where($this->groupMemberAttribute, '*')
+ ->limit(Logger::getInstance()->getLevel() === Logger::DEBUG ? 3 : 1)
+ ->setUnfoldAttribute($this->groupMemberAttribute)
+ ->setBase($this->groupBaseDn)
+ ->fetchAll();
+
+ Logger::debug('Ambiguity query returned %d results', count($sampleValues));
+
+ $i = 0;
+ $sampleValue = null;
+ foreach ($sampleValues as $key => $value) {
+ if ($sampleValue === null) {
+ $sampleValue = $value;
+ }
+
+ Logger::debug('Result %d: %s (%s)', ++$i, $value, $key);
+ }
+
+ if (is_object($sampleValue) && isset($sampleValue->{$this->groupMemberAttribute})) {
+ $this->ambiguousMemberAttribute = ! LdapUtils::isDn($sampleValue->{$this->groupMemberAttribute});
+
+ Logger::debug(
+ 'Ambiguity check came to the conclusion that the member attribute %s ambiguous. Tested sample: %s',
+ $this->ambiguousMemberAttribute ? 'is' : 'is not',
+ $sampleValue->{$this->groupMemberAttribute}
+ );
+ } else {
+ Logger::warning(
+ 'Ambiguity query returned zero or invalid results. Sample value is `%s`',
+ print_r($sampleValue, true)
+ );
+ }
+ }
+
+ return $this->ambiguousMemberAttribute;
+ }
+
+ /**
+ * Initialize this repository's virtual tables
+ *
+ * @return array
+ *
+ * @throws ProgrammingError In case $this->groupClass has not been set yet
+ */
+ protected function initializeVirtualTables()
+ {
+ if ($this->groupClass === null) {
+ throw new ProgrammingError('It is required to set the object class where to find groups first');
+ }
+
+ return array(
+ 'group' => $this->groupClass,
+ 'group_membership' => $this->groupClass
+ );
+ }
+
+ /**
+ * Initialize this repository's query columns
+ *
+ * @return array
+ *
+ * @throws ProgrammingError In case either $this->groupNameAttribute or
+ * $this->groupMemberAttribute has not been set yet
+ */
+ protected function initializeQueryColumns()
+ {
+ if ($this->groupNameAttribute === null) {
+ throw new ProgrammingError('It is required to set a attribute name where to find a group\'s name first');
+ }
+ if ($this->groupMemberAttribute === null) {
+ throw new ProgrammingError('It is required to set a attribute name where to find a group\'s members first');
+ }
+
+ if ($this->ds->getCapabilities()->isActiveDirectory()) {
+ $createdAtAttribute = 'whenCreated';
+ $lastModifiedAttribute = 'whenChanged';
+ } else {
+ $createdAtAttribute = 'createTimestamp';
+ $lastModifiedAttribute = 'modifyTimestamp';
+ }
+
+ $columns = array(
+ 'group' => $this->groupNameAttribute,
+ 'group_name' => $this->groupNameAttribute,
+ 'user' => $this->groupMemberAttribute,
+ 'user_name' => $this->groupMemberAttribute,
+ 'created_at' => $createdAtAttribute,
+ 'last_modified' => $lastModifiedAttribute
+ );
+ return array('group' => $columns, 'group_membership' => $columns);
+ }
+
+ /**
+ * Initialize this repository's filter columns
+ *
+ * @return array
+ */
+ protected function initializeFilterColumns()
+ {
+ return array(
+ t('Username') => 'user_name',
+ t('User Group') => 'group_name',
+ t('Created At') => 'created_at',
+ t('Last modified') => 'last_modified'
+ );
+ }
+
+ /**
+ * Initialize this repository's conversion rules
+ *
+ * @return array
+ */
+ protected function initializeConversionRules()
+ {
+ $rules = array(
+ 'group' => array(
+ 'created_at' => 'generalized_time',
+ 'last_modified' => 'generalized_time'
+ ),
+ 'group_membership' => array(
+ 'created_at' => 'generalized_time',
+ 'last_modified' => 'generalized_time'
+ )
+ );
+ if (! $this->isMemberAttributeAmbiguous()) {
+ $rules['group_membership']['user_name'] = 'user_name';
+ $rules['group_membership']['user'] = 'user_name';
+ $rules['group']['user_name'] = 'user_name';
+ $rules['group']['user'] = 'user_name';
+ }
+
+ return $rules;
+ }
+
+ /**
+ * Return the distinguished name for the given uid or gid
+ *
+ * @param string $name
+ *
+ * @return string
+ */
+ protected function persistUserName($name)
+ {
+ try {
+ $userDn = $this->ds
+ ->select()
+ ->from($this->userClass, array())
+ ->where($this->userNameAttribute, $name)
+ ->setBase($this->userBaseDn)
+ ->setUsePagedResults(false)
+ ->fetchDn();
+ if ($userDn) {
+ return $userDn;
+ }
+
+ $groupDn = $this->ds
+ ->select()
+ ->from($this->groupClass, array())
+ ->where($this->groupNameAttribute, $name)
+ ->setBase($this->groupBaseDn)
+ ->setUsePagedResults(false)
+ ->fetchDn();
+ if ($groupDn) {
+ return $groupDn;
+ }
+ } catch (LdapException $_) {
+ // pass
+ }
+
+ Logger::debug('Unable to persist uid or gid "%s" in repository "%s". No DN found.', $name, $this->getName());
+ return $name;
+ }
+
+ /**
+ * Return the uid for the given distinguished name
+ *
+ * @param string $username
+ *
+ * @return string
+ */
+ protected function retrieveUserName($dn)
+ {
+ return $this->ds
+ ->select()
+ ->from('*', array($this->userNameAttribute))
+ ->setUnfoldAttribute($this->userNameAttribute)
+ ->setBase($dn)
+ ->fetchOne();
+ }
+
+ /**
+ * Validate that the requested table exists
+ *
+ * @param string $table The table to validate
+ * @param RepositoryQuery $query An optional query to pass as context
+ *
+ * @return string
+ *
+ * @throws ProgrammingError In case the given table does not exist
+ */
+ public function requireTable($table, RepositoryQuery $query = null)
+ {
+ if ($query !== null) {
+ $query->getQuery()->setBase($this->groupBaseDn);
+ if ($table === 'group' && $this->groupFilter) {
+ $query->getQuery()->setNativeFilter($this->groupFilter);
+ }
+ }
+
+ return parent::requireTable($table, $query);
+ }
+
+ /**
+ * Validate that the given column is a valid query target and return it or the actual name if it's an alias
+ *
+ * @param string $table The table where to look for the column or alias
+ * @param string $name The name or alias of the column to validate
+ * @param RepositoryQuery $query An optional query to pass as context
+ *
+ * @return string The given column's name
+ *
+ * @throws QueryException In case the given column is not a valid query column
+ */
+ public function requireQueryColumn($table, $name, RepositoryQuery $query = null)
+ {
+ $column = parent::requireQueryColumn($table, $name, $query);
+ if (($name === 'user_name' || $name === 'group_name') && $query !== null) {
+ $query->getQuery()->setUnfoldAttribute($name);
+ }
+
+ return $column;
+ }
+
+ /**
+ * Return the groups the given user is a member of
+ *
+ * @param User $user
+ *
+ * @return array
+ */
+ public function getMemberships(User $user)
+ {
+ $domain = $this->getDomain();
+
+ if ($domain !== null) {
+ if (! $user->hasDomain() || strtolower($user->getDomain()) !== strtolower($domain)) {
+ return array();
+ }
+
+ $username = $user->getLocalUsername();
+ } else {
+ $username = $user->getUsername();
+ }
+
+ if ($this->isMemberAttributeAmbiguous()) {
+ $queryValue = $username;
+ } elseif (($queryValue = $user->getAdditional('ldap_dn')) === null) {
+ $userQuery = $this->ds
+ ->select()
+ ->from($this->userClass)
+ ->where($this->userNameAttribute, $username)
+ ->setBase($this->userBaseDn)
+ ->setUsePagedResults(false);
+ if ($this->userFilter) {
+ $userQuery->setNativeFilter($this->userFilter);
+ }
+
+ if (($queryValue = $userQuery->fetchDn()) === null) {
+ return array();
+ }
+ }
+
+ if ($this->nestedGroupSearch) {
+ $groupMemberAttribute = $this->groupMemberAttribute . ':1.2.840.113556.1.4.1941:';
+ } else {
+ $groupMemberAttribute = $this->groupMemberAttribute;
+ }
+
+ $groupQuery = $this->ds
+ ->select()
+ ->from($this->groupClass, array($this->groupNameAttribute))
+ ->setUnfoldAttribute($this->groupNameAttribute)
+ ->where($groupMemberAttribute, $queryValue)
+ ->setBase($this->groupBaseDn);
+ if ($this->groupFilter) {
+ $groupQuery->setNativeFilter($this->groupFilter);
+ }
+
+ $groups = array();
+ foreach ($groupQuery as $row) {
+ $groups[] = $row->{$this->groupNameAttribute};
+ if ($domain !== null) {
+ $groups[] = $row->{$this->groupNameAttribute} . "@$domain";
+ }
+ }
+
+ return $groups;
+ }
+
+ /**
+ * Return the name of the backend that is providing the given user
+ *
+ * @param string $username Unused
+ *
+ * @return null|string The name of the backend or null in case this information is not available
+ */
+ public function getUserBackendName($username)
+ {
+ $userBackend = $this->getUserBackend();
+ if ($userBackend !== null) {
+ return $userBackend->getName();
+ }
+ }
+
+ /**
+ * Apply the given configuration on this backend
+ *
+ * @param ConfigObject $config
+ *
+ * @return $this
+ *
+ * @throws ConfigurationError In case a linked user backend does not exist or is invalid
+ */
+ public function setConfig(ConfigObject $config)
+ {
+ if ($config->backend === 'ldap') {
+ $defaults = $this->getOpenLdapDefaults();
+ } elseif ($config->backend === 'msldap') {
+ $defaults = $this->getActiveDirectoryDefaults();
+ } else {
+ $defaults = new ConfigObject();
+ }
+
+ if ($config->user_backend && $config->user_backend !== 'none') {
+ $userBackend = UserBackend::create($config->user_backend);
+ if (! $userBackend instanceof LdapUserBackend) {
+ throw new ConfigurationError('User backend "%s" is not of type LDAP', $config->user_backend);
+ }
+
+ if ($this->ds->getHostname() !== $userBackend->getDataSource()->getHostname()
+ || $this->ds->getPort() !== $userBackend->getDataSource()->getPort()
+ ) {
+ // TODO(jom): Elaborate whether it makes sense to link directories on different hosts
+ throw new ConfigurationError(
+ 'It is required that a linked user backend refers to the '
+ . 'same directory as it\'s user group backend counterpart'
+ );
+ }
+
+ $this->setUserBackend($userBackend);
+ $defaults->merge(array(
+ 'user_base_dn' => $userBackend->getBaseDn(),
+ 'user_class' => $userBackend->getUserClass(),
+ 'user_name_attribute' => $userBackend->getUserNameAttribute(),
+ 'user_filter' => $userBackend->getFilter(),
+ 'domain' => $userBackend->getDomain()
+ ));
+ }
+
+ return $this
+ ->setGroupBaseDn($config->base_dn)
+ ->setUserBaseDn($config->get('user_base_dn', $defaults->get('user_base_dn', $this->getGroupBaseDn())))
+ ->setGroupClass($config->get('group_class', $defaults->group_class))
+ ->setUserClass($config->get('user_class', $defaults->user_class))
+ ->setGroupNameAttribute($config->get('group_name_attribute', $defaults->group_name_attribute))
+ ->setUserNameAttribute($config->get('user_name_attribute', $defaults->user_name_attribute))
+ ->setGroupMemberAttribute($config->get('group_member_attribute', $defaults->group_member_attribute))
+ ->setGroupFilter($config->group_filter)
+ ->setUserFilter($config->user_filter)
+ ->setNestedGroupSearch((bool) $config->get('nested_group_search', $defaults->nested_group_search))
+ ->setDomain($defaults->get('domain', $config->domain));
+ }
+
+ /**
+ * Return the configuration defaults for an OpenLDAP environment
+ *
+ * @return ConfigObject
+ */
+ public function getOpenLdapDefaults()
+ {
+ return new ConfigObject(array(
+ 'group_class' => 'group',
+ 'user_class' => 'inetOrgPerson',
+ 'group_name_attribute' => 'gid',
+ 'user_name_attribute' => 'uid',
+ 'group_member_attribute' => 'member',
+ 'nested_group_search' => '0'
+ ));
+ }
+
+ /**
+ * Return the configuration defaults for an ActiveDirectory environment
+ *
+ * @return ConfigObject
+ */
+ public function getActiveDirectoryDefaults()
+ {
+ return new ConfigObject(array(
+ 'group_class' => 'group',
+ 'user_class' => 'user',
+ 'group_name_attribute' => 'sAMAccountName',
+ 'user_name_attribute' => 'sAMAccountName',
+ 'group_member_attribute' => 'member',
+ 'nested_group_search' => '0'
+ ));
+ }
+
+ /**
+ * Inspect if this LDAP User Group Backend is working as expected by probing the backend
+ *
+ * Try to bind to the backend and fetch a single group to check if:
+ * <ul>
+ * <li>Connection credentials are correct and the bind is possible</li>
+ * <li>At least one group exists</li>
+ * <li>The specified groupClass has the property specified by groupNameAttribute</li>
+ * </ul>
+ *
+ * @return Inspection Inspection result
+ */
+ public function inspect()
+ {
+ $result = new Inspection('Ldap User Group Backend');
+
+ // inspect the used connection to get more diagnostic info in case the connection is not working
+ $result->write($this->ds->inspect());
+
+ try {
+ try {
+ $groupQuery = $this->ds
+ ->select()
+ ->from($this->groupClass, array($this->groupNameAttribute))
+ ->setBase($this->groupBaseDn);
+
+ if ($this->groupFilter) {
+ $groupQuery->setNativeFilter($this->groupFilter);
+ }
+
+ $res = $groupQuery->fetchRow();
+ } catch (LdapException $e) {
+ throw new AuthenticationException('Connection not possible', $e);
+ }
+
+ $result->write('Searching for: ' . sprintf(
+ 'objectClass "%s" in DN "%s" (Filter: %s)',
+ $this->groupClass,
+ $this->groupBaseDn ?: $this->ds->getDn(),
+ $this->groupFilter ?: 'None'
+ ));
+
+ if ($res === false) {
+ throw new AuthenticationException('Error, no groups found in backend');
+ }
+
+ $result->write(sprintf('%d groups found in backend', $groupQuery->count()));
+
+ if (! isset($res->{$this->groupNameAttribute})) {
+ throw new AuthenticationException(
+ 'GroupNameAttribute "%s" not existing in objectClass "%s"',
+ $this->groupNameAttribute,
+ $this->groupClass
+ );
+ }
+ } catch (AuthenticationException $e) {
+ if (($previous = $e->getPrevious()) !== null) {
+ $result->error($previous->getMessage());
+ } else {
+ $result->error($e->getMessage());
+ }
+ } catch (Exception $e) {
+ $result->error(sprintf('Unable to validate backend: %s', $e->getMessage()));
+ }
+
+ return $result;
+ }
+}