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; 13 14use FreeDSx\Asn1\Asn1; 15use FreeDSx\Asn1\Exception\EncoderException; 16use FreeDSx\Asn1\Type\AbstractType; 17use FreeDSx\Asn1\Type\IncompleteType; 18use FreeDSx\Asn1\Type\SequenceType; 19use FreeDSx\Ldap\Entry\Dn; 20use FreeDSx\Ldap\Exception\ProtocolException; 21use FreeDSx\Ldap\Exception\UrlParseException; 22use FreeDSx\Ldap\LdapUrl; 23use FreeDSx\Ldap\Operation\Response\ResponseInterface; 24use FreeDSx\Ldap\Protocol\LdapEncoder; 25use function array_map; 26use function count; 27 28/** 29 * Represents the result of an operation request. RFC 4511, 4.1.9 30 * 31 * LDAPResult ::= SEQUENCE { 32 * resultCode ENUMERATED { 33 * success (0), 34 * operationsError (1), 35 * protocolError (2), 36 * timeLimitExceeded (3), 37 * sizeLimitExceeded (4), 38 * compareFalse (5), 39 * compareTrue (6), 40 * authMethodNotSupported (7), 41 * strongerAuthRequired (8), 42 * -- 9 reserved -- 43 * referral (10), 44 * adminLimitExceeded (11), 45 * unavailableCriticalExtension (12), 46 * confidentialityRequired (13), 47 * saslBindInProgress (14), 48 * noSuchAttribute (16), 49 * undefinedAttributeType (17), 50 * inappropriateMatching (18), 51 * constraintViolation (19), 52 * attributeOrValueExists (20), 53 * invalidAttributeSyntax (21), 54 * -- 22-31 unused -- 55 * noSuchObject (32), 56 * aliasProblem (33), 57 * invalidDNSyntax (34), 58 * -- 35 reserved for undefined isLeaf -- 59 * aliasDereferencingProblem (36), 60 * -- 37-47 unused -- 61 * inappropriateAuthentication (48), 62 * invalidCredentials (49), 63 * insufficientAccessRights (50), 64 * busy (51), 65 * unavailable (52), 66 * unwillingToPerform (53), 67 * loopDetect (54), 68 * -- 55-63 unused -- 69 * namingViolation (64), 70 * objectClassViolation (65), 71 * notAllowedOnNonLeaf (66), 72 * notAllowedOnRDN (67), 73 * entryAlreadyExists (68), 74 * objectClassModsProhibited (69), 75 * -- 70 reserved for CLDAP -- 76 * affectsMultipleDSAs (71), 77 * -- 72-79 unused -- 78 * other (80), 79 * ... }, 80 * matchedDN LDAPDN, 81 * diagnosticMessage LDAPString, 82 * referral [3] Referral OPTIONAL } 83 * 84 * @author Chad Sikorra <Chad.Sikorra@gmail.com> 85 */ 86class LdapResult implements ResponseInterface 87{ 88 /** 89 * @var int 90 */ 91 protected $tagNumber; 92 93 /** 94 * @var int 95 */ 96 protected $resultCode; 97 98 /** 99 * @var Dn 100 */ 101 protected $dn; 102 103 /** 104 * @var string 105 */ 106 protected $diagnosticMessage; 107 108 /** 109 * @var LdapUrl[] 110 */ 111 protected $referrals = []; 112 113 public function __construct(int $resultCode, string $dn = '', string $diagnosticMessage = '', LdapUrl ...$referrals) 114 { 115 $this->resultCode = $resultCode; 116 $this->dn = new Dn($dn); 117 $this->diagnosticMessage = $diagnosticMessage; 118 $this->referrals = $referrals; 119 } 120 121 /** 122 * @return string 123 */ 124 public function getDiagnosticMessage(): string 125 { 126 return $this->diagnosticMessage; 127 } 128 129 /** 130 * @return Dn 131 */ 132 public function getDn(): Dn 133 { 134 return $this->dn; 135 } 136 137 /** 138 * @return LdapUrl[] 139 */ 140 public function getReferrals(): array 141 { 142 return $this->referrals; 143 } 144 145 /** 146 * {@inheritdoc} 147 */ 148 public function getResultCode(): int 149 { 150 return $this->resultCode; 151 } 152 153 /** 154 * @return AbstractType 155 * @throws ProtocolException 156 */ 157 public function toAsn1(): AbstractType 158 { 159 $result = Asn1::sequence( 160 Asn1::enumerated($this->resultCode), 161 Asn1::octetString($this->dn), 162 Asn1::octetString($this->diagnosticMessage) 163 ); 164 if (count($this->referrals) !== 0) { 165 $result->addChild(Asn1::context(3, Asn1::sequence(...array_map(function ($v) { 166 return Asn1::octetString($v); 167 }, $this->referrals)))); 168 } 169 if ($this->tagNumber === null) { 170 throw new ProtocolException(sprintf('You must define the tag number property on %s', get_parent_class())); 171 } 172 173 return Asn1::application($this->tagNumber, $result); 174 } 175 176 /** 177 * {@inheritDoc} 178 * @return self 179 * @throws EncoderException 180 */ 181 public static function fromAsn1(AbstractType $type) 182 { 183 [$resultCode, $dn, $diagnosticMessage, $referrals] = self::parseResultData($type); 184 185 return new static($resultCode, $dn, $diagnosticMessage, ...$referrals); 186 } 187 188 /** 189 * @param AbstractType $type 190 * @return array 191 * @psalm-return array{0: mixed, 1: mixed, 2: mixed, 3: list<LdapUrl>} 192 * @throws ProtocolException 193 * @throws EncoderException 194 */ 195 protected static function parseResultData(AbstractType $type) 196 { 197 if (!$type instanceof SequenceType) { 198 throw new ProtocolException('The LDAP result is malformed.'); 199 } 200 $referrals = []; 201 202 # Somewhat ugly minor optimization. Though it's probably less likely for most setups to get referrals. 203 # So only try to iterate them if we possibly have them. 204 $count = count($type->getChildren()); 205 if ($count > 3) { 206 for ($i = 3; $i < $count; $i++) { 207 $child = $type->getChild($i); 208 if ($child !== null && $child->getTagClass() === AbstractType::TAG_CLASS_CONTEXT_SPECIFIC && $child->getTagNumber() === 3) { 209 if (!$child instanceof IncompleteType) { 210 throw new ProtocolException('The ASN1 structure for the referrals is malformed.'); 211 } 212 $child = (new LdapEncoder())->complete($child, AbstractType::TAG_TYPE_SEQUENCE); 213 foreach ($child->getChildren() as $ldapUrl) { 214 try { 215 $referrals[] = LdapUrl::parse($ldapUrl->getValue()); 216 } catch (UrlParseException $e) { 217 throw new ProtocolException($e->getMessage()); 218 } 219 } 220 } 221 } 222 } 223 224 $result = $type->getChild(0); 225 $dn = $type->getChild(1); 226 $diagnostic = $type->getChild(2); 227 if ($result === null || $dn === null || $diagnostic === null) { 228 throw new ProtocolException('The LDAP result is malformed.'); 229 } 230 231 return [ 232 $result->getValue(), 233 $dn->getValue(), 234 $diagnostic->getValue(), 235 $referrals 236 ]; 237 } 238} 239