Completed
Push — master ( abe227...316baf )
by Raffael
13:55 queued 08:01
created

Ldap::getOne()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 11
cts 11
cp 1
rs 9.6333
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * tubee.io
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
    /**
28
     * Kind.
29
     */
30
    public const KIND = 'LdapEndpoint';
31
32
    /**
33
     * Ldap.
34
     *
35
     * @var Ldap
36
     */
37
    protected $ldap;
38
39
    /**
40
     * Uri.
41
     *
42
     * @var string
43
     */
44
    protected $uri = 'ldap://127.0.0.1:389';
45
46
    /**
47
     * Binddn.
48
     *
49
     * @var string
50
     */
51
    protected $binddn;
52
53
    /**
54
     * Bindpw.
55
     *
56
     * @var string
57
     */
58
    protected $bindpw;
59
60
    /**
61
     * Basedn.
62
     *
63
     * @var string
64
     */
65
    protected $basedn = '';
66
67
    /**
68
     * tls.
69
     *
70
     * @var bool
71
     */
72
    protected $tls = false;
73
74
    /**
75
     *  Options.
76
     *
77
     * @var array
78
     */
79
    protected $options = [];
80
81
    /**
82
     * Init endpoint.
83
     */
84 28
    public function __construct(string $name, string $type, LdapServer $ldap, CollectionInterface $collection, WorkflowFactory $workflow, LoggerInterface $logger, array $resource = [])
85
    {
86 28
        $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...
87
88 28
        if (isset($resource['data']['resource'])) {
89
            $this->setLdapOptions($resource['data']['resource']);
90
        }
91
92 28
        parent::__construct($name, $type, $collection, $workflow, $logger, $resource);
93 28
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98 4
    public function setup(bool $simulate = false): EndpointInterface
99
    {
100 4
        $this->logger->debug('connect to ldap server ['.$this->uri.']', [
101 4
            'category' => get_class($this),
102
        ]);
103
104 4
        if (null === $this->binddn) {
105 3
            $this->logger->warning('no binddn set for ldap connection, you should avoid anonymous bind', [
106 3
                'category' => get_class($this),
107
            ]);
108
        }
109
110 4
        if (false === $this->tls && 'ldaps' !== substr($this->uri, 0, 5)) {
111 3
            $this->logger->warning('neither tls nor ldaps enabled for ldap connection, it is strongly reccommended to encrypt ldap connections', [
112 3
                'category' => get_class($this),
113
            ]);
114
        }
115
116 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...
117
118 4
        foreach ($this->options as $opt => $value) {
119 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...
120
        }
121
122 4
        if (true === $this->tls) {
123 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...
124
        }
125
126 4
        $this->logger->info('bind to ldap server ['.$this->uri.'] with binddn ['.$this->binddn.']', [
127 4
            'category' => get_class($this),
128
        ]);
129
130 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...
131
132 4
        return $this;
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138 3
    public function setLdapOptions(?array $config = null): EndpointInterface
139
    {
140 3
        if ($config === null) {
141
            return $this;
142
        }
143
144 3
        foreach ($config as $option => $value) {
145 3
            switch ($option) {
146 3
                case 'options':
147 1
                    $this->options = $value;
148
149 1
                    break;
150 2
                case 'uri':
151 2
                case 'binddn':
152 1
                case 'bindpw':
153 1
                case 'basedn':
154 1
                    $this->{$option} = (string) $value;
155
156 1
                    break;
157 1
                case 'tls':
158 1
                    $this->tls = (bool) $value;
159
160 1
                    break;
161
                default:
162 3
                    throw new InvalidArgumentException('unknown ldap option '.$option.' given');
163
            }
164
        }
165
166 3
        return $this;
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172 1
    public function shutdown(bool $simulate = false): EndpointInterface
173
    {
174 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...
175
176 1
        return $this;
177
    }
178
179
    /**
180
     * {@inheritdoc}
181
     */
182 3
    public function change(AttributeMapInterface $map, array $diff, array $object, array $endpoint_object, bool $simulate = false): ?string
183
    {
184 3
        $object = array_change_key_case($object);
185 3
        $dn = $this->getDn($object, $endpoint_object);
186
187 3
        if (isset($diff['entrydn'])) {
188
            unset($diff['entrydn']);
189
        }
190
191 3
        $this->logger->info('update ldap object ['.$dn.'] on endpoint ['.$this->getIdentifier().'] with attributes [{attributes}]', [
192 3
            'category' => get_class($this),
193 3
            'attributes' => $diff,
194
        ]);
195
196 3
        if ($dn !== $endpoint_object['entrydn']) {
197 1
            $this->moveLdapObject($dn, $endpoint_object['entrydn'], $simulate);
198 1
            $rdn_attr = explode('=', $dn);
199 1
            $rdn_attr = strtolower(array_shift($rdn_attr));
200
201 1
            if (isset($diff[$rdn_attr])) {
202
                unset($diff[$rdn_attr]);
203
            }
204
        }
205
206 3
        if ($simulate === false) {
207 2
            $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...
208
209 2
            return $dn;
210
        }
211
212 1
        return null;
213
    }
214
215
    /**
216
     * {@inheritdoc}
217
     */
218 2
    public function delete(AttributeMapInterface $map, array $object, array $endpoint_object, bool $simulate = false): bool
219
    {
220 2
        $dn = $this->getDn($object, $endpoint_object);
221 2
        $this->logger->debug('delete ldap object ['.$dn.']', [
222 2
            'category' => get_class($this),
223
        ]);
224
225 2
        if ($simulate === false) {
226 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...
227
        }
228
229 2
        return true;
230
    }
231
232
    /**
233
     * {@inheritdoc}
234
     */
235 3
    public function create(AttributeMapInterface $map, array $object, bool $simulate = false): ?string
236
    {
237 3
        $dn = $this->getDn($object);
238
239 2
        if (isset($object['entrydn'])) {
240 2
            unset($object['entrydn']);
241
        }
242
243 2
        $this->logger->info('create new ldap object ['.$dn.'] on endpoint ['.$this->getIdentifier().'] with attributes [{attributes}]', [
244 2
            'category' => get_class($this),
245 2
            'attributes' => $object,
246
        ]);
247
248 2
        if ($simulate === false) {
249 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...
250
251 1
            return $dn;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $dn; (string) is incompatible with the return type declared by the interface Tubee\Endpoint\EndpointInterface::create of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
252
        }
253
254 1
        return null;
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260 7
    public function transformQuery(?array $query = null)
261
    {
262 7
        $result = null;
263
264 7
        if ($this->filter_all !== null) {
265
            $result = $this->filter_all;
266
        }
267
268 7
        if (!empty($query)) {
269 7
            if ($this->filter_all === null) {
270 7
                $result = QueryTransformer::transform($query);
271
            } else {
272
                $result = '&('.$this->filter_all.')('.QueryTransformer::transform($query).')';
273
            }
274
        }
275
276 7
        return $result;
277
    }
278
279
    /**
280
     * {@inheritdoc}
281
     */
282
    public function getAll(?array $query = null): Generator
283
    {
284
        $filter = $this->transformQuery($query);
285
        $this->logger->debug('find all ldap objects with ldap filter ['.$filter.'] on endpoint ['.$this->name.']', [
286
            'category' => get_class($this),
287
        ]);
288
289
        $i = 0;
290
        $result = $this->ldap->ldapSearch($this->basedn, $filter);
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...
291
        foreach ($result->getEntries() as $object) {
292
            yield $this->build($object);
293
        }
294
295
        return $i;
296
    }
297
298
    /**
299
     * {@inheritdoc}
300
     */
301 5
    public function getDiff(AttributeMapInterface $map, array $diff): array
302
    {
303 5
        $result = [];
304 5
        foreach ($diff as $attribute => $update) {
305 4
            switch ($update['action']) {
306
                case AttributeMapInterface::ACTION_REPLACE:
307 2
                    $result[] = [
308 2
                        'attrib' => $attribute,
309 2
                        'modtype' => LDAP_MODIFY_BATCH_REPLACE,
310 2
                        'values' => (array) $update['value'],
311
                    ];
312
313 2
                break;
314
                case AttributeMapInterface::ACTION_REMOVE:
315 1
                    $result[] = [
316 1
                        'attrib' => $attribute,
317 1
                        'modtype' => LDAP_MODIFY_BATCH_REMOVE_ALL,
318
                    ];
319
320 1
                break;
321
                case AttributeMapInterface::ACTION_ADD:
322 1
                    $result[] = [
323 1
                        'attrib' => $attribute,
324 1
                        'modtype' => LDAP_MODIFY_BATCH_ADD,
325 1
                        'values' => (array) $update['value'],
326
                    ];
327
328 1
                break;
329
                default:
330 4
                    throw new InvalidArgumentException('unknown diff action '.$update['action'].' given');
331
            }
332
        }
333
334 5
        return $result;
335
    }
336
337
    /**
338
     * {@inheritdoc}
339
     */
340 3
    public function getOne(array $object, ?array $attributes = []): EndpointObjectInterface
341
    {
342 3
        $filter = $this->getFilterOne($object);
343 3
        $this->logger->debug('find ldap object with ldap filter ['.$filter.'] in ['.$this->basedn.'] on endpoint ['.$this->getIdentifier().']', [
344 3
            'category' => get_class($this),
345
        ]);
346
347 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...
348 3
        $count = $result->countEntries();
349
350 3
        if ($count > 1) {
351 1
            throw new Exception\ObjectMultipleFound('found more than one object with filter '.$filter);
352
        }
353 2
        if ($count === 0) {
354 1
            throw new Exception\ObjectNotFound('no object found with filter '.$filter);
355
        }
356
357 1
        return $this->build($this->prepareRawObject($result->getEntries()[0]));
358
    }
359
360
    /**
361
     * Prepare object.
362
     */
363 1
    protected function prepareRawObject(array $result): array
364
    {
365 1
        $object = [];
366 1
        foreach ($result as $key => $attr) {
367 1
            if ($key === 'dn') {
368
                $object['entrydn'] = $attr;
369 1
            } elseif (!is_int($key)) {
370 1
                if ($attr['count'] === 1) {
371 1
                    $object[$key] = $attr[0];
372
                } else {
373
                    $val = $attr;
374
                    unset($val['count']);
375 1
                    $object[$key] = $val;
376
                }
377
            }
378
        }
379
380 1
        return $object;
381
    }
382
383
    /**
384
     * Move ldap object.
385
     */
386 1
    protected function moveLdapObject(string $new_dn, string $current_dn, bool $simulate = false): bool
387
    {
388 1
        $this->logger->info('found object ['.$current_dn.'] but is not at the expected place ['.$new_dn.'], move object', [
389 1
            'category' => get_class($this),
390
        ]);
391
392 1
        $result = explode(',', $new_dn);
393 1
        $rdn = array_shift($result);
394 1
        $parent_dn = implode(',', $result);
395
396 1
        if ($simulate === false) {
397 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...
398
        }
399
400 1
        return true;
401
    }
402
403
    /**
404
     * Get dn.
405
     */
406 8
    protected function getDn(array $object, array $endpoint_object = []): string
407
    {
408 8
        if (isset($object['entrydn'])) {
409 7
            return $object['entrydn'];
410
        }
411 1
        if (isset($endpoint_object['entrydn'])) {
412
            return $endpoint_object['entrydn'];
413
        }
414
415 1
        throw new LdapEndpointException\NoEntryDn('no attribute entrydn found in data object');
416
    }
417
}
418