xref: /plugin/pureldap/vendor/freedsx/ldap/src/FreeDSx/Ldap/Search/RangeRetrieval.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\Search;
130b3fd2d3SAndreas Gohr
140b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Entry\Attribute;
150b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Entry\Dn;
160b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Entry\Entry;
170b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Entry\Option;
18*dad993c5SAndreas Gohruse FreeDSx\Ldap\Exception\OperationException;
190b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\RuntimeException;
200b3fd2d3SAndreas Gohruse FreeDSx\Ldap\LdapClient;
210b3fd2d3SAndreas Gohr
220b3fd2d3SAndreas Gohr/**
230b3fd2d3SAndreas Gohr * Provides simple helper APIs for retrieving ranged results for an entry attribute.
240b3fd2d3SAndreas Gohr *
250b3fd2d3SAndreas Gohr * @see https://docs.microsoft.com/en-us/windows/desktop/adsi/attribute-range-retrieval
260b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com>
270b3fd2d3SAndreas Gohr */
280b3fd2d3SAndreas Gohrclass RangeRetrieval
290b3fd2d3SAndreas Gohr{
300b3fd2d3SAndreas Gohr    /**
310b3fd2d3SAndreas Gohr     * @var LdapClient
320b3fd2d3SAndreas Gohr     */
330b3fd2d3SAndreas Gohr    protected $client;
340b3fd2d3SAndreas Gohr
350b3fd2d3SAndreas Gohr    /**
360b3fd2d3SAndreas Gohr     * @param LdapClient $client
370b3fd2d3SAndreas Gohr     */
380b3fd2d3SAndreas Gohr    public function __construct(LdapClient $client)
390b3fd2d3SAndreas Gohr    {
400b3fd2d3SAndreas Gohr        $this->client = $client;
410b3fd2d3SAndreas Gohr    }
420b3fd2d3SAndreas Gohr
430b3fd2d3SAndreas Gohr    /**
440b3fd2d3SAndreas Gohr     * Get a specific ranged attribute by name from an entry. If it does not exist it will return null.
450b3fd2d3SAndreas Gohr     *
460b3fd2d3SAndreas Gohr     * @param string|Attribute $attribute
470b3fd2d3SAndreas Gohr     */
480b3fd2d3SAndreas Gohr    public function getRanged(Entry $entry, $attribute): ?Attribute
490b3fd2d3SAndreas Gohr    {
500b3fd2d3SAndreas Gohr        $attribute = $attribute instanceof Attribute ? new Attribute($attribute->getName()) : new Attribute($attribute);
510b3fd2d3SAndreas Gohr
520b3fd2d3SAndreas Gohr        foreach ($this->getAllRanged($entry) as $rangedAttribute) {
530b3fd2d3SAndreas Gohr            if ($rangedAttribute->equals($attribute)) {
540b3fd2d3SAndreas Gohr                return $rangedAttribute;
550b3fd2d3SAndreas Gohr            }
560b3fd2d3SAndreas Gohr        }
570b3fd2d3SAndreas Gohr
580b3fd2d3SAndreas Gohr        return null;
590b3fd2d3SAndreas Gohr    }
600b3fd2d3SAndreas Gohr
610b3fd2d3SAndreas Gohr    /**
620b3fd2d3SAndreas Gohr     * Get all ranged attributes as an array from a entry.
630b3fd2d3SAndreas Gohr     *
640b3fd2d3SAndreas Gohr     * @param Entry $entry
650b3fd2d3SAndreas Gohr     * @return Attribute[]
66*dad993c5SAndreas Gohr     * @psalm-return list<Attribute>
670b3fd2d3SAndreas Gohr     */
680b3fd2d3SAndreas Gohr    public function getAllRanged(Entry $entry): array
690b3fd2d3SAndreas Gohr    {
700b3fd2d3SAndreas Gohr        $ranged = [];
710b3fd2d3SAndreas Gohr
720b3fd2d3SAndreas Gohr        foreach ($entry->getAttributes() as $attribute) {
730b3fd2d3SAndreas Gohr            if (!$attribute->hasOptions()) {
740b3fd2d3SAndreas Gohr                continue;
750b3fd2d3SAndreas Gohr            }
760b3fd2d3SAndreas Gohr            /** @var Option $option */
770b3fd2d3SAndreas Gohr            foreach ($attribute->getOptions() as $option) {
780b3fd2d3SAndreas Gohr                if ($option->isRange()) {
790b3fd2d3SAndreas Gohr                    $ranged[] = $attribute;
800b3fd2d3SAndreas Gohr                    break;
810b3fd2d3SAndreas Gohr                }
820b3fd2d3SAndreas Gohr            }
830b3fd2d3SAndreas Gohr        }
840b3fd2d3SAndreas Gohr
850b3fd2d3SAndreas Gohr        return $ranged;
860b3fd2d3SAndreas Gohr    }
870b3fd2d3SAndreas Gohr
880b3fd2d3SAndreas Gohr    /**
890b3fd2d3SAndreas Gohr     * A simple check to determine if an entry contains any ranged attributes. Optionally pass an attribute
900b3fd2d3SAndreas Gohr     *
910b3fd2d3SAndreas Gohr     * @param Entry $entry
920b3fd2d3SAndreas Gohr     * @param Attribute|string|null $attribute
930b3fd2d3SAndreas Gohr     * @return bool
940b3fd2d3SAndreas Gohr     */
950b3fd2d3SAndreas Gohr    public function hasRanged(Entry $entry, $attribute = null): bool
960b3fd2d3SAndreas Gohr    {
970b3fd2d3SAndreas Gohr        return (bool) ($attribute !== null ? $this->getRanged($entry, $attribute) : $this->getAllRanged($entry));
980b3fd2d3SAndreas Gohr    }
990b3fd2d3SAndreas Gohr
1000b3fd2d3SAndreas Gohr    /**
1010b3fd2d3SAndreas Gohr     * Check if an attribute has more range values that can be queried.
1020b3fd2d3SAndreas Gohr     *
1030b3fd2d3SAndreas Gohr     * @param Attribute $attribute
1040b3fd2d3SAndreas Gohr     * @return bool
1050b3fd2d3SAndreas Gohr     */
1060b3fd2d3SAndreas Gohr    public function hasMoreValues(Attribute $attribute): bool
1070b3fd2d3SAndreas Gohr    {
1080b3fd2d3SAndreas Gohr        if (($range = $this->getRangeOption($attribute)) === null) {
1090b3fd2d3SAndreas Gohr            return false;
1100b3fd2d3SAndreas Gohr        }
1110b3fd2d3SAndreas Gohr
1120b3fd2d3SAndreas Gohr        return $range->getHighRange() !== '*';
1130b3fd2d3SAndreas Gohr    }
1140b3fd2d3SAndreas Gohr
1150b3fd2d3SAndreas Gohr    /**
1160b3fd2d3SAndreas Gohr     * Given a specific Entry/DN and an attribute, get the next set of ranged values available. Optionally pass a third
1170b3fd2d3SAndreas Gohr     * parameter to control how many values to grab next.
1180b3fd2d3SAndreas Gohr     *
1190b3fd2d3SAndreas Gohr     * @param Entry|Dn|string $entry
1200b3fd2d3SAndreas Gohr     * @param Attribute $attribute
1210b3fd2d3SAndreas Gohr     * @param string|int $amount
1220b3fd2d3SAndreas Gohr     * @return Attribute
123*dad993c5SAndreas Gohr     * @throws OperationException
1240b3fd2d3SAndreas Gohr     */
1250b3fd2d3SAndreas Gohr    public function getMoreValues($entry, Attribute $attribute, $amount = '*'): Attribute
1260b3fd2d3SAndreas Gohr    {
1270b3fd2d3SAndreas Gohr        if (($range = $this->getRangeOption($attribute)) === null || !$this->hasMoreValues($attribute)) {
1280b3fd2d3SAndreas Gohr            return new Attribute($attribute->getName());
1290b3fd2d3SAndreas Gohr        }
1300b3fd2d3SAndreas Gohr        if ($amount !== '*') {
1310b3fd2d3SAndreas Gohr            $amount = (int) $amount + (int) $range->getHighRange();
1320b3fd2d3SAndreas Gohr        }
1330b3fd2d3SAndreas Gohr        $attrReq = new Attribute($attribute->getName());
1340b3fd2d3SAndreas Gohr        $startAt = (int) $range->getHighRange() + 1;
1350b3fd2d3SAndreas Gohr        $attrReq->getOptions()->set(Option::fromRange((string) $startAt, (string) $amount));
1360b3fd2d3SAndreas Gohr        $result = $this->client->readOrFail($entry, [$attrReq]);
1370b3fd2d3SAndreas Gohr
1380b3fd2d3SAndreas Gohr        $attrResult = $result->get($attribute->getName());
1390b3fd2d3SAndreas Gohr        if ($attrResult === null) {
1400b3fd2d3SAndreas Gohr            throw new RuntimeException(sprintf(
1410b3fd2d3SAndreas Gohr                'The attribute %s was not returned from LDAP',
1420b3fd2d3SAndreas Gohr                $attribute->getName()
1430b3fd2d3SAndreas Gohr            ));
1440b3fd2d3SAndreas Gohr        }
1450b3fd2d3SAndreas Gohr        if (($range = $this->getRangeOption($attrResult)) === null) {
1460b3fd2d3SAndreas Gohr            throw new RuntimeException(sprintf(
1470b3fd2d3SAndreas Gohr                'No ranged option received for attribute "%s" on "%s".',
1480b3fd2d3SAndreas Gohr                $attribute->getName(),
1490b3fd2d3SAndreas Gohr                $result->getDn()->toString()
1500b3fd2d3SAndreas Gohr            ));
1510b3fd2d3SAndreas Gohr        }
1520b3fd2d3SAndreas Gohr
1530b3fd2d3SAndreas Gohr        return $attrResult;
1540b3fd2d3SAndreas Gohr    }
1550b3fd2d3SAndreas Gohr
1560b3fd2d3SAndreas Gohr    /**
1570b3fd2d3SAndreas Gohr     * Given a specific entry and attribute, range retrieve all values of the attribute.
1580b3fd2d3SAndreas Gohr     *
1590b3fd2d3SAndreas Gohr     * @param Entry|Dn|string $entry
1600b3fd2d3SAndreas Gohr     * @param string|Attribute $attribute
1610b3fd2d3SAndreas Gohr     * @return Attribute
162*dad993c5SAndreas Gohr     * @throws OperationException
1630b3fd2d3SAndreas Gohr     */
1640b3fd2d3SAndreas Gohr    public function getAllValues($entry, $attribute): Attribute
1650b3fd2d3SAndreas Gohr    {
1660b3fd2d3SAndreas Gohr        $attrResult = $attribute instanceof Attribute ? new Attribute($attribute->getName()) : new Attribute($attribute);
1670b3fd2d3SAndreas Gohr        $attrResult->getOptions()->set(Option::fromRange('0'));
1680b3fd2d3SAndreas Gohr
1690b3fd2d3SAndreas Gohr        $entry = $this->client->readOrFail($entry, [$attrResult]);
1700b3fd2d3SAndreas Gohr        $attribute = $this->getRanged($entry, $attrResult);
1710b3fd2d3SAndreas Gohr        if ($attribute === null) {
1720b3fd2d3SAndreas Gohr            throw new RuntimeException(sprintf(
1730b3fd2d3SAndreas Gohr                'No ranged result received for "%s" on entry "%s".',
1740b3fd2d3SAndreas Gohr                $attrResult->getName(),
1750b3fd2d3SAndreas Gohr                $entry->getDn()->toString()
1760b3fd2d3SAndreas Gohr            ));
1770b3fd2d3SAndreas Gohr        }
1780b3fd2d3SAndreas Gohr
1790b3fd2d3SAndreas Gohr        $attrResult->add(...$attribute->getValues());
1800b3fd2d3SAndreas Gohr        while ($this->hasMoreValues($attribute)) {
1810b3fd2d3SAndreas Gohr            $attribute = $this->getMoreValues($entry, $attribute);
1820b3fd2d3SAndreas Gohr            $attrResult->add(...$attribute->getValues());
1830b3fd2d3SAndreas Gohr        }
1840b3fd2d3SAndreas Gohr
1850b3fd2d3SAndreas Gohr        return $attrResult;
1860b3fd2d3SAndreas Gohr    }
1870b3fd2d3SAndreas Gohr
1880b3fd2d3SAndreas Gohr    /**
1890b3fd2d3SAndreas Gohr     * @param Attribute $attribute
1900b3fd2d3SAndreas Gohr     * @return Option|null
1910b3fd2d3SAndreas Gohr     */
1920b3fd2d3SAndreas Gohr    protected function getRangeOption(Attribute $attribute): ?Option
1930b3fd2d3SAndreas Gohr    {
1940b3fd2d3SAndreas Gohr        /** @var Option $option */
1950b3fd2d3SAndreas Gohr        foreach ($attribute->getOptions() as $option) {
1960b3fd2d3SAndreas Gohr            if ($option->isRange()) {
1970b3fd2d3SAndreas Gohr                return $option;
1980b3fd2d3SAndreas Gohr            }
1990b3fd2d3SAndreas Gohr        }
2000b3fd2d3SAndreas Gohr
2010b3fd2d3SAndreas Gohr        return null;
2020b3fd2d3SAndreas Gohr    }
2030b3fd2d3SAndreas Gohr}
204