Merge branch 'master' into bugfix/pgsql-queries-apply-lower-on-select-columns-10364
This commit is contained in:
commit
e58d747d4a
|
@ -37,7 +37,16 @@ userpassword: password
|
|||
dn: cn=Users,ou=groups,dc=icinga,dc=org
|
||||
objectClass: groupOfUniqueNames
|
||||
cn: Users
|
||||
uniqueMember: cn=Jon Doe,ou=people,dc=icinga,dc=org
|
||||
uniqueMember: cn=John Doe,ou=people,dc=icinga,dc=org
|
||||
uniqueMember: cn=Jane Smith,ou=people,dc=icinga,dc=org
|
||||
uniqueMember: cn=John Q. Public,ou=people,dc=icinga,dc=org
|
||||
uniqueMember: cn=Richard Roe,ou=people,dc=icinga,dc=org
|
||||
|
||||
dn: cn=PosixUsers,ou=groups,dc=icinga,dc=org
|
||||
objectClass: posixGroup
|
||||
cn: PosixUsers
|
||||
gidNumber: 2001
|
||||
memberUid: jdoe
|
||||
memberUid: jsmith
|
||||
memberUid: jqpublic
|
||||
memberUid: rroe
|
||||
|
|
|
@ -136,6 +136,16 @@ class Logger
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the logging level being used
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLevel()
|
||||
{
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given message as config error
|
||||
*
|
||||
|
|
|
@ -190,24 +190,6 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In
|
|||
->setFilter($config->filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$query = parent::select($columns);
|
||||
$query->getQuery()->setBase($this->baseDn);
|
||||
if ($this->filter) {
|
||||
$query->getQuery()->setNativeFilter($this->filter);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this repository's virtual tables
|
||||
*
|
||||
|
@ -335,6 +317,28 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In
|
|||
return ((int) $value) >= $bigBang->diff($now)->days;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->baseDn);
|
||||
if ($this->filter) {
|
||||
$query->getQuery()->setNativeFilter($this->filter);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
|
|
|
@ -72,6 +72,13 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
*/
|
||||
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
|
||||
*
|
||||
|
@ -358,17 +365,36 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
}
|
||||
|
||||
/**
|
||||
* Return a new query for the given columns
|
||||
* Return whether the attribute name where to find a group's member holds ambiguous values
|
||||
*
|
||||
* @param array $columns The desired columns, if null all columns will be queried
|
||||
* @return bool
|
||||
*
|
||||
* @return RepositoryQuery
|
||||
* @throws ProgrammingError In case either $this->groupClass or $this->groupMemberAttribute
|
||||
* has not been set yet
|
||||
*/
|
||||
public function select(array $columns = null)
|
||||
protected function isMemberAttributeAmbiguous()
|
||||
{
|
||||
$query = parent::select($columns);
|
||||
$query->getQuery()->setBase($this->groupBaseDn);
|
||||
return $query;
|
||||
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'
|
||||
);
|
||||
}
|
||||
|
||||
$sampleValue = $this->ds
|
||||
->select()
|
||||
->from($this->groupClass, array($this->groupMemberAttribute))
|
||||
->setUnfoldAttribute($this->groupMemberAttribute)
|
||||
->setBase($this->groupBaseDn)
|
||||
->fetchOne();
|
||||
$this->ambiguousMemberAttribute = !$this->isRelatedDn($sampleValue);
|
||||
}
|
||||
|
||||
return $this->ambiguousMemberAttribute;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -445,19 +471,9 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
* Initialize this repository's conversion rules
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws ProgrammingError In case either $this->groupClass or $this->groupMemberAttribute
|
||||
* has not been set yet
|
||||
*/
|
||||
protected function initializeConversionRules()
|
||||
{
|
||||
if ($this->groupClass === null) {
|
||||
throw new ProgrammingError('It is required to set the objectClass where to look for groups first');
|
||||
}
|
||||
if ($this->groupMemberAttribute === null) {
|
||||
throw new ProgrammingError('It is required to set a attribute name where to find a group\'s members first');
|
||||
}
|
||||
|
||||
$rules = array(
|
||||
'group' => array(
|
||||
'created_at' => 'generalized_time',
|
||||
|
@ -468,7 +484,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
'last_modified' => 'generalized_time'
|
||||
)
|
||||
);
|
||||
if (! $this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) {
|
||||
if (! $this->isMemberAttributeAmbiguous()) {
|
||||
$rules['group_membership']['user_name'] = 'user_name';
|
||||
$rules['group_membership']['user'] = 'user_name';
|
||||
$rules['group']['user_name'] = 'user_name';
|
||||
|
@ -492,6 +508,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
->select()
|
||||
->from($this->userClass, array())
|
||||
->where($this->userNameAttribute, $name)
|
||||
->setBase($this->userBaseDn)
|
||||
->setUsePagedResults(false)
|
||||
->fetchDn();
|
||||
if ($userDn) {
|
||||
|
@ -502,6 +519,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
->select()
|
||||
->from($this->groupClass, array())
|
||||
->where($this->groupNameAttribute, $name)
|
||||
->setBase($this->groupBaseDn)
|
||||
->setUsePagedResults(false)
|
||||
->fetchDn();
|
||||
if ($groupDn) {
|
||||
|
@ -543,8 +561,11 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
*/
|
||||
public function requireTable($table, RepositoryQuery $query = null)
|
||||
{
|
||||
if ($query !== null && $table === 'group' && $this->groupFilter) {
|
||||
$query->getQuery()->setNativeFilter($this->groupFilter);
|
||||
if ($query !== null) {
|
||||
$query->getQuery()->setBase($this->groupBaseDn);
|
||||
if ($table === 'group' && $this->groupFilter) {
|
||||
$query->getQuery()->setNativeFilter($this->groupFilter);
|
||||
}
|
||||
}
|
||||
|
||||
return parent::requireTable($table, $query);
|
||||
|
@ -580,7 +601,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
*/
|
||||
public function getMemberships(User $user)
|
||||
{
|
||||
if ($this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) {
|
||||
if ($this->isMemberAttributeAmbiguous()) {
|
||||
$queryValue = $user->getUsername();
|
||||
} elseif (($queryValue = $user->getAdditional('ldap_dn')) === null) {
|
||||
$userQuery = $this->ds
|
||||
|
|
|
@ -378,14 +378,7 @@ class LdapConnection implements Selectable, Inspectable
|
|||
}
|
||||
|
||||
$ds = $this->getConnection();
|
||||
$results = @ldap_search(
|
||||
$ds,
|
||||
$query->getBase() ?: $this->getDn(),
|
||||
(string) $query,
|
||||
array('dn'),
|
||||
0,
|
||||
0
|
||||
);
|
||||
$results = $this->ldapSearch($query, array('dn'));
|
||||
|
||||
if ($results === false) {
|
||||
if (ldap_errno($ds) !== self::LDAP_NO_SUCH_OBJECT) {
|
||||
|
@ -483,8 +476,28 @@ class LdapConnection implements Selectable, Inspectable
|
|||
*/
|
||||
public function fetchOne(LdapQuery $query, array $fields = null)
|
||||
{
|
||||
$row = (array) $this->fetchRow($query, $fields);
|
||||
return array_shift($row) ?: false;
|
||||
$row = $this->fetchRow($query, $fields);
|
||||
if ($row === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$values = get_object_vars($row);
|
||||
if (empty($values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($fields === null) {
|
||||
// Fetch the desired columns from the query if not explicitly overriden in the method's parameter
|
||||
$fields = $query->getColumns();
|
||||
}
|
||||
|
||||
if (empty($fields)) {
|
||||
// The desired columns may be empty independently whether provided by the query or the method's parameter
|
||||
return array_shift($values);
|
||||
}
|
||||
|
||||
$alias = key($fields);
|
||||
return $values[is_string($alias) ? $alias : $fields[$alias]];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -712,14 +725,10 @@ class LdapConnection implements Selectable, Inspectable
|
|||
}
|
||||
}
|
||||
|
||||
$results = @ldap_search(
|
||||
$ds,
|
||||
$query->getBase() ?: $this->rootDn,
|
||||
(string) $query,
|
||||
$unfoldAttribute
|
||||
? array_unique(array_values($fields))
|
||||
: array_values($fields),
|
||||
0, // Attributes and values
|
||||
$results = $this->ldapSearch(
|
||||
$query,
|
||||
array_values($fields),
|
||||
0,
|
||||
$serverSorting && $limit ? $offset + $limit : 0
|
||||
);
|
||||
if ($results === false) {
|
||||
|
@ -808,8 +817,6 @@ class LdapConnection implements Selectable, Inspectable
|
|||
|
||||
$limit = $query->getLimit();
|
||||
$offset = $query->hasOffset() ? $query->getOffset() : 0;
|
||||
$queryString = (string) $query;
|
||||
$base = $query->getBase() ?: $this->rootDn;
|
||||
|
||||
if ($fields === null) {
|
||||
$fields = $query->getColumns();
|
||||
|
@ -853,14 +860,10 @@ class LdapConnection implements Selectable, Inspectable
|
|||
));
|
||||
}
|
||||
|
||||
$results = @ldap_search(
|
||||
$ds,
|
||||
$base,
|
||||
$queryString,
|
||||
$unfoldAttribute
|
||||
? array_unique(array_values($fields))
|
||||
: array_values($fields),
|
||||
0, // Attributes and values
|
||||
$results = $this->ldapSearch(
|
||||
$query,
|
||||
array_values($fields),
|
||||
0,
|
||||
$serverSorting && $limit ? $offset + $limit : 0
|
||||
);
|
||||
if ($results === false) {
|
||||
|
@ -870,8 +873,8 @@ class LdapConnection implements Selectable, Inspectable
|
|||
|
||||
throw new LdapException(
|
||||
'LDAP query "%s" (base %s) failed. Error: %s',
|
||||
$queryString,
|
||||
$base,
|
||||
(string) $query,
|
||||
$query->getBase() ?: $this->getDn(),
|
||||
ldap_error($ds)
|
||||
);
|
||||
} elseif (ldap_count_entries($ds, $results) === 0) {
|
||||
|
@ -950,7 +953,8 @@ class LdapConnection implements Selectable, Inspectable
|
|||
// pagedResultsControl with the size set to zero (0) and the cookie set to the last cookie returned by
|
||||
// the server: https://www.ietf.org/rfc/rfc2696.txt
|
||||
ldap_control_paged_result($ds, 0, false, $cookie);
|
||||
ldap_search($ds, $base, $queryString); // Returns no entries, due to the page size
|
||||
// Returns no entries, due to the page size
|
||||
ldap_search($ds, $query->getBase() ?: $this->getDn(), (string) $query);
|
||||
}
|
||||
|
||||
if (! $serverSorting && $query->hasOrder()) {
|
||||
|
@ -1164,6 +1168,77 @@ class LdapConnection implements Selectable, Inspectable
|
|||
return $ds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a LDAP search and return the result
|
||||
*
|
||||
* @param LdapQuery $query
|
||||
* @param array $attributes An array of the required attributes
|
||||
* @param int $attrsonly Should be set to 1 if only attribute types are wanted
|
||||
* @param int $sizelimit Enables you to limit the count of entries fetched
|
||||
* @param int $timelimit Sets the number of seconds how long is spend on the search
|
||||
* @param int $deref
|
||||
*
|
||||
* @return resource|bool A search result identifier or false on error
|
||||
*/
|
||||
public function ldapSearch(
|
||||
LdapQuery $query,
|
||||
array $attributes = null,
|
||||
$attrsonly = 0,
|
||||
$sizelimit = 0,
|
||||
$timelimit = 0,
|
||||
$deref = LDAP_DEREF_NEVER
|
||||
) {
|
||||
$queryString = (string) $query;
|
||||
$baseDn = $query->getBase() ?: $this->getDn();
|
||||
|
||||
if (Logger::getInstance()->getLevel() === Logger::DEBUG) {
|
||||
// We're checking the level by ourself to avoid rendering the ldapsearch commandline for nothing
|
||||
$starttlsParam = $this->encryption === static::STARTTLS ? ' -ZZ' : '';
|
||||
$ldapUrl = ($this->encryption === static::LDAPS ? 'ldaps://' : 'ldap://')
|
||||
. $this->hostname
|
||||
. ($this->port ? ':' . $this->port : '');
|
||||
|
||||
if ($this->bound) {
|
||||
$bindParams = ' -D "' . $this->bindDn . '"' . ($this->bindPw ? ' -w "' . $this->bindPw . '"' : '');
|
||||
}
|
||||
|
||||
if ($deref === LDAP_DEREF_NEVER) {
|
||||
$derefName = 'never';
|
||||
} elseif ($deref === LDAP_DEREF_ALWAYS) {
|
||||
$derefName = 'always';
|
||||
} elseif ($deref === LDAP_DEREF_SEARCHING) {
|
||||
$derefName = 'search';
|
||||
} else { // $deref === LDAP_DEREF_FINDING
|
||||
$derefName = 'find';
|
||||
}
|
||||
|
||||
Logger::debug("Issueing LDAP search. Use '%s' to reproduce.", sprintf(
|
||||
'ldapsearch -P 3%s -H "%s"%s -b "%s" -s "sub" -z %u -l %u -a "%s"%s%s%s',
|
||||
$starttlsParam,
|
||||
$ldapUrl,
|
||||
$bindParams,
|
||||
$baseDn,
|
||||
$sizelimit,
|
||||
$timelimit,
|
||||
$derefName,
|
||||
$attrsonly ? ' -A' : '',
|
||||
$queryString ? ' "' . $queryString . '"' : '',
|
||||
$attributes ? ' "' . join('" "', $attributes) . '"' : ''
|
||||
));
|
||||
}
|
||||
|
||||
return @ldap_search(
|
||||
$this->getConnection(),
|
||||
$baseDn,
|
||||
$queryString,
|
||||
$attributes,
|
||||
$attrsonly,
|
||||
$sizelimit,
|
||||
$timelimit,
|
||||
$deref
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an LDAP entry
|
||||
*
|
||||
|
|
|
@ -42,15 +42,6 @@ abstract class LdapRepository extends Repository
|
|||
'groupofuniquenames' => 'groupOfUniqueNames'
|
||||
);
|
||||
|
||||
/**
|
||||
* Object attributes whose value is not distinguished name
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $ambiguousAttributes = array(
|
||||
'posixGroup' => 'memberUid'
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a new LDAP repository object
|
||||
*
|
||||
|
@ -79,15 +70,19 @@ abstract class LdapRepository extends Repository
|
|||
}
|
||||
|
||||
/**
|
||||
* Return whether the given object attribute's value is not a distinguished name
|
||||
* Return whether the given object DN is related to the given base DN
|
||||
*
|
||||
* @param string $objectClass
|
||||
* @param string $attributeName
|
||||
* Will use the current connection's root DN if $baseDn is not given.
|
||||
*
|
||||
* @param string $dn The object DN to check
|
||||
* @param string $baseDn The base DN to compare the object DN with
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAmbiguous($objectClass, $attributeName)
|
||||
protected function isRelatedDn($dn, $baseDn = null)
|
||||
{
|
||||
return isset($this->ambiguousAttributes[$objectClass][$attributeName]);
|
||||
$normalizedDn = strtolower(join(',', array_map('trim', explode(',', $dn))));
|
||||
$normalizedBaseDn = strtolower(join(',', array_map('trim', explode(',', $baseDn ?: $this->ds->getDn()))));
|
||||
return strpos($normalizedDn, $normalizedBaseDn) !== false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,9 +86,8 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
|||
*/
|
||||
public function from($target, array $columns = null)
|
||||
{
|
||||
$this->query = $this->repository->getDataSource()->select()->from(
|
||||
$this->repository->requireTable($target, $this)
|
||||
);
|
||||
$this->query = $this->repository->getDataSource()->select();
|
||||
$this->query->from($this->repository->requireTable($target, $this));
|
||||
$this->query->columns($this->prepareQueryColumns($target, $columns));
|
||||
$this->target = $target;
|
||||
return $this;
|
||||
|
|
|
@ -203,6 +203,7 @@ class Controller extends ModuleActionController
|
|||
'sort', // setupSortControl()
|
||||
'dir', // setupSortControl()
|
||||
'backend', // Framework
|
||||
'view', // Framework
|
||||
'_dev' // Framework
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in New Issue