10b3fd2d3SAndreas Gohr<?php 2*dad993c5SAndreas Gohr 30b3fd2d3SAndreas Gohr/** 40b3fd2d3SAndreas Gohr * This file is part of the FreeDSx LDAP package. 50b3fd2d3SAndreas Gohr * 60b3fd2d3SAndreas Gohr * (c) Chad Sikorra <Chad.Sikorra@gmail.com> 70b3fd2d3SAndreas Gohr * 80b3fd2d3SAndreas Gohr * For the full copyright and license information, please view the LICENSE 90b3fd2d3SAndreas Gohr * file that was distributed with this source code. 100b3fd2d3SAndreas Gohr */ 110b3fd2d3SAndreas Gohr 120b3fd2d3SAndreas Gohrnamespace FreeDSx\Ldap\Operation; 130b3fd2d3SAndreas Gohr 140b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Asn1; 15*dad993c5SAndreas Gohruse FreeDSx\Asn1\Exception\EncoderException; 160b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\AbstractType; 170b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\IncompleteType; 180b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\SequenceType; 190b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Entry\Dn; 200b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\ProtocolException; 210b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\UrlParseException; 220b3fd2d3SAndreas Gohruse FreeDSx\Ldap\LdapUrl; 230b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Response\ResponseInterface; 240b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Protocol\LdapEncoder; 25*dad993c5SAndreas Gohruse function array_map; 26*dad993c5SAndreas Gohruse function count; 270b3fd2d3SAndreas Gohr 280b3fd2d3SAndreas Gohr/** 290b3fd2d3SAndreas Gohr * Represents the result of an operation request. RFC 4511, 4.1.9 300b3fd2d3SAndreas Gohr * 310b3fd2d3SAndreas Gohr * LDAPResult ::= SEQUENCE { 320b3fd2d3SAndreas Gohr * resultCode ENUMERATED { 330b3fd2d3SAndreas Gohr * success (0), 340b3fd2d3SAndreas Gohr * operationsError (1), 350b3fd2d3SAndreas Gohr * protocolError (2), 360b3fd2d3SAndreas Gohr * timeLimitExceeded (3), 370b3fd2d3SAndreas Gohr * sizeLimitExceeded (4), 380b3fd2d3SAndreas Gohr * compareFalse (5), 390b3fd2d3SAndreas Gohr * compareTrue (6), 400b3fd2d3SAndreas Gohr * authMethodNotSupported (7), 410b3fd2d3SAndreas Gohr * strongerAuthRequired (8), 420b3fd2d3SAndreas Gohr * -- 9 reserved -- 430b3fd2d3SAndreas Gohr * referral (10), 440b3fd2d3SAndreas Gohr * adminLimitExceeded (11), 450b3fd2d3SAndreas Gohr * unavailableCriticalExtension (12), 460b3fd2d3SAndreas Gohr * confidentialityRequired (13), 470b3fd2d3SAndreas Gohr * saslBindInProgress (14), 480b3fd2d3SAndreas Gohr * noSuchAttribute (16), 490b3fd2d3SAndreas Gohr * undefinedAttributeType (17), 500b3fd2d3SAndreas Gohr * inappropriateMatching (18), 510b3fd2d3SAndreas Gohr * constraintViolation (19), 520b3fd2d3SAndreas Gohr * attributeOrValueExists (20), 530b3fd2d3SAndreas Gohr * invalidAttributeSyntax (21), 540b3fd2d3SAndreas Gohr * -- 22-31 unused -- 550b3fd2d3SAndreas Gohr * noSuchObject (32), 560b3fd2d3SAndreas Gohr * aliasProblem (33), 570b3fd2d3SAndreas Gohr * invalidDNSyntax (34), 580b3fd2d3SAndreas Gohr * -- 35 reserved for undefined isLeaf -- 590b3fd2d3SAndreas Gohr * aliasDereferencingProblem (36), 600b3fd2d3SAndreas Gohr * -- 37-47 unused -- 610b3fd2d3SAndreas Gohr * inappropriateAuthentication (48), 620b3fd2d3SAndreas Gohr * invalidCredentials (49), 630b3fd2d3SAndreas Gohr * insufficientAccessRights (50), 640b3fd2d3SAndreas Gohr * busy (51), 650b3fd2d3SAndreas Gohr * unavailable (52), 660b3fd2d3SAndreas Gohr * unwillingToPerform (53), 670b3fd2d3SAndreas Gohr * loopDetect (54), 680b3fd2d3SAndreas Gohr * -- 55-63 unused -- 690b3fd2d3SAndreas Gohr * namingViolation (64), 700b3fd2d3SAndreas Gohr * objectClassViolation (65), 710b3fd2d3SAndreas Gohr * notAllowedOnNonLeaf (66), 720b3fd2d3SAndreas Gohr * notAllowedOnRDN (67), 730b3fd2d3SAndreas Gohr * entryAlreadyExists (68), 740b3fd2d3SAndreas Gohr * objectClassModsProhibited (69), 750b3fd2d3SAndreas Gohr * -- 70 reserved for CLDAP -- 760b3fd2d3SAndreas Gohr * affectsMultipleDSAs (71), 770b3fd2d3SAndreas Gohr * -- 72-79 unused -- 780b3fd2d3SAndreas Gohr * other (80), 790b3fd2d3SAndreas Gohr * ... }, 800b3fd2d3SAndreas Gohr * matchedDN LDAPDN, 810b3fd2d3SAndreas Gohr * diagnosticMessage LDAPString, 820b3fd2d3SAndreas Gohr * referral [3] Referral OPTIONAL } 830b3fd2d3SAndreas Gohr * 840b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com> 850b3fd2d3SAndreas Gohr */ 860b3fd2d3SAndreas Gohrclass LdapResult implements ResponseInterface 870b3fd2d3SAndreas Gohr{ 880b3fd2d3SAndreas Gohr /** 890b3fd2d3SAndreas Gohr * @var int 900b3fd2d3SAndreas Gohr */ 910b3fd2d3SAndreas Gohr protected $tagNumber; 920b3fd2d3SAndreas Gohr 930b3fd2d3SAndreas Gohr /** 940b3fd2d3SAndreas Gohr * @var int 950b3fd2d3SAndreas Gohr */ 960b3fd2d3SAndreas Gohr protected $resultCode; 970b3fd2d3SAndreas Gohr 980b3fd2d3SAndreas Gohr /** 990b3fd2d3SAndreas Gohr * @var Dn 1000b3fd2d3SAndreas Gohr */ 1010b3fd2d3SAndreas Gohr protected $dn; 1020b3fd2d3SAndreas Gohr 1030b3fd2d3SAndreas Gohr /** 1040b3fd2d3SAndreas Gohr * @var string 1050b3fd2d3SAndreas Gohr */ 1060b3fd2d3SAndreas Gohr protected $diagnosticMessage; 1070b3fd2d3SAndreas Gohr 1080b3fd2d3SAndreas Gohr /** 1090b3fd2d3SAndreas Gohr * @var LdapUrl[] 1100b3fd2d3SAndreas Gohr */ 1110b3fd2d3SAndreas Gohr protected $referrals = []; 1120b3fd2d3SAndreas Gohr 1130b3fd2d3SAndreas Gohr public function __construct(int $resultCode, string $dn = '', string $diagnosticMessage = '', LdapUrl ...$referrals) 1140b3fd2d3SAndreas Gohr { 1150b3fd2d3SAndreas Gohr $this->resultCode = $resultCode; 1160b3fd2d3SAndreas Gohr $this->dn = new Dn($dn); 1170b3fd2d3SAndreas Gohr $this->diagnosticMessage = $diagnosticMessage; 1180b3fd2d3SAndreas Gohr $this->referrals = $referrals; 1190b3fd2d3SAndreas Gohr } 1200b3fd2d3SAndreas Gohr 1210b3fd2d3SAndreas Gohr /** 1220b3fd2d3SAndreas Gohr * @return string 1230b3fd2d3SAndreas Gohr */ 1240b3fd2d3SAndreas Gohr public function getDiagnosticMessage(): string 1250b3fd2d3SAndreas Gohr { 1260b3fd2d3SAndreas Gohr return $this->diagnosticMessage; 1270b3fd2d3SAndreas Gohr } 1280b3fd2d3SAndreas Gohr 1290b3fd2d3SAndreas Gohr /** 1300b3fd2d3SAndreas Gohr * @return Dn 1310b3fd2d3SAndreas Gohr */ 1320b3fd2d3SAndreas Gohr public function getDn(): Dn 1330b3fd2d3SAndreas Gohr { 1340b3fd2d3SAndreas Gohr return $this->dn; 1350b3fd2d3SAndreas Gohr } 1360b3fd2d3SAndreas Gohr 1370b3fd2d3SAndreas Gohr /** 1380b3fd2d3SAndreas Gohr * @return LdapUrl[] 1390b3fd2d3SAndreas Gohr */ 1400b3fd2d3SAndreas Gohr public function getReferrals(): array 1410b3fd2d3SAndreas Gohr { 1420b3fd2d3SAndreas Gohr return $this->referrals; 1430b3fd2d3SAndreas Gohr } 1440b3fd2d3SAndreas Gohr 1450b3fd2d3SAndreas Gohr /** 1460b3fd2d3SAndreas Gohr * {@inheritdoc} 1470b3fd2d3SAndreas Gohr */ 1480b3fd2d3SAndreas Gohr public function getResultCode(): int 1490b3fd2d3SAndreas Gohr { 1500b3fd2d3SAndreas Gohr return $this->resultCode; 1510b3fd2d3SAndreas Gohr } 1520b3fd2d3SAndreas Gohr 1530b3fd2d3SAndreas Gohr /** 154*dad993c5SAndreas Gohr * @return AbstractType 155*dad993c5SAndreas Gohr * @throws ProtocolException 1560b3fd2d3SAndreas Gohr */ 1570b3fd2d3SAndreas Gohr public function toAsn1(): AbstractType 1580b3fd2d3SAndreas Gohr { 1590b3fd2d3SAndreas Gohr $result = Asn1::sequence( 1600b3fd2d3SAndreas Gohr Asn1::enumerated($this->resultCode), 1610b3fd2d3SAndreas Gohr Asn1::octetString($this->dn), 1620b3fd2d3SAndreas Gohr Asn1::octetString($this->diagnosticMessage) 1630b3fd2d3SAndreas Gohr ); 164*dad993c5SAndreas Gohr if (count($this->referrals) !== 0) { 165*dad993c5SAndreas Gohr $result->addChild(Asn1::context(3, Asn1::sequence(...array_map(function ($v) { 1660b3fd2d3SAndreas Gohr return Asn1::octetString($v); 1670b3fd2d3SAndreas Gohr }, $this->referrals)))); 1680b3fd2d3SAndreas Gohr } 1690b3fd2d3SAndreas Gohr if ($this->tagNumber === null) { 1700b3fd2d3SAndreas Gohr throw new ProtocolException(sprintf('You must define the tag number property on %s', get_parent_class())); 1710b3fd2d3SAndreas Gohr } 1720b3fd2d3SAndreas Gohr 1730b3fd2d3SAndreas Gohr return Asn1::application($this->tagNumber, $result); 1740b3fd2d3SAndreas Gohr } 1750b3fd2d3SAndreas Gohr 1760b3fd2d3SAndreas Gohr /** 177*dad993c5SAndreas Gohr * {@inheritDoc} 178*dad993c5SAndreas Gohr * @return self 179*dad993c5SAndreas Gohr * @throws EncoderException 1800b3fd2d3SAndreas Gohr */ 1810b3fd2d3SAndreas Gohr public static function fromAsn1(AbstractType $type) 1820b3fd2d3SAndreas Gohr { 1830b3fd2d3SAndreas Gohr [$resultCode, $dn, $diagnosticMessage, $referrals] = self::parseResultData($type); 1840b3fd2d3SAndreas Gohr 1850b3fd2d3SAndreas Gohr return new static($resultCode, $dn, $diagnosticMessage, ...$referrals); 1860b3fd2d3SAndreas Gohr } 1870b3fd2d3SAndreas Gohr 1880b3fd2d3SAndreas Gohr /** 1890b3fd2d3SAndreas Gohr * @param AbstractType $type 1900b3fd2d3SAndreas Gohr * @return array 191*dad993c5SAndreas Gohr * @psalm-return array{0: mixed, 1: mixed, 2: mixed, 3: list<LdapUrl>} 1920b3fd2d3SAndreas Gohr * @throws ProtocolException 193*dad993c5SAndreas Gohr * @throws EncoderException 1940b3fd2d3SAndreas Gohr */ 1950b3fd2d3SAndreas Gohr protected static function parseResultData(AbstractType $type) 1960b3fd2d3SAndreas Gohr { 1970b3fd2d3SAndreas Gohr if (!$type instanceof SequenceType) { 1980b3fd2d3SAndreas Gohr throw new ProtocolException('The LDAP result is malformed.'); 1990b3fd2d3SAndreas Gohr } 2000b3fd2d3SAndreas Gohr $referrals = []; 2010b3fd2d3SAndreas Gohr 2020b3fd2d3SAndreas Gohr # Somewhat ugly minor optimization. Though it's probably less likely for most setups to get referrals. 2030b3fd2d3SAndreas Gohr # So only try to iterate them if we possibly have them. 204*dad993c5SAndreas Gohr $count = count($type->getChildren()); 2050b3fd2d3SAndreas Gohr if ($count > 3) { 2060b3fd2d3SAndreas Gohr for ($i = 3; $i < $count; $i++) { 2070b3fd2d3SAndreas Gohr $child = $type->getChild($i); 2080b3fd2d3SAndreas Gohr if ($child !== null && $child->getTagClass() === AbstractType::TAG_CLASS_CONTEXT_SPECIFIC && $child->getTagNumber() === 3) { 2090b3fd2d3SAndreas Gohr if (!$child instanceof IncompleteType) { 2100b3fd2d3SAndreas Gohr throw new ProtocolException('The ASN1 structure for the referrals is malformed.'); 2110b3fd2d3SAndreas Gohr } 2120b3fd2d3SAndreas Gohr $child = (new LdapEncoder())->complete($child, AbstractType::TAG_TYPE_SEQUENCE); 2130b3fd2d3SAndreas Gohr foreach ($child->getChildren() as $ldapUrl) { 2140b3fd2d3SAndreas Gohr try { 2150b3fd2d3SAndreas Gohr $referrals[] = LdapUrl::parse($ldapUrl->getValue()); 2160b3fd2d3SAndreas Gohr } catch (UrlParseException $e) { 2170b3fd2d3SAndreas Gohr throw new ProtocolException($e->getMessage()); 2180b3fd2d3SAndreas Gohr } 2190b3fd2d3SAndreas Gohr } 2200b3fd2d3SAndreas Gohr } 2210b3fd2d3SAndreas Gohr } 2220b3fd2d3SAndreas Gohr } 2230b3fd2d3SAndreas Gohr 2240b3fd2d3SAndreas Gohr $result = $type->getChild(0); 2250b3fd2d3SAndreas Gohr $dn = $type->getChild(1); 2260b3fd2d3SAndreas Gohr $diagnostic = $type->getChild(2); 2270b3fd2d3SAndreas Gohr if ($result === null || $dn === null || $diagnostic === null) { 2280b3fd2d3SAndreas Gohr throw new ProtocolException('The LDAP result is malformed.'); 2290b3fd2d3SAndreas Gohr } 2300b3fd2d3SAndreas Gohr 2310b3fd2d3SAndreas Gohr return [ 2320b3fd2d3SAndreas Gohr $result->getValue(), 2330b3fd2d3SAndreas Gohr $dn->getValue(), 2340b3fd2d3SAndreas Gohr $diagnostic->getValue(), 2350b3fd2d3SAndreas Gohr $referrals 2360b3fd2d3SAndreas Gohr ]; 2370b3fd2d3SAndreas Gohr } 2380b3fd2d3SAndreas Gohr} 239