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\Control; 13 14use FreeDSx\Asn1\Asn1; 15use FreeDSx\Asn1\Exception\EncoderException; 16use FreeDSx\Asn1\Exception\PartialPduException; 17use FreeDSx\Asn1\Type\AbstractType; 18use FreeDSx\Asn1\Type\BooleanType; 19use FreeDSx\Asn1\Type\OctetStringType; 20use FreeDSx\Asn1\Type\SequenceType; 21use FreeDSx\Ldap\Exception\ProtocolException; 22use FreeDSx\Ldap\Protocol\LdapEncoder; 23use FreeDSx\Ldap\Protocol\ProtocolElementInterface; 24use function count; 25 26/** 27 * Represents a control. RFC 4511, 4.1.11 28 * 29 * Control ::= SEQUENCE { 30 * controlType LDAPOID, 31 * criticality BOOLEAN DEFAULT FALSE, 32 * controlValue OCTET STRING OPTIONAL } 33 * 34 * @author Chad Sikorra <Chad.Sikorra@gmail.com> 35 */ 36class Control implements ProtocolElementInterface 37{ 38 public const OID_DIR_SYNC = '1.2.840.113556.1.4.841'; 39 40 public const OID_EXPECTED_ENTRY_COUNT = '1.2.840.113556.1.4.2211'; 41 42 public const OID_EXTENDED_DN = '1.2.840.113556.1.4.529'; 43 44 public const OID_PAGING = '1.2.840.113556.1.4.319'; 45 46 public const OID_POLICY_HINTS = '1.2.840.113556.1.4.2239'; 47 48 public const OID_PWD_POLICY = '1.3.6.1.4.1.42.2.27.8.5.1'; 49 50 public const OID_SET_OWNER = '1.2.840.113556.1.4.2255'; 51 52 public const OID_SD_FLAGS = '1.2.840.113556.1.4.801'; 53 54 public const OID_SHOW_DELETED = '1.2.840.113556.1.4.417'; 55 56 public const OID_SHOW_RECYCLED = '1.2.840.113556.1.4.2064'; 57 58 public const OID_SUBTREE_DELETE = '1.2.840.113556.1.4.805'; 59 60 public const OID_SORTING = '1.2.840.113556.1.4.473'; 61 62 public const OID_SORTING_RESPONSE = '1.2.840.113556.1.4.474'; 63 64 public const OID_VLV = '2.16.840.1.113730.3.4.9'; 65 66 public const OID_VLV_RESPONSE = '2.16.840.1.113730.3.4.10'; 67 68 /** 69 * @var string 70 */ 71 protected $controlType; 72 73 /** 74 * @var bool 75 */ 76 protected $criticality; 77 78 /** 79 * @var AbstractType|ProtocolElementInterface|string|null 80 */ 81 protected $controlValue; 82 83 /** 84 * @param string $controlType 85 * @param bool $criticality 86 * @param null|mixed $controlValue 87 */ 88 public function __construct(string $controlType, bool $criticality = false, $controlValue = null) 89 { 90 $this->controlType = $controlType; 91 $this->criticality = $criticality; 92 $this->controlValue = $controlValue; 93 } 94 95 /** 96 * @param string $oid 97 * @return $this 98 */ 99 public function setTypeOid(string $oid) 100 { 101 $this->controlType = $oid; 102 103 return $this; 104 } 105 106 /** 107 * @return string 108 */ 109 public function getTypeOid(): string 110 { 111 return $this->controlType; 112 } 113 114 /** 115 * @param bool $criticality 116 * @return $this 117 */ 118 public function setCriticality(bool $criticality) 119 { 120 $this->criticality = $criticality; 121 122 return $this; 123 } 124 125 /** 126 * @return bool 127 */ 128 public function getCriticality(): bool 129 { 130 return $this->criticality; 131 } 132 133 /** 134 * @param AbstractType|ProtocolElementInterface|string|null $controlValue 135 * @return $this 136 */ 137 public function setValue($controlValue) 138 { 139 $this->controlValue = $controlValue; 140 141 return $this; 142 } 143 144 /** 145 * @return AbstractType|ProtocolElementInterface|string|null 146 */ 147 public function getValue() 148 { 149 return $this->controlValue; 150 } 151 152 /** 153 * @return AbstractType 154 * @throws EncoderException 155 */ 156 public function toAsn1(): AbstractType 157 { 158 $asn1 = Asn1::sequence( 159 Asn1::octetString($this->controlType), 160 Asn1::boolean($this->criticality) 161 ); 162 163 if ($this->controlValue !== null) { 164 $encoder = new LdapEncoder(); 165 if ($this->controlValue instanceof AbstractType) { 166 $value = $encoder->encode($this->controlValue); 167 } elseif ($this->controlValue instanceof ProtocolElementInterface) { 168 $value = $encoder->encode($this->controlValue->toAsn1()); 169 } else { 170 $value = $this->controlValue; 171 } 172 $asn1->addChild(Asn1::octetString($value)); 173 } 174 175 return $asn1; 176 } 177 178 /** 179 * @return string 180 */ 181 public function __toString() 182 { 183 return $this->controlType; 184 } 185 186 /** 187 * {@inheritDoc} 188 * @return static|ProtocolElementInterface 189 */ 190 public static function fromAsn1(AbstractType $type) 191 { 192 if (!$type instanceof SequenceType) { 193 throw new ProtocolException(sprintf( 194 'Protocol encoding issue. Expected a sequence type but received: %s', 195 get_class($type) 196 )); 197 } 198 199 return new static(...self::parseAsn1ControlValues($type)); 200 } 201 202 /** 203 * @param Control $control 204 * @param AbstractType $type 205 * @return self 206 * @throws ProtocolException 207 */ 208 protected static function mergeControlData(Control $control, AbstractType $type) 209 { 210 if (!($type instanceof SequenceType && count($type->getChildren()) <= 3)) { 211 throw new ProtocolException(sprintf( 212 'The received control is malformed. Expected at least 3 sequence values. Received %s.', 213 count($type->getChildren()) 214 )); 215 } 216 [0 => $control->controlType, 1 => $control->criticality, 2 => $control->controlValue] = self::parseAsn1ControlValues($type); 217 218 return $control; 219 } 220 221 /** 222 * @param AbstractType $type 223 * @return AbstractType 224 * @throws ProtocolException 225 * @throws EncoderException 226 * @throws PartialPduException 227 */ 228 protected static function decodeEncodedValue(AbstractType $type) 229 { 230 if (!$type instanceof SequenceType) { 231 throw new ProtocolException('The received control is malformed. Unable to get the encoded value.'); 232 } 233 234 [2 => $value] = self::parseAsn1ControlValues($type); 235 if ($value === null) { 236 throw new ProtocolException('The received control is malformed. Unable to get the encoded value.'); 237 } 238 239 return (new LdapEncoder())->decode($value); 240 } 241 242 /** 243 * @param SequenceType $type 244 * @return array 245 */ 246 protected static function parseAsn1ControlValues(SequenceType $type) 247 { 248 $oid = null; 249 $criticality = false; 250 $value = null; 251 252 /* 253 * RFC 4511, 4.1.1. States responses should not have criticality set, but not that it must not be set. So do not 254 * assume the position of the octet string value. Accounts for the additional logic of the checks here. 255 */ 256 foreach ($type->getChildren() as $i => $child) { 257 if ($child->getTagClass() !== AbstractType::TAG_CLASS_UNIVERSAL) { 258 continue; 259 } 260 261 if ($i === 0) { 262 $oid = $child->getValue(); 263 } elseif ($child instanceof BooleanType) { 264 $criticality = $child->getValue(); 265 } elseif ($child instanceof OctetStringType) { 266 $value = $child->getValue(); 267 } 268 } 269 270 return [$oid, $criticality, $value]; 271 } 272} 273