Completed
Push — master ( 519f76...318dd5 )
by Raffael
24:13 queued 15:29
created

Ldap   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 433
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 83.52%

Importance

Changes 0
Metric Value
wmc 60
lcom 1
cbo 8
dl 0
loc 433
ccs 147
cts 176
cp 0.8352
rs 3.6
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 2
B setup() 0 36 6
A shutdown() 0 6 1
B setLdapOptions() 0 30 9
A change() 0 26 4
A delete() 0 12 2
A create() 0 18 3
A transformQuery() 0 20 5
A count() 0 6 1
A getAll() 0 17 2
B getDiff() 0 39 6
A getOne() 0 17 3
A normalizeDn() 0 12 2
B prepareRawObject() 0 34 9
A moveLdapObject() 0 16 2
A getDn() 0 11 3

How to fix   Complexity   

Complex Class

Complex classes like Ldap often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Ldap, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * tubee
7
 *
8
 * @copyright   Copryright (c) 2017-2019 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Tubee\Endpoint;
13
14
use Dreamscapes\Ldap\Core\Ldap as LdapServer;
15
use Generator;
16
use InvalidArgumentException;
17
use Psr\Log\LoggerInterface;
18
use Tubee\AttributeMap\AttributeMapInterface;
19
use Tubee\Collection\CollectionInterface;
20
use Tubee\Endpoint\Ldap\Exception as LdapEndpointException;
21
use Tubee\Endpoint\Ldap\QueryTransformer;
22
use Tubee\EndpointObject\EndpointObjectInterface;
23
use Tubee\Workflow\Factory as WorkflowFactory;
24
25
class Ldap extends AbstractEndpoint
26
{
27
    use LoggerTrait;
28
29
    /**
30
     * Kind.
31
     */
32
    public const KIND = 'LdapEndpoint';
33
34
    /**
35
     * Ldap.
36
     *
37
     * @var Ldap
38
     */
39
    protected $ldap;
40
41
    /**
42
     * Uri.
43
     *
44
     * @var string
45
     */
46
    protected $uri = 'ldap://127.0.0.1:389';
47
48
    /**
49
     * Binddn.
50
     *
51
     * @var string
52
     */
53
    protected $binddn;
54
55
    /**
56
     * Bindpw.
57
     *
58
     * @var string
59
     */
60
    protected $bindpw;
61
62
    /**
63
     * Basedn.
64
     *
65
     * @var string
66
     */
67
    protected $basedn = '';
68
69
    /**
70
     * tls.
71
     *
72
     * @var bool
73
     */
74
    protected $tls = false;
75
76
    /**
77
     *  Options.
78
     *
79
     * @var array
80
     */
81
    protected $options = [];
82
83
    /**
84
     * Init endpoint.
85
     */
86 32
    public function __construct(string $name, string $type, LdapServer $ldap, CollectionInterface $collection, WorkflowFactory $workflow, LoggerInterface $logger, array $resource = [])
87
    {
88 32
        $this->ldap = $ldap;
0 ignored issues
show
Documentation Bug introduced by
It seems like $ldap of type object<Dreamscapes\Ldap\Core\Ldap> is incompatible with the declared type object<Tubee\Endpoint\Ldap> of property $ldap.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
89 32
        $this->identifier = 'entrydn';
90
91 32
        if (isset($resource['data']['resource'])) {
92
            $this->setLdapOptions($resource['data']['resource']);
93
        }
94
95 32
        parent::__construct($name, $type, $collection, $workflow, $logger, $resource);
96 32
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101 4
    public function setup(bool $simulate = false): EndpointInterface
102
    {
103 4
        $this->logger->debug('connect to ldap server ['.$this->uri.']', [
104 4
            'category' => get_class($this),
105
        ]);
106
107 4
        if (null === $this->binddn) {
108 3
            $this->logger->warning('no binddn set for ldap connection, you should avoid anonymous bind', [
109 3
                'category' => get_class($this),
110
            ]);
111
        }
112
113 4
        if (false === $this->tls && 'ldaps' !== substr($this->uri, 0, 5)) {
114 3
            $this->logger->warning('neither tls nor ldaps enabled for ldap connection, it is strongly reccommended to encrypt ldap connections', [
115 3
                'category' => get_class($this),
116
            ]);
117
        }
118
119 4
        $this->ldap->connect($this->uri);
0 ignored issues
show
Bug introduced by
The method connect() does not seem to exist on object<Tubee\Endpoint\Ldap>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
120
121 4
        foreach ($this->options as $opt => $value) {
122 1
            $this->ldap->setOption(constant($opt), $value);
0 ignored issues
show
Bug introduced by
The method setOption() does not exist on Tubee\Endpoint\Ldap. Did you maybe mean setOptions()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
123
        }
124
125 4
        if (true === $this->tls) {
126 1
            $this->ldap->startTls();
0 ignored issues
show
Bug introduced by
The method startTls() does not seem to exist on object<Tubee\Endpoint\Ldap>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
127
        }
128
129 4
        $this->logger->info('bind to ldap server ['.$this->uri.'] with binddn ['.$this->binddn.']', [
130 4
            'category' => get_class($this),
131
        ]);
132
133 4
        $this->ldap->bind($this->binddn, $this->bindpw);
0 ignored issues
show
Bug introduced by
The method bind() does not seem to exist on object<Tubee\Endpoint\Ldap>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
134
135 4
        return $this;
136
    }
137
138
    /**
139
     * {@inheritdoc}
140
     */
141 3
    public function setLdapOptions(?array $config = null): EndpointInterface
142
    {
143 3
        if ($config === null) {
144
            return $this;
145
        }
146
147 3
        foreach ($config as $option => $value) {
148
            switch ($option) {
149 3
                case 'options':
150 1
                    $this->options = $value;
151
152 1
                    break;
153 2
                case 'uri':
154 2
                case 'binddn':
155 1
                case 'bindpw':
156 1
                case 'basedn':
157 1
                    $this->{$option} = (string) $value;
158
159 1
                    break;
160 1
                case 'tls':
161 1
                    $this->tls = (bool) $value;
162
163 1
                    break;
164
                default:
165
                    throw new InvalidArgumentException('unknown ldap option '.$option.' given');
166
            }
167
        }
168
169 3
        return $this;
170
    }
171
172
    /**
173
     * {@inheritdoc}
174
     */
175 1
    public function shutdown(bool $simulate = false): EndpointInterface
176
    {
177 1
        $this->ldap->close();
0 ignored issues
show
Bug introduced by
The method close() does not seem to exist on object<Tubee\Endpoint\Ldap>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
178
179 1
        return $this;
180
    }
181
182
    /**
183
     * {@inheritdoc}
184
     */
185 4
    public function change(AttributeMapInterface $map, array $diff, array $object, EndpointObjectInterface $endpoint_object, bool $simulate = false): ?string
186
    {
187 4
        $object = array_change_key_case($object);
188 4
        $endpoint_object = $endpoint_object->getData();
189 4
        $dn = $this->getDn($object, $endpoint_object);
190
191 4
        $this->logChange($dn, $diff);
192
193 4
        if (strtolower($dn) !== strtolower($endpoint_object['entrydn'])) {
194 1
            $this->moveLdapObject($dn, $endpoint_object['entrydn'], $simulate);
195 1
            $rdn_attr = explode('=', $dn);
196 1
            $rdn_attr = strtolower(array_shift($rdn_attr));
197
198 1
            if (isset($diff[$rdn_attr])) {
199
                unset($diff[$rdn_attr]);
200
            }
201
        }
202
203 4
        if ($simulate === false) {
204 3
            $this->ldap->modifyBatch($dn, $diff);
0 ignored issues
show
Bug introduced by
The method modifyBatch() does not seem to exist on object<Tubee\Endpoint\Ldap>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
205
206 3
            return $dn;
207
        }
208
209 1
        return null;
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215 2
    public function delete(AttributeMapInterface $map, array $object, EndpointObjectInterface $endpoint_object, bool $simulate = false): bool
216
    {
217 2
        $endpoint_object = $endpoint_object->getData();
218 2
        $dn = $this->getDn($object, $endpoint_object);
219 2
        $this->logDelete($dn);
220
221 2
        if ($simulate === false) {
222 1
            $this->ldap->delete($dn);
0 ignored issues
show
Bug introduced by
The call to delete() misses some required arguments starting with $object.
Loading history...
Documentation introduced by
$dn is of type string, but the function expects a object<Tubee\AttributeMap\AttributeMapInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
223
        }
224
225 2
        return true;
226
    }
227
228
    /**
229
     * {@inheritdoc}
230
     */
231 3
    public function create(AttributeMapInterface $map, array $object, bool $simulate = false): ?string
232
    {
233 3
        $dn = $this->getDn($object);
234
235 2
        if (isset($object['entrydn'])) {
236 2
            unset($object['entrydn']);
237
        }
238
239 2
        $this->logCreate($object);
240
241 2
        if ($simulate === false) {
242 1
            $this->ldap->add($dn, $object);
0 ignored issues
show
Bug introduced by
The method add() does not seem to exist on object<Tubee\Endpoint\Ldap>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
243
244 1
            return $dn;
245
        }
246
247 1
        return null;
248
    }
249
250
    /**
251
     * {@inheritdoc}
252
     */
253 11
    public function transformQuery(?array $query = null)
254
    {
255 11
        if ($this->filter_all !== null && empty($query)) {
256
            return QueryTransformer::transform($this->getFilterAll());
257
        }
258 11
        if (!empty($query)) {
259 11
            if ($this->filter_all === null) {
260 10
                return QueryTransformer::transform($query);
261
            }
262
263 1
            return QueryTransformer::transform([
264
                    '$and' => [
265 1
                        $this->getFilterAll(),
266 1
                        $query,
267
                    ],
268
                ]);
269
        }
270
271
        return '(objectClass=*)';
272
    }
273
274
    /**
275
     * {@inheritdoc}
276
     */
277
    public function count(?array $query = null): int
278
    {
279
        $filter = $this->transformQuery($query);
280
281
        return $this->ldap->ldapSearch($this->basedn, $filter)->countEntries();
0 ignored issues
show
Bug introduced by
The method ldapSearch() does not seem to exist on object<Tubee\Endpoint\Ldap>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
282
    }
283
284
    /**
285
     * {@inheritdoc}
286
     */
287
    public function getAll(?array $query = null): Generator
288
    {
289
        $filter = $this->transformQuery($query);
290
        $this->logGetAll($filter);
291
292
        $i = 0;
293
        $this->logger->debug(json_encode([$filter, $this->basedn]));
294
295
        $result = $this->ldap->ldapSearch($this->basedn, $filter)->getEntries();
0 ignored issues
show
Bug introduced by
The method ldapSearch() does not seem to exist on object<Tubee\Endpoint\Ldap>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
296
        array_shift($result);
297
298
        foreach ($result as $object) {
299
            yield $this->build($this->prepareRawObject($object));
300
        }
301
302
        return $i;
303
    }
304
305
    /**
306
     * {@inheritdoc}
307
     */
308 6
    public function getDiff(AttributeMapInterface $map, array $diff): array
309
    {
310 6
        $result = [];
311 6
        foreach ($diff as $attribute => $update) {
312 5
            if ($attribute === 'entrydn') {
313 1
                continue;
314
            }
315
316 4
            switch ($update['action']) {
317 4
                case AttributeMapInterface::ACTION_REPLACE:
318 2
                    $result[] = [
319 2
                        'attrib' => $attribute,
320 2
                        'modtype' => LDAP_MODIFY_BATCH_REPLACE,
321 2
                        'values' => (array) $update['value'],
322
                    ];
323
324 2
                break;
325 2
                case AttributeMapInterface::ACTION_REMOVE:
326 1
                    $result[] = [
327 1
                        'attrib' => $attribute,
328 1
                        'modtype' => LDAP_MODIFY_BATCH_REMOVE_ALL,
329
                    ];
330
331 1
                break;
332 1
                case AttributeMapInterface::ACTION_ADD:
333 1
                    $result[] = [
334 1
                        'attrib' => $attribute,
335 1
                        'modtype' => LDAP_MODIFY_BATCH_ADD,
336 1
                        'values' => (array) $update['value'],
337
                    ];
338
339 1
                break;
340
                default:
341 4
                    throw new InvalidArgumentException('unknown diff action '.$update['action'].' given');
342
            }
343
        }
344
345 6
        return $result;
346
    }
347
348
    /**
349
     * {@inheritdoc}
350
     */
351 4
    public function getOne(array $object, ?array $attributes = []): EndpointObjectInterface
352
    {
353 4
        $filter = $this->transformQuery($this->getFilterOne($object));
354 3
        $this->logGetOne($filter);
355
356 3
        $result = $this->ldap->ldapSearch($this->basedn, $filter, $attributes);
0 ignored issues
show
Bug introduced by
The method ldapSearch() does not seem to exist on object<Tubee\Endpoint\Ldap>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
357 3
        $count = $result->countEntries();
358
359 3
        if ($count > 1) {
360
            throw new Exception\ObjectMultipleFound('found more than one object with filter '.$filter);
361
        }
362 3
        if ($count === 0) {
363 1
            throw new Exception\ObjectNotFound('no object found with filter '.$filter);
364
        }
365
366 2
        return $this->build($this->prepareRawObject($result->getEntries()[0]), $filter);
367
    }
368
369
    /**
370
     * Normalize dn.
371
     */
372 8
    protected function normalizeDn(string $dn): string
373
    {
374 8
        $parts = explode(',', $dn);
375 8
        $normalized = [];
376
377 8
        foreach ($parts as $part) {
378 8
            $subs = explode('=', $part);
379 8
            $normalized[] = strtolower($subs[0]).'='.$subs[1];
380
        }
381
382 8
        return join(',', $normalized);
383
    }
384
385
    /**
386
     * Prepare object.
387
     */
388 2
    protected function prepareRawObject(array $result): array
389
    {
390 2
        $object = [];
391 2
        foreach ($result as $key => $attr) {
392 2
            if ($key === 'count') {
393
                continue;
394
            }
395
396 2
            if ($key === 'dn') {
397
                $object['entrydn'] = $this->normalizeDn($attr);
398 2
            } elseif (!is_int($key)) {
399 2
                if ($attr['count'] === 1) {
400 2
                    if (json_encode($attr[0]) === false) {
401 1
                        $object[$key] = base64_encode($attr[0]);
402
                    } else {
403 1
                        $object[$key] = $attr[0];
404
                    }
405
                } else {
406
                    $val = $attr;
407
                    unset($val['count']);
408
409
                    foreach ($val as $v) {
410
                        if (json_encode($v) === false) {
411
                            $object[$key][] = base64_encode($v);
412
                        } else {
413
                            $object[$key][] = $v;
414
                        }
415
                    }
416
                }
417
            }
418
        }
419
420 2
        return $object;
421
    }
422
423
    /**
424
     * Move ldap object.
425
     */
426 1
    protected function moveLdapObject(string $new_dn, string $current_dn, bool $simulate = false): bool
427
    {
428 1
        $this->logger->info('found object ['.$current_dn.'] but is not at the expected place ['.$new_dn.'], move object', [
429 1
            'category' => get_class($this),
430
        ]);
431
432 1
        $result = explode(',', $new_dn);
433 1
        $rdn = array_shift($result);
434 1
        $parent_dn = implode(',', $result);
435
436 1
        if ($simulate === false) {
437 1
            $this->ldap->rename($current_dn, $rdn, $parent_dn, true);
0 ignored issues
show
Bug introduced by
The method rename() does not seem to exist on object<Tubee\Endpoint\Ldap>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
438
        }
439
440 1
        return true;
441
    }
442
443
    /**
444
     * Get dn.
445
     */
446 9
    protected function getDn(array $object, array $endpoint_object = []): string
447
    {
448 9
        if (isset($object['entrydn'])) {
449 8
            return $this->normalizeDn($object['entrydn']);
450
        }
451 1
        if (isset($endpoint_object['entrydn'])) {
452
            return $endpoint_object['entrydn'];
453
        }
454
455 1
        throw new LdapEndpointException\NoEntryDn('no attribute entrydn found in data object');
456
    }
457
}
458