* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FreeDSx\Ldap\Operation\Request; use FreeDSx\Asn1\Asn1; use FreeDSx\Asn1\Type\AbstractType; use FreeDSx\Asn1\Type\BooleanType; use FreeDSx\Asn1\Type\EnumeratedType; use FreeDSx\Asn1\Type\IntegerType; use FreeDSx\Asn1\Type\OctetStringType; use FreeDSx\Asn1\Type\SequenceType; use FreeDSx\Ldap\Entry\Attribute; use FreeDSx\Ldap\Entry\Dn; use FreeDSx\Ldap\Exception\ProtocolException; use FreeDSx\Ldap\Exception\RuntimeException; use FreeDSx\Ldap\Protocol\Factory\FilterFactory; use FreeDSx\Ldap\Search\Filter\FilterInterface; use function array_map; /** * A Search Request. RFC 4511, 4.5.1. * * SearchRequest ::= [APPLICATION 3] SEQUENCE { * baseObject LDAPDN, * scope ENUMERATED { * baseObject (0), * singleLevel (1), * wholeSubtree (2), * ... }, * derefAliases ENUMERATED { * neverDerefAliases (0), * derefInSearching (1), * derefFindingBaseObj (2), * derefAlways (3) }, * sizeLimit INTEGER (0 .. maxInt), * timeLimit INTEGER (0 .. maxInt), * typesOnly BOOLEAN, * filter Filter, * attributes AttributeSelection } * * @author Chad Sikorra */ class SearchRequest implements RequestInterface { /** * Searches a scope of a single object (IE. a specific DN) */ public const SCOPE_BASE_OBJECT = 0; /** * Searches one level under a specific DN (ie. like a non-recursive directory listing). */ public const SCOPE_SINGLE_LEVEL = 1; /** * Searches a complete subtree under a DN (ie. like a recursive directory listing). */ public const SCOPE_WHOLE_SUBTREE = 2; /** * Never dereference aliases. */ public const DEREF_NEVER = 0; public const DEREF_IN_SEARCHING = 1; /** * Dereference aliases when finding the base object only. */ public const DEREF_FINDING_BASE_OBJECT = 2; /** * Always dereference aliases. */ public const DEREF_ALWAYS = 3; protected const APP_TAG = 3; /** * @var Dn|null */ protected $baseDn; /** * @var int */ protected $scope = self::SCOPE_WHOLE_SUBTREE; /** * @var int */ protected $derefAliases = self::DEREF_NEVER; /** * @var int */ protected $sizeLimit = 0; /** * @var int */ protected $timeLimit = 0; /** * @var bool */ protected $attributesOnly = false; /** * @var FilterInterface */ protected $filter; /** * @var Attribute[] */ protected $attributes = []; /** * @param FilterInterface $filter * @param string|Attribute ...$attributes */ public function __construct(FilterInterface $filter, ...$attributes) { $this->filter = $filter; $this->setAttributes(...$attributes); } /** * Alias to setAttributes. Convenience for a more fluent method call. * * @param string|Attribute ...$attributes * @return SearchRequest */ public function select(...$attributes) { return $this->setAttributes(...$attributes); } /** * Alias to setBaseDn. Convenience for a more fluent method call. * * @param string|Dn|null $dn * @return SearchRequest */ public function base($dn) { return $this->setBaseDn($dn); } /** * Set the search scope for all children underneath the base DN. * * @return $this */ public function useSubtreeScope() { $this->scope = self::SCOPE_WHOLE_SUBTREE; return $this; } /** * Set the search scope to the base DN object only. * * @return $this */ public function useBaseScope() { $this->scope = self::SCOPE_BASE_OBJECT; return $this; } /** * Set the search scope to a single level listing from the base DN. * * @return $this */ public function useSingleLevelScope() { $this->scope = self::SCOPE_SINGLE_LEVEL; return $this; } /** * Alias to setSizeLimit. Convenience for a more fluent method call. * * @param int $size * @return SearchRequest */ public function sizeLimit(int $size) { return $this->setSizeLimit($size); } /** * Alias to setTimeLimit. Convenience for a more fluent method call. * * @param int $time * @return SearchRequest */ public function timeLimit($time) { return $this->setTimeLimit($time); } /** * @return Attribute[] */ public function getAttributes(): array { return $this->attributes; } /** * @param string|Attribute ...$attributes * @return $this */ public function setAttributes(...$attributes) { $attr = []; foreach ($attributes as $attribute) { $attr[] = $attribute instanceof Attribute ? $attribute : new Attribute($attribute); } $this->attributes = $attr; return $this; } /** * @return Dn|null */ public function getBaseDn(): ?Dn { return $this->baseDn; } /** * @param string|Dn|null $baseDn * @return $this */ public function setBaseDn($baseDn) { if ($baseDn !== null) { $baseDn = $baseDn instanceof Dn ? $baseDn : new Dn($baseDn); } $this->baseDn = $baseDn; return $this; } /** * @return int */ public function getScope(): int { return $this->scope; } /** * @param int $scope * @return $this */ public function setScope(int $scope) { $this->scope = $scope; return $this; } /** * @return int */ public function getDereferenceAliases(): int { return $this->derefAliases; } /** * @param int $derefAliases * @return $this */ public function setDereferenceAliases(int $derefAliases) { $this->derefAliases = $derefAliases; return $this; } /** * @return int */ public function getSizeLimit(): int { return $this->sizeLimit; } /** * @param int $sizeLimit * @return $this */ public function setSizeLimit(int $sizeLimit) { $this->sizeLimit = $sizeLimit; return $this; } /** * @return int */ public function getTimeLimit(): int { return $this->timeLimit; } /** * @param int $timeLimit * @return $this */ public function setTimeLimit(int $timeLimit) { $this->timeLimit = $timeLimit; return $this; } /** * @return bool */ public function getAttributesOnly(): bool { return $this->attributesOnly; } /** * @param bool $attributesOnly * @return $this */ public function setAttributesOnly(bool $attributesOnly) { $this->attributesOnly = $attributesOnly; return $this; } /** * @return FilterInterface */ public function getFilter(): FilterInterface { return $this->filter; } /** * @param FilterInterface $filter * @return $this */ public function setFilter(FilterInterface $filter) { $this->filter = $filter; return $this; } /** * {@inheritDoc} * @return self * @throws RuntimeException */ public static function fromAsn1(AbstractType $type) { if (!($type instanceof SequenceType && count($type) === 8)) { throw new ProtocolException('The search request is malformed'); } $baseDn = $type->getChild(0); $scope = $type->getChild(1); $deref = $type->getChild(2); $sizeLimit = $type->getChild(3); $timeLimit = $type->getChild(4); $typesOnly = $type->getChild(5); $attributes = $type->getChild(7); $filter = $type->getChild(6); if ($filter === null) { throw new ProtocolException('The search request is malformed.'); } $filter = FilterFactory::get($filter); if ( !($baseDn instanceof OctetStringType && $scope instanceof EnumeratedType && $deref instanceof EnumeratedType && $sizeLimit instanceof IntegerType && $timeLimit instanceof IntegerType && $typesOnly instanceof BooleanType && $attributes instanceof SequenceType) ) { throw new ProtocolException('The search request is malformed'); } $attrList = []; foreach ($attributes->getChildren() as $attribute) { if (!$attribute instanceof OctetStringType) { throw new ProtocolException('The search request is malformed.'); } $attrList[] = new Attribute($attribute->getValue()); } $search = new self($filter, ...$attrList); $search->setScope($scope->getValue()); $search->setBaseDn($baseDn->getValue()); $search->setDereferenceAliases($deref->getValue()); $search->setSizeLimit($sizeLimit->getValue()); $search->setTimeLimit($timeLimit->getValue()); $search->setAttributesOnly($typesOnly->getValue()); return $search; } /** * @throws RuntimeException */ public function toAsn1(): AbstractType { if ($this->baseDn === null) { throw new RuntimeException('The search baseDn cannot be empty.'); } return Asn1::application(self::APP_TAG, Asn1::sequence( Asn1::octetString($this->baseDn), Asn1::enumerated($this->scope), Asn1::enumerated($this->derefAliases), Asn1::integer($this->sizeLimit), Asn1::integer($this->timeLimit), Asn1::boolean($this->attributesOnly), $this->filter->toAsn1(), Asn1::sequenceOf(...array_map(function ($attr) { /** @var Attribute|string $attr */ return Asn1::octetString($attr instanceof Attribute ? $attr->getDescription() : $attr); }, $this->attributes)) )); } }