Passed
Pull Request — master (#18)
by Matt
02:29
created

LDAPIterator::fetchPagedResult()   B

Complexity

Conditions 10
Paths 41

Size

Total Lines 53
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 29
nc 41
nop 0
dl 0
loc 53
rs 7.6666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SilverStripe\LDAP\Iterators;
4
5
use \Iterator;
6
use Zend\Ldap\Ldap;
7
use Zend\Ldap\Exception;
8
use Zend\Ldap\ErrorHandler;
9
use Zend\Ldap\Exception\LdapException;
10
11
/**
12
 * Class LDAPIterator
13
 * @package SilverStripe\LDAP\Iterators
14
 *
15
 * Original implementation provided by ThaDafinser: https://gist.github.com/ThaDafinser/1d081bed8e5e6505e97bedf5863a187c
16
 * See also: https://github.com/zendframework/zend-ldap/issues/41
17
 *
18
 * This uses the ldap_control_paged_result() and ldap_control_paged_result_response() functions to request multiple
19
 * pages of data from LDAP as needed, to ensure that the end result is everything that matches the requested filter,
20
 * regardless of the maximum page size set by the LDAP server.
21
 *
22
 * Note: The page size attribute provided to this class must be less than the LDAP server's page size, or objects may
23
 * still be missing from results.
24
 */
25
final class LDAPIterator implements Iterator
26
{
27
    private $ldap;
28
    private $filter;
29
    private $baseDn;
30
    private $returnAttributes;
31
    private $pageSize;
32
    private $resolveRangedAttributes;
33
    private $entries;
34
    private $current;
35
    /**
36
     * Required for paging
37
     *
38
     * @var unknown
0 ignored issues
show
Bug introduced by
The type SilverStripe\LDAP\Iterators\unknown was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
39
     */
40
    private $currentResult;
0 ignored issues
show
introduced by
The private property $currentResult is not used, and could be removed.
Loading history...
41
    /**
42
     * Required for paging
43
     *
44
     * @var unknown
45
     */
46
    private $cookie = true;
47
48
    /**
49
     * @param Ldap $ldap The Zend\Ldap\Ldap object that this iterator will use to retrieve results from
50
     * @param string $filter An LDAP search filter (e.g. "(&(objectClass=user)(!(objectClass=computer)))")
51
     * @param string|null $baseDn The Base DN to search from (or null to search from the connection root)
52
     * @param array|null $returnAttributes The attributes to request from the LDAP server, or null to request all
53
     * @param int $pageSize Number of results per page. This *must* be less than the LDAP server MaxPageSize setting
54
     * @param bool $resolveRangedAttributes Whether or not to return ranged attributes
55
     */
56
    public function __construct(Ldap $ldap, $filter = "", $baseDn = null, array $returnAttributes = null, $pageSize = 250, $resolveRangedAttributes = false)
57
    {
58
        $this->ldap = $ldap;
59
        $this->filter = $filter;
60
        $this->baseDn = $baseDn;
61
        $this->returnAttributes = $returnAttributes;
62
        $this->pageSize = $pageSize;
63
        $this->resolveRangedAttributes = $resolveRangedAttributes;
64
    }
65
66
    private function getLdap()
67
    {
68
        return $this->ldap;
69
    }
70
71
    private function getFilter()
72
    {
73
        return $this->filter;
74
    }
75
76
    private function getBaseDn()
77
    {
78
        return $this->baseDn;
79
    }
80
81
    private function getReturnAttributes()
82
    {
83
        return $this->returnAttributes;
84
    }
85
86
    private function getPageSize()
87
    {
88
        return $this->pageSize;
89
    }
90
91
    /**
92
     * @return bool
93
     */
94
    private function getResolveRangedAttributes()
95
    {
96
        return $this->resolveRangedAttributes;
97
    }
98
99
    private function fetchPagedResult()
100
    {
101
        if ($this->cookie === null || $this->cookie === '') {
102
            return false;
103
        }
104
105
        if ($this->cookie === true) {
0 ignored issues
show
introduced by
The condition $this->cookie is always false. If $this->cookie can have other possible types, add them to src/Iterators/LDAPIterator.php:44
Loading history...
106
            // First fetch!
107
            $this->cookie = '';
108
        }
109
110
        $ldap = $this->getLdap();
111
        $resource = $ldap->getResource();
112
113
        $baseDn = $this->getBaseDn();
114
        if (!$baseDn) {
115
            $baseDn = $ldap->getBaseDn();
116
        }
117
118
        ldap_control_paged_result($resource, $this->getPageSize(), true, $this->cookie);
119
        if ($this->getReturnAttributes() !== null) {
120
            $resultResource = ldap_search($resource, $baseDn, $this->getFilter(), $this->getReturnAttributes());
121
        } else {
122
            $resultResource = ldap_search($resource, $baseDn, $this->getFilter());
123
        }
124
        if (! is_resource($resultResource)) {
125
            /*
126
             * @TODO better exception msg
127
             */
128
            throw new \Exception('ldap_search returned something wrong...' . ldap_error($resource));
129
        }
130
131
        $entries = ldap_get_entries($resource, $resultResource);
132
        if ($entries === false) {
133
            throw new LdapException($ldap, 'Entries could not get fetched');
134
        }
135
        $entries = $this->getConvertedEntries($entries);
136
137
        ErrorHandler::start();
138
        $response = ldap_control_paged_result_response($resource, $resultResource, $this->cookie);
139
        ErrorHandler::stop();
140
141
        if ($response !== true) {
142
            throw new LdapException($ldap, 'Paged result was empty');
143
        }
144
145
        if ($this->entries === null) {
146
            $this->entries = [];
147
        }
148
149
        $this->entries = array_merge($this->entries, $entries);
150
151
        return true;
152
    }
153
    private function getConvertedEntries(array $entries)
154
    {
155
        $result = [];
156
157
        foreach ($entries as $key => $entry) {
158
            if ($key === 'count') {
159
                continue;
160
            }
161
162
            $result[$key] = $this->getConvertedEntry($entry);
163
        }
164
165
        return $result;
166
    }
167
    private function getConvertedEntry(array $entry)
168
    {
169
        $result = [];
170
171
        foreach ($entry as $key => $value) {
172
            if (is_int($key)) {
173
                continue;
174
            }
175
            if ($key === 'count') {
176
                continue;
177
            }
178
179
            if (isset($value['count'])) {
180
                unset($value['count']);
181
            }
182
183
            $result[$key] = $value;
184
        }
185
186
        if ($this->getResolveRangedAttributes() === true) {
187
            $result = $this->resolveRangedAttributes($result);
188
        }
189
190
        return $result;
191
    }
192
    private function resolveRangedAttributes(array $row)
193
    {
194
        $result = [];
195
        foreach ($row as $key => $value) {
196
            $keyExploded = explode(';range=', $key);
197
198
            if (count($keyExploded) === 2) {
199
                $range = explode('-', $keyExploded[1]);
200
                $offsetAndLimit = (int) $range[1] + 1;
201
202
                $result[$keyExploded[0]] = array_merge($value, $this->getAttributeRecursive($row['dn'], $keyExploded[0], $offsetAndLimit, $offsetAndLimit));
203
            } else {
204
                $result[$key] = $value;
205
            }
206
        }
207
208
        return $result;
209
    }
210
    private function getAttributeRecursive(string $dn, string $attrName, int $offset, int $maxPerRequest)
211
    {
212
        $attributeValue = [];
213
214
        $limit = $offset + $maxPerRequest - 1;
215
        $searchedAttribute = $attrName . ';range=' . $offset . '-' . $limit;
216
217
        $ldap = $this->getLdap();
218
        $entry = $ldap->getEntry($dn, [
219
            $searchedAttribute
220
        ], true);
221
        foreach ($entry as $key => $value) {
222
            // skip DN and other fields (if returned)
223
            if (stripos($key, $attrName) === false) {
224
                continue;
225
            }
226
227
            $attributeValue = $value;
228
229
            // range result (pagination)
230
            $keyExploded = explode(';range=', $key);
231
232
            $range = explode('-', $keyExploded[1]);
233
            $rangeEnd = (int) $range[1];
234
235
            if ($range[0] == $offset && $range[1] == $limit) {
236
                // more pages, there are more pages to fetch
237
                $attributeValue = array_merge($attributeValue, $this->getAttributeRecursive($dn, $attrName, $rangeEnd + 1, $maxPerRequest));
238
            }
239
        }
240
241
        return $attributeValue;
242
    }
243
    public function current()
244
    {
245
        if (! is_array($this->current)) {
246
            $this->rewind();
247
        }
248
        if (! is_array($this->current)) {
249
            return;
250
        }
251
252
        return $this->current;
253
    }
254
    public function key()
255
    {
256
        if (! is_array($this->current)) {
257
            $this->rewind();
258
        }
259
        if (! is_array($this->current)) {
260
            return;
261
        }
262
263
        return $this->current['dn'];
264
    }
265
    public function next()
266
    {
267
        // initial
268
        if ($this->entries === null) {
269
            $this->fetchPagedResult();
270
        }
271
272
        next($this->entries);
273
274
        $this->current = current($this->entries);
275
    }
276
    public function rewind()
277
    {
278
        // initial
279
        if ($this->entries === null) {
280
            $this->fetchPagedResult();
281
        }
282
283
        reset($this->entries);
284
        $this->current = current($this->entries);
285
    }
286
    public function valid()
287
    {
288
        if (is_array($this->current)) {
289
            return true;
290
        }
291
292
        return $this->fetchPagedResult();
293
    }
294
}
295