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