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