icingaweb2/library/Icinga/Protocol/Ldap/LdapQuery.php

362 lines
8.8 KiB
PHP

<?php
/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
namespace Icinga\Protocol\Ldap;
use Icinga\Data\Filter\Filter;
use LogicException;
use Icinga\Data\SimpleQuery;
/**
* LDAP query class
*/
class LdapQuery extends SimpleQuery
{
/**
* The base dn being used for this query
*
* @var string
*/
protected $base;
/**
* Whether this query is permitted to utilize paged results
*
* @var bool
*/
protected $usePagedResults;
/**
* The name of the attribute used to unfold the result
*
* @var string
*/
protected $unfoldAttribute;
/**
* This query's native LDAP filter
*
* @var string
*/
protected $nativeFilter;
/**
* Only fetch the entry at the base of the search
*/
const SCOPE_BASE = 'base';
/**
* Fetch entries one below the base DN
*/
const SCOPE_ONE = 'one';
/**
* Fetch all entries below the base DN
*/
const SCOPE_SUB = 'sub';
/**
* All available scopes
*
* @var array
*/
public static $scopes = array(
LdapQuery::SCOPE_BASE,
LdapQuery::SCOPE_ONE,
LdapQuery::SCOPE_SUB
);
/**
* LDAP search scope (default: SCOPE_SUB)
*
* @var string
*/
protected $scope = LdapQuery::SCOPE_SUB;
/**
* Initialize this query
*/
protected function init()
{
$this->usePagedResults = false;
}
/**
* Set the base dn to be used for this query
*
* @param string $base
*
* @return $this
*/
public function setBase($base)
{
$this->base = $base;
return $this;
}
/**
* Return the base dn being used for this query
*
* @return string
*/
public function getBase()
{
return $this->base;
}
/**
* Set whether this query is permitted to utilize paged results
*
* @param bool $state
*
* @return $this
*/
public function setUsePagedResults($state = true)
{
$this->usePagedResults = (bool) $state;
return $this;
}
/**
* Return whether this query is permitted to utilize paged results
*
* @return bool
*/
public function getUsePagedResults()
{
return $this->usePagedResults;
}
/**
* Set the attribute to be used to unfold the result
*
* @param string $attributeName
*
* @return $this
*/
public function setUnfoldAttribute($attributeName)
{
$this->unfoldAttribute = $attributeName;
return $this;
}
/**
* Return the attribute to use to unfold the result
*
* @return string
*/
public function getUnfoldAttribute()
{
return $this->unfoldAttribute;
}
/**
* Set this query's native LDAP filter
*
* @param string $filter
*
* @return $this
*/
public function setNativeFilter($filter)
{
$this->nativeFilter = $filter;
return $this;
}
/**
* Return this query's native LDAP filter
*
* @return string
*/
public function getNativeFilter()
{
return $this->nativeFilter;
}
/**
* Choose an objectClass and the columns you are interested in
*
* {@inheritdoc} This creates an objectClass filter.
*/
public function from($target, array $fields = null)
{
$this->where('objectClass', $target);
return parent::from($target, $fields);
}
public function where($condition, $value = null)
{
$this->addFilter(Filter::expression($condition, '=', $value));
return $this;
}
public function addFilter(Filter $filter)
{
$this->makeCaseInsensitive($filter);
return parent::addFilter($filter);
}
public function setFilter(Filter $filter)
{
$this->makeCaseInsensitive($filter);
return parent::setFilter($filter);
}
protected function makeCaseInsensitive(Filter $filter)
{
if ($filter->isExpression()) {
/** @var \Icinga\Data\Filter\FilterExpression $filter */
$filter->setCaseSensitive(false);
} else {
/** @var \Icinga\Data\Filter\FilterChain $filter */
foreach ($filter->filters() as $subFilter) {
$this->makeCaseInsensitive($subFilter);
}
}
}
public function compare($a, $b, $orderIndex = 0)
{
if (array_key_exists($orderIndex, $this->order)) {
$column = $this->order[$orderIndex][0];
$direction = $this->order[$orderIndex][1];
$flippedColumns = $this->flippedColumns ?: array_flip($this->columns);
if (array_key_exists($column, $flippedColumns) && is_string($flippedColumns[$column])) {
$column = $flippedColumns[$column];
}
if (is_array($a->$column)) {
// rfc2891 states: If a sort key is a multi-valued attribute, and an entry happens to
// have multiple values for that attribute and no other controls are
// present that affect the sorting order, then the server SHOULD use the
// least value (according to the ORDERING rule for that attribute).
$a = clone $a;
$a->$column = array_reduce($a->$column, function ($carry, $item) use ($direction) {
$result = $carry === null ? 0 : strcmp($item, $carry);
return ($direction === self::SORT_ASC ? $result : $result * -1) < 1 ? $item : $carry;
});
}
if (is_array($b->$column)) {
$b = clone $b;
$b->$column = array_reduce($b->$column, function ($carry, $item) use ($direction) {
$result = $carry === null ? 0 : strcmp($item, $carry);
return ($direction === self::SORT_ASC ? $result : $result * -1) < 1 ? $item : $carry;
});
}
}
return parent::compare($a, $b, $orderIndex);
}
/**
* Fetch result as tree
*
* @return Root
*
* @todo This is untested waste, not being used anywhere and ignores the query's order and base dn.
* Evaluate whether it's reasonable to properly implement and test it.
*/
public function fetchTree()
{
$result = $this->fetchAll();
$sorted = array();
$quotedDn = preg_quote($this->ds->getDn(), '/');
foreach ($result as $key => & $item) {
$new_key = LdapUtils::implodeDN(
array_reverse(
LdapUtils::explodeDN(
preg_replace('/,' . $quotedDn . '$/', '', $key)
)
)
);
$sorted[$new_key] = $key;
}
unset($groups);
ksort($sorted);
$tree = Root::forConnection($this->ds);
$root_dn = $tree->getDN();
foreach ($sorted as $sort_key => & $key) {
if ($key === $root_dn) {
continue;
}
$tree->createChildByDN($key, $result[$key]);
}
return $tree;
}
/**
* Fetch the distinguished name of the first result
*
* @return string|false The distinguished name or false in case it's not possible to fetch a result
*
* @throws LdapException In case the query returns multiple results
* (i.e. it's not possible to fetch a unique DN)
*/
public function fetchDn()
{
return $this->ds->fetchDn($this);
}
/**
* Render and return this query's filter
*
* @return string
*/
public function renderFilter()
{
$filter = $this->ds->renderFilter($this->filter);
if ($this->nativeFilter) {
$filter = '(&(' . $this->nativeFilter . ')' . $filter . ')';
}
return $filter;
}
/**
* Return the LDAP filter to be applied on this query
*
* @return string
*/
public function __toString()
{
return $this->renderFilter();
}
/**
* Get LDAP search scope
*
* @return string
*/
public function getScope()
{
return $this->scope;
}
/**
* Set LDAP search scope
*
* Valid: sub one base (Default: sub)
*
* @param string $scope
*
* @return LdapQuery
*
* @throws LogicException If scope value is invalid
*/
public function setScope($scope)
{
if (! in_array($scope, static::$scopes)) {
throw new LogicException(
'Can\'t set scope %d, it is is invalid. Use one of %s or LdapQuery\'s constants.',
$scope,
implode(', ', static::$scopes)
);
}
$this->scope = $scope;
return $this;
}
}