Merge branch 'master' into bugfix/pgsql-queries-apply-lower-on-select-columns-10364

This commit is contained in:
Johannes Meyer 2015-11-11 13:37:14 +01:00
commit e58d747d4a
8 changed files with 202 additions and 88 deletions

View File

@ -37,7 +37,16 @@ userpassword: password
dn: cn=Users,ou=groups,dc=icinga,dc=org dn: cn=Users,ou=groups,dc=icinga,dc=org
objectClass: groupOfUniqueNames objectClass: groupOfUniqueNames
cn: Users 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=Jane Smith,ou=people,dc=icinga,dc=org
uniqueMember: cn=John Q. Public,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 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

View File

@ -136,6 +136,16 @@ class Logger
return $this; return $this;
} }
/**
* Return the logging level being used
*
* @return int
*/
public function getLevel()
{
return $this->level;
}
/** /**
* Register the given message as config error * Register the given message as config error
* *

View File

@ -190,24 +190,6 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In
->setFilter($config->filter); ->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 * Initialize this repository's virtual tables
* *
@ -335,6 +317,28 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In
return ((int) $value) >= $bigBang->diff($now)->days; 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 * Validate that the given column is a valid query target and return it or the actual name if it's an alias
* *

View File

@ -72,6 +72,13 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
*/ */
protected $groupMemberAttribute; 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 * 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); if ($this->ambiguousMemberAttribute === null) {
$query->getQuery()->setBase($this->groupBaseDn); if ($this->groupClass === null) {
return $query; 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 * Initialize this repository's conversion rules
* *
* @return array * @return array
*
* @throws ProgrammingError In case either $this->groupClass or $this->groupMemberAttribute
* has not been set yet
*/ */
protected function initializeConversionRules() 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( $rules = array(
'group' => array( 'group' => array(
'created_at' => 'generalized_time', 'created_at' => 'generalized_time',
@ -468,7 +484,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
'last_modified' => 'generalized_time' '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_name'] = 'user_name';
$rules['group_membership']['user'] = 'user_name'; $rules['group_membership']['user'] = 'user_name';
$rules['group']['user_name'] = 'user_name'; $rules['group']['user_name'] = 'user_name';
@ -492,6 +508,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
->select() ->select()
->from($this->userClass, array()) ->from($this->userClass, array())
->where($this->userNameAttribute, $name) ->where($this->userNameAttribute, $name)
->setBase($this->userBaseDn)
->setUsePagedResults(false) ->setUsePagedResults(false)
->fetchDn(); ->fetchDn();
if ($userDn) { if ($userDn) {
@ -502,6 +519,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
->select() ->select()
->from($this->groupClass, array()) ->from($this->groupClass, array())
->where($this->groupNameAttribute, $name) ->where($this->groupNameAttribute, $name)
->setBase($this->groupBaseDn)
->setUsePagedResults(false) ->setUsePagedResults(false)
->fetchDn(); ->fetchDn();
if ($groupDn) { if ($groupDn) {
@ -543,9 +561,12 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
*/ */
public function requireTable($table, RepositoryQuery $query = null) public function requireTable($table, RepositoryQuery $query = null)
{ {
if ($query !== null && $table === 'group' && $this->groupFilter) { if ($query !== null) {
$query->getQuery()->setBase($this->groupBaseDn);
if ($table === 'group' && $this->groupFilter) {
$query->getQuery()->setNativeFilter($this->groupFilter); $query->getQuery()->setNativeFilter($this->groupFilter);
} }
}
return parent::requireTable($table, $query); return parent::requireTable($table, $query);
} }
@ -580,7 +601,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
*/ */
public function getMemberships(User $user) public function getMemberships(User $user)
{ {
if ($this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) { if ($this->isMemberAttributeAmbiguous()) {
$queryValue = $user->getUsername(); $queryValue = $user->getUsername();
} elseif (($queryValue = $user->getAdditional('ldap_dn')) === null) { } elseif (($queryValue = $user->getAdditional('ldap_dn')) === null) {
$userQuery = $this->ds $userQuery = $this->ds

View File

@ -378,14 +378,7 @@ class LdapConnection implements Selectable, Inspectable
} }
$ds = $this->getConnection(); $ds = $this->getConnection();
$results = @ldap_search( $results = $this->ldapSearch($query, array('dn'));
$ds,
$query->getBase() ?: $this->getDn(),
(string) $query,
array('dn'),
0,
0
);
if ($results === false) { if ($results === false) {
if (ldap_errno($ds) !== self::LDAP_NO_SUCH_OBJECT) { 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) public function fetchOne(LdapQuery $query, array $fields = null)
{ {
$row = (array) $this->fetchRow($query, $fields); $row = $this->fetchRow($query, $fields);
return array_shift($row) ?: false; 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( $results = $this->ldapSearch(
$ds, $query,
$query->getBase() ?: $this->rootDn, array_values($fields),
(string) $query, 0,
$unfoldAttribute
? array_unique(array_values($fields))
: array_values($fields),
0, // Attributes and values
$serverSorting && $limit ? $offset + $limit : 0 $serverSorting && $limit ? $offset + $limit : 0
); );
if ($results === false) { if ($results === false) {
@ -808,8 +817,6 @@ class LdapConnection implements Selectable, Inspectable
$limit = $query->getLimit(); $limit = $query->getLimit();
$offset = $query->hasOffset() ? $query->getOffset() : 0; $offset = $query->hasOffset() ? $query->getOffset() : 0;
$queryString = (string) $query;
$base = $query->getBase() ?: $this->rootDn;
if ($fields === null) { if ($fields === null) {
$fields = $query->getColumns(); $fields = $query->getColumns();
@ -853,14 +860,10 @@ class LdapConnection implements Selectable, Inspectable
)); ));
} }
$results = @ldap_search( $results = $this->ldapSearch(
$ds, $query,
$base, array_values($fields),
$queryString, 0,
$unfoldAttribute
? array_unique(array_values($fields))
: array_values($fields),
0, // Attributes and values
$serverSorting && $limit ? $offset + $limit : 0 $serverSorting && $limit ? $offset + $limit : 0
); );
if ($results === false) { if ($results === false) {
@ -870,8 +873,8 @@ class LdapConnection implements Selectable, Inspectable
throw new LdapException( throw new LdapException(
'LDAP query "%s" (base %s) failed. Error: %s', 'LDAP query "%s" (base %s) failed. Error: %s',
$queryString, (string) $query,
$base, $query->getBase() ?: $this->getDn(),
ldap_error($ds) ldap_error($ds)
); );
} elseif (ldap_count_entries($ds, $results) === 0) { } 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 // 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 // the server: https://www.ietf.org/rfc/rfc2696.txt
ldap_control_paged_result($ds, 0, false, $cookie); 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()) { if (! $serverSorting && $query->hasOrder()) {
@ -1164,6 +1168,77 @@ class LdapConnection implements Selectable, Inspectable
return $ds; 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 * Create an LDAP entry
* *

View File

@ -42,15 +42,6 @@ abstract class LdapRepository extends Repository
'groupofuniquenames' => 'groupOfUniqueNames' 'groupofuniquenames' => 'groupOfUniqueNames'
); );
/**
* Object attributes whose value is not distinguished name
*
* @var array
*/
protected $ambiguousAttributes = array(
'posixGroup' => 'memberUid'
);
/** /**
* Create a new LDAP repository object * 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 * Will use the current connection's root DN if $baseDn is not given.
* @param string $attributeName *
* @param string $dn The object DN to check
* @param string $baseDn The base DN to compare the object DN with
* *
* @return bool * @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;
} }
} }

View File

@ -86,9 +86,8 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
*/ */
public function from($target, array $columns = null) public function from($target, array $columns = null)
{ {
$this->query = $this->repository->getDataSource()->select()->from( $this->query = $this->repository->getDataSource()->select();
$this->repository->requireTable($target, $this) $this->query->from($this->repository->requireTable($target, $this));
);
$this->query->columns($this->prepareQueryColumns($target, $columns)); $this->query->columns($this->prepareQueryColumns($target, $columns));
$this->target = $target; $this->target = $target;
return $this; return $this;

View File

@ -203,6 +203,7 @@ class Controller extends ModuleActionController
'sort', // setupSortControl() 'sort', // setupSortControl()
'dir', // setupSortControl() 'dir', // setupSortControl()
'backend', // Framework 'backend', // Framework
'view', // Framework
'_dev' // Framework '_dev' // Framework
); );