1<?php 2 3/** 4 * This file is part of the FreeDSx LDAP package. 5 * 6 * (c) Chad Sikorra <Chad.Sikorra@gmail.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace FreeDSx\Ldap\Operation\Request; 13 14use FreeDSx\Asn1\Asn1; 15use FreeDSx\Asn1\Type\AbstractType; 16use FreeDSx\Asn1\Type\BooleanType; 17use FreeDSx\Asn1\Type\EnumeratedType; 18use FreeDSx\Asn1\Type\IntegerType; 19use FreeDSx\Asn1\Type\OctetStringType; 20use FreeDSx\Asn1\Type\SequenceType; 21use FreeDSx\Ldap\Entry\Attribute; 22use FreeDSx\Ldap\Entry\Dn; 23use FreeDSx\Ldap\Exception\ProtocolException; 24use FreeDSx\Ldap\Exception\RuntimeException; 25use FreeDSx\Ldap\Protocol\Factory\FilterFactory; 26use FreeDSx\Ldap\Search\Filter\FilterInterface; 27use function array_map; 28 29/** 30 * A Search Request. RFC 4511, 4.5.1. 31 * 32 * SearchRequest ::= [APPLICATION 3] SEQUENCE { 33 * baseObject LDAPDN, 34 * scope ENUMERATED { 35 * baseObject (0), 36 * singleLevel (1), 37 * wholeSubtree (2), 38 * ... }, 39 * derefAliases ENUMERATED { 40 * neverDerefAliases (0), 41 * derefInSearching (1), 42 * derefFindingBaseObj (2), 43 * derefAlways (3) }, 44 * sizeLimit INTEGER (0 .. maxInt), 45 * timeLimit INTEGER (0 .. maxInt), 46 * typesOnly BOOLEAN, 47 * filter Filter, 48 * attributes AttributeSelection } 49 * 50 * @author Chad Sikorra <Chad.Sikorra@gmail.com> 51 */ 52class SearchRequest implements RequestInterface 53{ 54 /** 55 * Searches a scope of a single object (IE. a specific DN) 56 */ 57 public const SCOPE_BASE_OBJECT = 0; 58 59 /** 60 * Searches one level under a specific DN (ie. like a non-recursive directory listing). 61 */ 62 public const SCOPE_SINGLE_LEVEL = 1; 63 64 /** 65 * Searches a complete subtree under a DN (ie. like a recursive directory listing). 66 */ 67 public const SCOPE_WHOLE_SUBTREE = 2; 68 69 /** 70 * Never dereference aliases. 71 */ 72 public const DEREF_NEVER = 0; 73 74 public const DEREF_IN_SEARCHING = 1; 75 76 /** 77 * Dereference aliases when finding the base object only. 78 */ 79 public const DEREF_FINDING_BASE_OBJECT = 2; 80 81 /** 82 * Always dereference aliases. 83 */ 84 public const DEREF_ALWAYS = 3; 85 86 protected const APP_TAG = 3; 87 88 /** 89 * @var Dn|null 90 */ 91 protected $baseDn; 92 93 /** 94 * @var int 95 */ 96 protected $scope = self::SCOPE_WHOLE_SUBTREE; 97 98 /** 99 * @var int 100 */ 101 protected $derefAliases = self::DEREF_NEVER; 102 103 /** 104 * @var int 105 */ 106 protected $sizeLimit = 0; 107 108 /** 109 * @var int 110 */ 111 protected $timeLimit = 0; 112 113 /** 114 * @var bool 115 */ 116 protected $attributesOnly = false; 117 118 /** 119 * @var FilterInterface 120 */ 121 protected $filter; 122 123 /** 124 * @var Attribute[] 125 */ 126 protected $attributes = []; 127 128 /** 129 * @param FilterInterface $filter 130 * @param string|Attribute ...$attributes 131 */ 132 public function __construct(FilterInterface $filter, ...$attributes) 133 { 134 $this->filter = $filter; 135 $this->setAttributes(...$attributes); 136 } 137 138 /** 139 * Alias to setAttributes. Convenience for a more fluent method call. 140 * 141 * @param string|Attribute ...$attributes 142 * @return SearchRequest 143 */ 144 public function select(...$attributes) 145 { 146 return $this->setAttributes(...$attributes); 147 } 148 149 /** 150 * Alias to setBaseDn. Convenience for a more fluent method call. 151 * 152 * @param string|Dn|null $dn 153 * @return SearchRequest 154 */ 155 public function base($dn) 156 { 157 return $this->setBaseDn($dn); 158 } 159 160 /** 161 * Set the search scope for all children underneath the base DN. 162 * 163 * @return $this 164 */ 165 public function useSubtreeScope() 166 { 167 $this->scope = self::SCOPE_WHOLE_SUBTREE; 168 169 return $this; 170 } 171 172 /** 173 * Set the search scope to the base DN object only. 174 * 175 * @return $this 176 */ 177 public function useBaseScope() 178 { 179 $this->scope = self::SCOPE_BASE_OBJECT; 180 181 return $this; 182 } 183 184 /** 185 * Set the search scope to a single level listing from the base DN. 186 * 187 * @return $this 188 */ 189 public function useSingleLevelScope() 190 { 191 $this->scope = self::SCOPE_SINGLE_LEVEL; 192 193 return $this; 194 } 195 196 /** 197 * Alias to setSizeLimit. Convenience for a more fluent method call. 198 * 199 * @param int $size 200 * @return SearchRequest 201 */ 202 public function sizeLimit(int $size) 203 { 204 return $this->setSizeLimit($size); 205 } 206 207 /** 208 * Alias to setTimeLimit. Convenience for a more fluent method call. 209 * 210 * @param int $time 211 * @return SearchRequest 212 */ 213 public function timeLimit($time) 214 { 215 return $this->setTimeLimit($time); 216 } 217 218 /** 219 * @return Attribute[] 220 */ 221 public function getAttributes(): array 222 { 223 return $this->attributes; 224 } 225 226 /** 227 * @param string|Attribute ...$attributes 228 * @return $this 229 */ 230 public function setAttributes(...$attributes) 231 { 232 $attr = []; 233 foreach ($attributes as $attribute) { 234 $attr[] = $attribute instanceof Attribute ? $attribute : new Attribute($attribute); 235 } 236 $this->attributes = $attr; 237 238 return $this; 239 } 240 241 /** 242 * @return Dn|null 243 */ 244 public function getBaseDn(): ?Dn 245 { 246 return $this->baseDn; 247 } 248 249 /** 250 * @param string|Dn|null $baseDn 251 * @return $this 252 */ 253 public function setBaseDn($baseDn) 254 { 255 if ($baseDn !== null) { 256 $baseDn = $baseDn instanceof Dn ? $baseDn : new Dn($baseDn); 257 } 258 $this->baseDn = $baseDn; 259 260 return $this; 261 } 262 263 /** 264 * @return int 265 */ 266 public function getScope(): int 267 { 268 return $this->scope; 269 } 270 271 /** 272 * @param int $scope 273 * @return $this 274 */ 275 public function setScope(int $scope) 276 { 277 $this->scope = $scope; 278 279 return $this; 280 } 281 282 /** 283 * @return int 284 */ 285 public function getDereferenceAliases(): int 286 { 287 return $this->derefAliases; 288 } 289 290 /** 291 * @param int $derefAliases 292 * @return $this 293 */ 294 public function setDereferenceAliases(int $derefAliases) 295 { 296 $this->derefAliases = $derefAliases; 297 298 return $this; 299 } 300 301 /** 302 * @return int 303 */ 304 public function getSizeLimit(): int 305 { 306 return $this->sizeLimit; 307 } 308 309 /** 310 * @param int $sizeLimit 311 * @return $this 312 */ 313 public function setSizeLimit(int $sizeLimit) 314 { 315 $this->sizeLimit = $sizeLimit; 316 317 return $this; 318 } 319 320 /** 321 * @return int 322 */ 323 public function getTimeLimit(): int 324 { 325 return $this->timeLimit; 326 } 327 328 /** 329 * @param int $timeLimit 330 * @return $this 331 */ 332 public function setTimeLimit(int $timeLimit) 333 { 334 $this->timeLimit = $timeLimit; 335 336 return $this; 337 } 338 339 /** 340 * @return bool 341 */ 342 public function getAttributesOnly(): bool 343 { 344 return $this->attributesOnly; 345 } 346 347 /** 348 * @param bool $attributesOnly 349 * @return $this 350 */ 351 public function setAttributesOnly(bool $attributesOnly) 352 { 353 $this->attributesOnly = $attributesOnly; 354 355 return $this; 356 } 357 358 /** 359 * @return FilterInterface 360 */ 361 public function getFilter(): FilterInterface 362 { 363 return $this->filter; 364 } 365 366 /** 367 * @param FilterInterface $filter 368 * @return $this 369 */ 370 public function setFilter(FilterInterface $filter) 371 { 372 $this->filter = $filter; 373 374 return $this; 375 } 376 377 /** 378 * {@inheritDoc} 379 * @return self 380 * @throws RuntimeException 381 */ 382 public static function fromAsn1(AbstractType $type) 383 { 384 if (!($type instanceof SequenceType && count($type) === 8)) { 385 throw new ProtocolException('The search request is malformed'); 386 } 387 $baseDn = $type->getChild(0); 388 $scope = $type->getChild(1); 389 $deref = $type->getChild(2); 390 $sizeLimit = $type->getChild(3); 391 $timeLimit = $type->getChild(4); 392 $typesOnly = $type->getChild(5); 393 $attributes = $type->getChild(7); 394 395 $filter = $type->getChild(6); 396 if ($filter === null) { 397 throw new ProtocolException('The search request is malformed.'); 398 } 399 $filter = FilterFactory::get($filter); 400 401 if ( 402 !($baseDn instanceof OctetStringType 403 && $scope instanceof EnumeratedType 404 && $deref instanceof EnumeratedType 405 && $sizeLimit instanceof IntegerType 406 && $timeLimit instanceof IntegerType 407 && $typesOnly instanceof BooleanType 408 && $attributes instanceof SequenceType) 409 ) { 410 throw new ProtocolException('The search request is malformed'); 411 } 412 413 $attrList = []; 414 foreach ($attributes->getChildren() as $attribute) { 415 if (!$attribute instanceof OctetStringType) { 416 throw new ProtocolException('The search request is malformed.'); 417 } 418 $attrList[] = new Attribute($attribute->getValue()); 419 } 420 421 $search = new self($filter, ...$attrList); 422 $search->setScope($scope->getValue()); 423 $search->setBaseDn($baseDn->getValue()); 424 $search->setDereferenceAliases($deref->getValue()); 425 $search->setSizeLimit($sizeLimit->getValue()); 426 $search->setTimeLimit($timeLimit->getValue()); 427 $search->setAttributesOnly($typesOnly->getValue()); 428 429 return $search; 430 } 431 432 /** 433 * @throws RuntimeException 434 */ 435 public function toAsn1(): AbstractType 436 { 437 if ($this->baseDn === null) { 438 throw new RuntimeException('The search baseDn cannot be empty.'); 439 } 440 441 return Asn1::application(self::APP_TAG, Asn1::sequence( 442 Asn1::octetString($this->baseDn), 443 Asn1::enumerated($this->scope), 444 Asn1::enumerated($this->derefAliases), 445 Asn1::integer($this->sizeLimit), 446 Asn1::integer($this->timeLimit), 447 Asn1::boolean($this->attributesOnly), 448 $this->filter->toAsn1(), 449 Asn1::sequenceOf(...array_map(function ($attr) { 450 /** @var Attribute|string $attr */ 451 return Asn1::octetString($attr instanceof Attribute ? $attr->getDescription() : $attr); 452 }, $this->attributes)) 453 )); 454 } 455} 456