xref: /plugin/pureldap/vendor/freedsx/ldap/src/FreeDSx/Ldap/Protocol/ClientProtocolHandler.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
14*dad993c5SAndreas Gohruse FreeDSx\Asn1\Exception\EncoderException;
150b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Control\Control;
160b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Control\ControlBag;
170b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Entry\Entry;
18*dad993c5SAndreas Gohruse FreeDSx\Ldap\Exception\BindException;
190b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\ConnectionException;
200b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\OperationException;
21*dad993c5SAndreas Gohruse FreeDSx\Ldap\Exception\ProtocolException;
22*dad993c5SAndreas Gohruse FreeDSx\Ldap\Exception\ReferralException;
230b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\UnsolicitedNotificationException;
240b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Request\RequestInterface;
250b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Response\ExtendedResponse;
260b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Response\SearchResponse;
270b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operations;
280b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Protocol\ClientProtocolHandler\ClientProtocolContext;
290b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Protocol\Factory\ClientProtocolHandlerFactory;
300b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Protocol\Queue\ClientQueue;
31*dad993c5SAndreas Gohruse FreeDSx\Sasl\Exception\SaslException;
320b3fd2d3SAndreas Gohruse FreeDSx\Socket\Exception\ConnectionException as SocketException;
330b3fd2d3SAndreas Gohruse FreeDSx\Socket\SocketPool;
340b3fd2d3SAndreas Gohr
350b3fd2d3SAndreas Gohr/**
360b3fd2d3SAndreas Gohr * Handles client specific protocol communication details.
370b3fd2d3SAndreas Gohr *
380b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com>
390b3fd2d3SAndreas Gohr */
400b3fd2d3SAndreas Gohrclass ClientProtocolHandler
410b3fd2d3SAndreas Gohr{
42*dad993c5SAndreas Gohr    public const ROOTDSE_ATTRIBUTES = [
430b3fd2d3SAndreas Gohr        'supportedSaslMechanisms',
440b3fd2d3SAndreas Gohr        'supportedControl',
450b3fd2d3SAndreas Gohr        'supportedLDAPVersion',
460b3fd2d3SAndreas Gohr    ];
470b3fd2d3SAndreas Gohr
480b3fd2d3SAndreas Gohr    /**
490b3fd2d3SAndreas Gohr     * @var SocketPool
500b3fd2d3SAndreas Gohr     */
510b3fd2d3SAndreas Gohr    protected $pool;
520b3fd2d3SAndreas Gohr
530b3fd2d3SAndreas Gohr    /**
540b3fd2d3SAndreas Gohr     * @var ClientQueue|null
550b3fd2d3SAndreas Gohr     */
560b3fd2d3SAndreas Gohr    protected $queue;
570b3fd2d3SAndreas Gohr
580b3fd2d3SAndreas Gohr    /**
590b3fd2d3SAndreas Gohr     * @var array
600b3fd2d3SAndreas Gohr     */
610b3fd2d3SAndreas Gohr    protected $options;
620b3fd2d3SAndreas Gohr
630b3fd2d3SAndreas Gohr    /**
640b3fd2d3SAndreas Gohr     * @var ControlBag
650b3fd2d3SAndreas Gohr     */
660b3fd2d3SAndreas Gohr    protected $controls;
670b3fd2d3SAndreas Gohr
680b3fd2d3SAndreas Gohr    /**
690b3fd2d3SAndreas Gohr     * @var ClientProtocolHandlerFactory
700b3fd2d3SAndreas Gohr     */
710b3fd2d3SAndreas Gohr    protected $protocolHandlerFactory;
720b3fd2d3SAndreas Gohr
730b3fd2d3SAndreas Gohr    /**
740b3fd2d3SAndreas Gohr     * @var null|Entry
750b3fd2d3SAndreas Gohr     */
760b3fd2d3SAndreas Gohr    protected $rootDse;
770b3fd2d3SAndreas Gohr
780b3fd2d3SAndreas Gohr    public function __construct(array $options, ClientQueue $queue = null, SocketPool $pool = null, ClientProtocolHandlerFactory $clientProtocolHandlerFactory = null)
790b3fd2d3SAndreas Gohr    {
800b3fd2d3SAndreas Gohr        $this->options = $options;
810b3fd2d3SAndreas Gohr        $this->pool = $pool ?? new SocketPool($options);
820b3fd2d3SAndreas Gohr        $this->protocolHandlerFactory = $clientProtocolHandlerFactory ?? new ClientProtocolHandlerFactory();
830b3fd2d3SAndreas Gohr        $this->controls = new ControlBag();
840b3fd2d3SAndreas Gohr        $this->queue = $queue;
850b3fd2d3SAndreas Gohr    }
860b3fd2d3SAndreas Gohr
870b3fd2d3SAndreas Gohr    /**
880b3fd2d3SAndreas Gohr     * @return ControlBag
890b3fd2d3SAndreas Gohr     */
900b3fd2d3SAndreas Gohr    public function controls(): ControlBag
910b3fd2d3SAndreas Gohr    {
920b3fd2d3SAndreas Gohr        return $this->controls;
930b3fd2d3SAndreas Gohr    }
940b3fd2d3SAndreas Gohr
950b3fd2d3SAndreas Gohr    /**
960b3fd2d3SAndreas Gohr     * Make a single search request to fetch the RootDSE. Handle the various errors that could occur.
970b3fd2d3SAndreas Gohr     *
98*dad993c5SAndreas Gohr     * @param bool $reload
99*dad993c5SAndreas Gohr     * @return Entry
1000b3fd2d3SAndreas Gohr     * @throws ConnectionException
1010b3fd2d3SAndreas Gohr     * @throws OperationException
102*dad993c5SAndreas Gohr     * @throws SocketException
1030b3fd2d3SAndreas Gohr     * @throws UnsolicitedNotificationException
104*dad993c5SAndreas Gohr     * @throws EncoderException
105*dad993c5SAndreas Gohr     * @throws BindException
106*dad993c5SAndreas Gohr     * @throws ProtocolException
107*dad993c5SAndreas Gohr     * @throws ReferralException
108*dad993c5SAndreas Gohr     * @throws SaslException
1090b3fd2d3SAndreas Gohr     */
1100b3fd2d3SAndreas Gohr    public function fetchRootDse(bool $reload = false): Entry
1110b3fd2d3SAndreas Gohr    {
1120b3fd2d3SAndreas Gohr        if ($reload === false && $this->rootDse !== null) {
1130b3fd2d3SAndreas Gohr            return $this->rootDse;
1140b3fd2d3SAndreas Gohr        }
1150b3fd2d3SAndreas Gohr        $message = $this->send(Operations::read('', ...self::ROOTDSE_ATTRIBUTES));
1160b3fd2d3SAndreas Gohr        if ($message === null) {
1170b3fd2d3SAndreas Gohr            throw new OperationException('Expected a search response for the RootDSE. None received.');
1180b3fd2d3SAndreas Gohr        }
1190b3fd2d3SAndreas Gohr
1200b3fd2d3SAndreas Gohr        $searchResponse = $message->getResponse();
1210b3fd2d3SAndreas Gohr        if (!$searchResponse instanceof SearchResponse) {
1220b3fd2d3SAndreas Gohr            throw new OperationException('Expected a search response for the RootDSE. None received.');
1230b3fd2d3SAndreas Gohr        }
1240b3fd2d3SAndreas Gohr
1250b3fd2d3SAndreas Gohr        $entry = $searchResponse->getEntries()->first();
1260b3fd2d3SAndreas Gohr        if ($entry === null) {
1270b3fd2d3SAndreas Gohr            throw new OperationException('Expected a single entry for the RootDSE. None received.');
1280b3fd2d3SAndreas Gohr        }
1290b3fd2d3SAndreas Gohr        $this->rootDse = $entry;
1300b3fd2d3SAndreas Gohr
1310b3fd2d3SAndreas Gohr        return $entry;
1320b3fd2d3SAndreas Gohr    }
1330b3fd2d3SAndreas Gohr
1340b3fd2d3SAndreas Gohr    /**
135*dad993c5SAndreas Gohr     * @param RequestInterface $request
136*dad993c5SAndreas Gohr     * @param Control ...$controls
137*dad993c5SAndreas Gohr     * @return LdapMessageResponse|null
1380b3fd2d3SAndreas Gohr     * @throws ConnectionException
1390b3fd2d3SAndreas Gohr     * @throws OperationException
140*dad993c5SAndreas Gohr     * @throws SocketException
1410b3fd2d3SAndreas Gohr     * @throws UnsolicitedNotificationException
142*dad993c5SAndreas Gohr     * @throws EncoderException
143*dad993c5SAndreas Gohr     * @throws BindException
144*dad993c5SAndreas Gohr     * @throws ProtocolException
145*dad993c5SAndreas Gohr     * @throws ReferralException
146*dad993c5SAndreas Gohr     * @throws SaslException
1470b3fd2d3SAndreas Gohr     */
1480b3fd2d3SAndreas Gohr    public function send(RequestInterface $request, Control ...$controls): ?LdapMessageResponse
1490b3fd2d3SAndreas Gohr    {
1500b3fd2d3SAndreas Gohr        try {
1510b3fd2d3SAndreas Gohr            $context = new ClientProtocolContext(
1520b3fd2d3SAndreas Gohr                $request,
1530b3fd2d3SAndreas Gohr                $controls,
1540b3fd2d3SAndreas Gohr                $this,
1550b3fd2d3SAndreas Gohr                $this->queue(),
1560b3fd2d3SAndreas Gohr                $this->options
1570b3fd2d3SAndreas Gohr            );
1580b3fd2d3SAndreas Gohr
1590b3fd2d3SAndreas Gohr            $messageFrom = $this->protocolHandlerFactory->forRequest($request)->handleRequest($context);
1600b3fd2d3SAndreas Gohr            $messageTo = $context->messageToSend();
1610b3fd2d3SAndreas Gohr            if ($messageFrom !== null) {
1620b3fd2d3SAndreas Gohr                $messageFrom = $this->protocolHandlerFactory->forResponse($messageTo->getRequest(), $messageFrom->getResponse())->handleResponse(
1630b3fd2d3SAndreas Gohr                    $messageTo,
1640b3fd2d3SAndreas Gohr                    $messageFrom,
1650b3fd2d3SAndreas Gohr                    $this->queue(),
1660b3fd2d3SAndreas Gohr                    $this->options
1670b3fd2d3SAndreas Gohr                );
1680b3fd2d3SAndreas Gohr            }
1690b3fd2d3SAndreas Gohr
1700b3fd2d3SAndreas Gohr            return $messageFrom;
1710b3fd2d3SAndreas Gohr        } catch (UnsolicitedNotificationException $exception) {
1720b3fd2d3SAndreas Gohr            if ($exception->getOid() === ExtendedResponse::OID_NOTICE_OF_DISCONNECTION) {
1730b3fd2d3SAndreas Gohr                $this->queue()->close();
1740b3fd2d3SAndreas Gohr                throw new ConnectionException(
1750b3fd2d3SAndreas Gohr                    sprintf('The remote server has disconnected the session. %s', $exception->getMessage()),
1760b3fd2d3SAndreas Gohr                    $exception->getCode()
1770b3fd2d3SAndreas Gohr                );
1780b3fd2d3SAndreas Gohr            }
1790b3fd2d3SAndreas Gohr
1800b3fd2d3SAndreas Gohr            throw $exception;
1810b3fd2d3SAndreas Gohr        } catch (SocketException $exception) {
1820b3fd2d3SAndreas Gohr            throw new ConnectionException(
1830b3fd2d3SAndreas Gohr                $exception->getMessage(),
1840b3fd2d3SAndreas Gohr                $exception->getCode(),
1850b3fd2d3SAndreas Gohr                $exception
1860b3fd2d3SAndreas Gohr            );
1870b3fd2d3SAndreas Gohr        }
1880b3fd2d3SAndreas Gohr    }
1890b3fd2d3SAndreas Gohr
1900b3fd2d3SAndreas Gohr    /**
1910b3fd2d3SAndreas Gohr     * @return bool
1920b3fd2d3SAndreas Gohr     */
1930b3fd2d3SAndreas Gohr    public function isConnected(): bool
1940b3fd2d3SAndreas Gohr    {
1950b3fd2d3SAndreas Gohr        return ($this->queue !== null && $this->queue->isConnected());
1960b3fd2d3SAndreas Gohr    }
1970b3fd2d3SAndreas Gohr
1980b3fd2d3SAndreas Gohr    /**
1990b3fd2d3SAndreas Gohr     * @throws SocketException
2000b3fd2d3SAndreas Gohr     */
2010b3fd2d3SAndreas Gohr    protected function queue(): ClientQueue
2020b3fd2d3SAndreas Gohr    {
2030b3fd2d3SAndreas Gohr        if ($this->queue === null) {
2040b3fd2d3SAndreas Gohr            $this->queue = new ClientQueue($this->pool);
2050b3fd2d3SAndreas Gohr        }
2060b3fd2d3SAndreas Gohr
2070b3fd2d3SAndreas Gohr        return $this->queue;
2080b3fd2d3SAndreas Gohr    }
2090b3fd2d3SAndreas Gohr}
210