xref: /plugin/pureldap/vendor/freedsx/ldap/src/FreeDSx/Ldap/Protocol/LdapMessage.php (revision dad993c57a70866aa1db59c43f043769c2eb7ed0)
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\Protocol;
130b3fd2d3SAndreas Gohr
140b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Asn1;
15*dad993c5SAndreas Gohruse FreeDSx\Asn1\Exception\EncoderException;
16*dad993c5SAndreas Gohruse FreeDSx\Asn1\Exception\PartialPduException;
170b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\AbstractType;
180b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\IncompleteType;
190b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\IntegerType;
200b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\OctetStringType;
210b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\SequenceOfType;
220b3fd2d3SAndreas Gohruse FreeDSx\Asn1\Type\SequenceType;
230b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Control;
240b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Control\ControlBag;
250b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\ProtocolException;
26*dad993c5SAndreas Gohruse FreeDSx\Ldap\Exception\RuntimeException;
270b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Request;
280b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Response;
290b3fd2d3SAndreas Gohruse FreeDSx\Socket\PduInterface;
30*dad993c5SAndreas Gohruse function count;
310b3fd2d3SAndreas Gohr
320b3fd2d3SAndreas Gohr/**
330b3fd2d3SAndreas Gohr * The LDAP Message envelope (PDU). RFC 4511, 4.1.1
340b3fd2d3SAndreas Gohr *
350b3fd2d3SAndreas Gohr * LDAPMessage ::= SEQUENCE {
360b3fd2d3SAndreas Gohr *     messageID       MessageID,
370b3fd2d3SAndreas Gohr *     protocolOp      CHOICE {
380b3fd2d3SAndreas Gohr *         bindRequest           BindRequest,
390b3fd2d3SAndreas Gohr *         bindResponse          BindResponse,
400b3fd2d3SAndreas Gohr *         unbindRequest         UnbindRequest,
410b3fd2d3SAndreas Gohr *         searchRequest         SearchRequest,
420b3fd2d3SAndreas Gohr *         searchResEntry        SearchResultEntry,
430b3fd2d3SAndreas Gohr *         searchResDone         SearchResultDone,
440b3fd2d3SAndreas Gohr *         searchResRef          SearchResultReference,
450b3fd2d3SAndreas Gohr *         modifyRequest         ModifyRequest,
460b3fd2d3SAndreas Gohr *         modifyResponse        ModifyResponse,
470b3fd2d3SAndreas Gohr *         addRequest            AddRequest,
480b3fd2d3SAndreas Gohr *         addResponse           AddResponse,
490b3fd2d3SAndreas Gohr *         delRequest            DelRequest,
500b3fd2d3SAndreas Gohr *         delResponse           DelResponse,
510b3fd2d3SAndreas Gohr *         modDNRequest          ModifyDNRequest,
520b3fd2d3SAndreas Gohr *         modDNResponse         ModifyDNResponse,
530b3fd2d3SAndreas Gohr *         compareRequest        CompareRequest,
540b3fd2d3SAndreas Gohr *         compareResponse       CompareResponse,
550b3fd2d3SAndreas Gohr *         abandonRequest        AbandonRequest,
560b3fd2d3SAndreas Gohr *         extendedReq           ExtendedRequest,
570b3fd2d3SAndreas Gohr *         extendedResp          ExtendedResponse,
580b3fd2d3SAndreas Gohr *         ...,
590b3fd2d3SAndreas Gohr *         intermediateResponse  IntermediateResponse },
600b3fd2d3SAndreas Gohr *     controls       [0] Controls OPTIONAL }
610b3fd2d3SAndreas Gohr *
620b3fd2d3SAndreas Gohr * MessageID ::= INTEGER (0 ..  maxInt)
630b3fd2d3SAndreas Gohr *
640b3fd2d3SAndreas Gohr * maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
650b3fd2d3SAndreas Gohr *
660b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com>
670b3fd2d3SAndreas Gohr */
680b3fd2d3SAndreas Gohrabstract class LdapMessage implements ProtocolElementInterface, PduInterface
690b3fd2d3SAndreas Gohr{
700b3fd2d3SAndreas Gohr    /**
710b3fd2d3SAndreas Gohr     * @var int
720b3fd2d3SAndreas Gohr     */
730b3fd2d3SAndreas Gohr    protected $messageId;
740b3fd2d3SAndreas Gohr
750b3fd2d3SAndreas Gohr    /**
760b3fd2d3SAndreas Gohr     * @var ControlBag
770b3fd2d3SAndreas Gohr     */
780b3fd2d3SAndreas Gohr    protected $controls;
790b3fd2d3SAndreas Gohr
800b3fd2d3SAndreas Gohr    /**
810b3fd2d3SAndreas Gohr     * @param int $messageId
820b3fd2d3SAndreas Gohr     * @param Control\Control ...$controls
830b3fd2d3SAndreas Gohr     */
840b3fd2d3SAndreas Gohr    public function __construct(int $messageId, Control\Control ...$controls)
850b3fd2d3SAndreas Gohr    {
860b3fd2d3SAndreas Gohr        $this->messageId = $messageId;
870b3fd2d3SAndreas Gohr        $this->controls = new ControlBag(...$controls);
880b3fd2d3SAndreas Gohr    }
890b3fd2d3SAndreas Gohr
900b3fd2d3SAndreas Gohr    /**
910b3fd2d3SAndreas Gohr     * @return int
920b3fd2d3SAndreas Gohr     */
930b3fd2d3SAndreas Gohr    public function getMessageId(): int
940b3fd2d3SAndreas Gohr    {
950b3fd2d3SAndreas Gohr        return $this->messageId;
960b3fd2d3SAndreas Gohr    }
970b3fd2d3SAndreas Gohr
980b3fd2d3SAndreas Gohr    /**
990b3fd2d3SAndreas Gohr     * Get the controls for this specific message.
1000b3fd2d3SAndreas Gohr     *
1010b3fd2d3SAndreas Gohr     * @return ControlBag
1020b3fd2d3SAndreas Gohr     */
1030b3fd2d3SAndreas Gohr    public function controls(): ControlBag
1040b3fd2d3SAndreas Gohr    {
1050b3fd2d3SAndreas Gohr        return $this->controls;
1060b3fd2d3SAndreas Gohr    }
1070b3fd2d3SAndreas Gohr
1080b3fd2d3SAndreas Gohr    /**
109*dad993c5SAndreas Gohr     * @return AbstractType
110*dad993c5SAndreas Gohr     * @psalm-return SequenceType
111*dad993c5SAndreas Gohr     * @throws EncoderException
1120b3fd2d3SAndreas Gohr     */
1130b3fd2d3SAndreas Gohr    public function toAsn1(): AbstractType
1140b3fd2d3SAndreas Gohr    {
1150b3fd2d3SAndreas Gohr        $asn1 = Asn1::sequence(
1160b3fd2d3SAndreas Gohr            Asn1::integer($this->messageId),
1170b3fd2d3SAndreas Gohr            $this->getOperationAsn1()
1180b3fd2d3SAndreas Gohr        );
1190b3fd2d3SAndreas Gohr
120*dad993c5SAndreas Gohr        if (count($this->controls->toArray()) !== 0) {
1210b3fd2d3SAndreas Gohr            /** @var SequenceOfType $controls */
1220b3fd2d3SAndreas Gohr            $controls = Asn1::context(0, Asn1::sequenceOf());
1230b3fd2d3SAndreas Gohr            foreach ($this->controls->toArray() as $control) {
1240b3fd2d3SAndreas Gohr                $controls->addChild($control->toAsn1());
1250b3fd2d3SAndreas Gohr            }
1260b3fd2d3SAndreas Gohr            $asn1->addChild($controls);
1270b3fd2d3SAndreas Gohr        }
1280b3fd2d3SAndreas Gohr
1290b3fd2d3SAndreas Gohr        return $asn1;
1300b3fd2d3SAndreas Gohr    }
1310b3fd2d3SAndreas Gohr
1320b3fd2d3SAndreas Gohr    /**
133*dad993c5SAndreas Gohr     * {@inheritDoc}
134*dad993c5SAndreas Gohr     * @return self
135*dad993c5SAndreas Gohr     * @throws EncoderException
136*dad993c5SAndreas Gohr     * @throws PartialPduException
137*dad993c5SAndreas Gohr     * @throws RuntimeException
1380b3fd2d3SAndreas Gohr     */
1390b3fd2d3SAndreas Gohr    public static function fromAsn1(AbstractType $type)
1400b3fd2d3SAndreas Gohr    {
1410b3fd2d3SAndreas Gohr        if (!$type instanceof SequenceType) {
1420b3fd2d3SAndreas Gohr            throw new ProtocolException(sprintf(
1430b3fd2d3SAndreas Gohr                'Expected an ASN1 sequence type, but got: %s',
1440b3fd2d3SAndreas Gohr                get_class($type)
1450b3fd2d3SAndreas Gohr            ));
1460b3fd2d3SAndreas Gohr        }
147*dad993c5SAndreas Gohr        $count = count($type->getChildren());
1480b3fd2d3SAndreas Gohr        if ($count < 2) {
1490b3fd2d3SAndreas Gohr            throw new ProtocolException(sprintf(
1500b3fd2d3SAndreas Gohr                'Expected an ASN1 sequence with at least 2 elements, but it has %s',
1510b3fd2d3SAndreas Gohr                count($type->getChildren())
1520b3fd2d3SAndreas Gohr            ));
1530b3fd2d3SAndreas Gohr        }
1540b3fd2d3SAndreas Gohr
1550b3fd2d3SAndreas Gohr        $controls = [];
1560b3fd2d3SAndreas Gohr        if ($count > 2) {
1570b3fd2d3SAndreas Gohr            for ($i = 2; $i < $count; $i++) {
1580b3fd2d3SAndreas Gohr                $child = $type->getChild($i);
1590b3fd2d3SAndreas Gohr                if ($child !== null && $child->getTagClass() === AbstractType::TAG_CLASS_CONTEXT_SPECIFIC && $child->getTagNumber() === 0) {
1600b3fd2d3SAndreas Gohr                    if (!$child instanceof IncompleteType) {
1610b3fd2d3SAndreas Gohr                        throw new ProtocolException('The ASN1 structure for the controls is malformed.');
1620b3fd2d3SAndreas Gohr                    }
1630b3fd2d3SAndreas Gohr                    /** @var SequenceOfType $child */
164*dad993c5SAndreas Gohr                    $child = (new LdapEncoder())->complete($child, AbstractType::TAG_TYPE_SEQUENCE);
165*dad993c5SAndreas Gohr
1660b3fd2d3SAndreas Gohr                    foreach ($child->getChildren() as $control) {
1670b3fd2d3SAndreas Gohr                        if (!($control instanceof SequenceType && $control->getChild(0) !== null && $control->getChild(0) instanceof OctetStringType)) {
1680b3fd2d3SAndreas Gohr                            throw new ProtocolException('The control either is not a sequence or has no OID value attached.');
1690b3fd2d3SAndreas Gohr                        }
1700b3fd2d3SAndreas Gohr                        switch ($control->getChild(0)->getValue()) {
1710b3fd2d3SAndreas Gohr                            case Control\Control::OID_PAGING:
1720b3fd2d3SAndreas Gohr                                $controls[] = Control\PagingControl::fromAsn1($control);
1730b3fd2d3SAndreas Gohr                                break;
1740b3fd2d3SAndreas Gohr                            case Control\Control::OID_SORTING_RESPONSE:
1750b3fd2d3SAndreas Gohr                                $controls[] = Control\Sorting\SortingResponseControl::fromAsn1($control);
1760b3fd2d3SAndreas Gohr                                break;
1770b3fd2d3SAndreas Gohr                            case Control\Control::OID_VLV_RESPONSE:
1780b3fd2d3SAndreas Gohr                                $controls[] = Control\Vlv\VlvResponseControl::fromAsn1($control);
1790b3fd2d3SAndreas Gohr                                break;
1800b3fd2d3SAndreas Gohr                            case Control\Control::OID_DIR_SYNC:
1810b3fd2d3SAndreas Gohr                                $controls[] = Control\Ad\DirSyncResponseControl::fromAsn1($control);
1820b3fd2d3SAndreas Gohr                                break;
1830b3fd2d3SAndreas Gohr                            default:
1840b3fd2d3SAndreas Gohr                                $controls[] = Control\Control::fromAsn1($control);
1850b3fd2d3SAndreas Gohr                                break;
1860b3fd2d3SAndreas Gohr                        }
1870b3fd2d3SAndreas Gohr                    }
1880b3fd2d3SAndreas Gohr                }
1890b3fd2d3SAndreas Gohr            }
1900b3fd2d3SAndreas Gohr        }
1910b3fd2d3SAndreas Gohr
1920b3fd2d3SAndreas Gohr        $messageId = $type->getChild(0);
1930b3fd2d3SAndreas Gohr        if (!($messageId !== null && $messageId instanceof IntegerType)) {
1940b3fd2d3SAndreas Gohr            throw new ProtocolException('Expected an LDAP message ID as an ASN.1 integer type. None received.');
1950b3fd2d3SAndreas Gohr        }
196*dad993c5SAndreas Gohr        /** @var SequenceType|null $opAsn1 */
1970b3fd2d3SAndreas Gohr        $opAsn1 = $type->getChild(1);
1980b3fd2d3SAndreas Gohr        if ($opAsn1 === null) {
1990b3fd2d3SAndreas Gohr            throw new ProtocolException('The LDAP message is malformed.');
2000b3fd2d3SAndreas Gohr        }
2010b3fd2d3SAndreas Gohr
2020b3fd2d3SAndreas Gohr        switch ($opAsn1->getTagNumber()) {
2030b3fd2d3SAndreas Gohr            case 0:
2040b3fd2d3SAndreas Gohr                $operation = Request\BindRequest::fromAsn1($opAsn1);
2050b3fd2d3SAndreas Gohr                break;
2060b3fd2d3SAndreas Gohr            case 1:
2070b3fd2d3SAndreas Gohr                $operation = Response\BindResponse::fromAsn1($opAsn1);
2080b3fd2d3SAndreas Gohr                break;
2090b3fd2d3SAndreas Gohr            case 2:
2100b3fd2d3SAndreas Gohr                $operation = Request\UnbindRequest::fromAsn1($opAsn1);
2110b3fd2d3SAndreas Gohr                break;
2120b3fd2d3SAndreas Gohr            case 3:
2130b3fd2d3SAndreas Gohr                $operation = Request\SearchRequest::fromAsn1($opAsn1);
2140b3fd2d3SAndreas Gohr                break;
2150b3fd2d3SAndreas Gohr            case 4:
2160b3fd2d3SAndreas Gohr                $operation = Response\SearchResultEntry::fromAsn1($opAsn1);
2170b3fd2d3SAndreas Gohr                break;
2180b3fd2d3SAndreas Gohr            case 5:
2190b3fd2d3SAndreas Gohr                $operation = Response\SearchResultDone::fromAsn1($opAsn1);
2200b3fd2d3SAndreas Gohr                break;
2210b3fd2d3SAndreas Gohr            case 6:
2220b3fd2d3SAndreas Gohr                $operation = Request\ModifyRequest::fromAsn1($opAsn1);
2230b3fd2d3SAndreas Gohr                break;
2240b3fd2d3SAndreas Gohr            case 7:
2250b3fd2d3SAndreas Gohr                $operation = Response\ModifyResponse::fromAsn1($opAsn1);
2260b3fd2d3SAndreas Gohr                break;
2270b3fd2d3SAndreas Gohr            case 8:
2280b3fd2d3SAndreas Gohr                $operation = Request\AddRequest::fromAsn1($opAsn1);
2290b3fd2d3SAndreas Gohr                break;
2300b3fd2d3SAndreas Gohr            case 9:
2310b3fd2d3SAndreas Gohr                $operation = Response\AddResponse::fromAsn1($opAsn1);
2320b3fd2d3SAndreas Gohr                break;
2330b3fd2d3SAndreas Gohr            case 10:
2340b3fd2d3SAndreas Gohr                $operation = Request\DeleteRequest::fromAsn1($opAsn1);
2350b3fd2d3SAndreas Gohr                break;
2360b3fd2d3SAndreas Gohr            case 11:
2370b3fd2d3SAndreas Gohr                $operation = Response\DeleteResponse::fromAsn1($opAsn1);
2380b3fd2d3SAndreas Gohr                break;
2390b3fd2d3SAndreas Gohr            case 12:
2400b3fd2d3SAndreas Gohr                $operation = Request\ModifyDnRequest::fromAsn1($opAsn1);
2410b3fd2d3SAndreas Gohr                break;
2420b3fd2d3SAndreas Gohr            case 13:
2430b3fd2d3SAndreas Gohr                $operation = Response\ModifyDnResponse::fromAsn1($opAsn1);
2440b3fd2d3SAndreas Gohr                break;
2450b3fd2d3SAndreas Gohr            case 14:
2460b3fd2d3SAndreas Gohr                $operation = Request\CompareRequest::fromAsn1($opAsn1);
2470b3fd2d3SAndreas Gohr                break;
2480b3fd2d3SAndreas Gohr            case 15:
2490b3fd2d3SAndreas Gohr                $operation = Response\CompareResponse::fromAsn1($opAsn1);
2500b3fd2d3SAndreas Gohr                break;
2510b3fd2d3SAndreas Gohr            case 19:
2520b3fd2d3SAndreas Gohr                $operation = Response\SearchResultReference::fromAsn1($opAsn1);
2530b3fd2d3SAndreas Gohr                break;
2540b3fd2d3SAndreas Gohr            case 23:
2550b3fd2d3SAndreas Gohr                $operation = Request\ExtendedRequest::fromAsn1($opAsn1);
2560b3fd2d3SAndreas Gohr                break;
2570b3fd2d3SAndreas Gohr            case 24:
2580b3fd2d3SAndreas Gohr                $operation = Response\ExtendedResponse::fromAsn1($opAsn1);
2590b3fd2d3SAndreas Gohr                break;
2600b3fd2d3SAndreas Gohr            case 25:
2610b3fd2d3SAndreas Gohr                $operation = Response\IntermediateResponse::fromAsn1($opAsn1);
2620b3fd2d3SAndreas Gohr                break;
2630b3fd2d3SAndreas Gohr            default:
2640b3fd2d3SAndreas Gohr                throw new ProtocolException(sprintf(
2650b3fd2d3SAndreas Gohr                    'The tag %s for the LDAP operation is not supported.',
2660b3fd2d3SAndreas Gohr                    $opAsn1->getTagNumber()
2670b3fd2d3SAndreas Gohr                ));
2680b3fd2d3SAndreas Gohr        }
2690b3fd2d3SAndreas Gohr
2700b3fd2d3SAndreas Gohr        return new static(
2710b3fd2d3SAndreas Gohr            $messageId->getValue(),
2720b3fd2d3SAndreas Gohr            $operation,
2730b3fd2d3SAndreas Gohr            ...$controls
2740b3fd2d3SAndreas Gohr        );
2750b3fd2d3SAndreas Gohr    }
2760b3fd2d3SAndreas Gohr
2770b3fd2d3SAndreas Gohr    /**
2780b3fd2d3SAndreas Gohr     * @return AbstractType
2790b3fd2d3SAndreas Gohr     */
2800b3fd2d3SAndreas Gohr    abstract protected function getOperationAsn1(): AbstractType;
2810b3fd2d3SAndreas Gohr}
282