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