Completed
Push — master ( a9e3ec...818d10 )
by Christopher
01:44
created

Connection::setOption()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
/**
3
 * @link      https://github.com/chrmorandi/yii2-ldap for the source repository
4
 * @package   yii2-ldap
5
 * @author    Christopher Mota <[email protected]>
6
 * @license   MIT License - view the LICENSE file that was distributed with this source code.
7
 * @since     1.0.0
8
 */
9
10
namespace chrmorandi\ldap;
11
12
use Yii;
13
use yii\base\Component;
14
use yii\caching\Cache;
15
16
/**
17
 * @property resource $resource
18
 * @property bool  $bount
19
 * @property int      $errNo Error number of the last command
20
 * @property string   $lastError Error message of the last command
21
 *
22
 * @author Christopher Mota <[email protected]>
23
 * @since  1.0
24
 */
25
class Connection extends Component
26
{
27
    /**
28
     * LDAP protocol string.
29
     * @var string
30
     */
31
    const PROTOCOL = 'ldap://';
32
33
    /**
34
     * LDAP port number.
35
     * @var string
36
     */
37
    const PORT = '389';
38
39
    /**
40
     * @event Event an event that is triggered after a DB connection is established
41
     */
42
    const EVENT_AFTER_OPEN = 'afterOpen';
43
44
    /**
45
     * @var string the LDAP base dn.
46
     */
47
    public $baseDn;
48
49
    /**
50
     * https://msdn.microsoft.com/en-us/library/ms677913(v=vs.85).aspx
51
     * @var bool the integer to instruct the LDAP connection whether or not to follow referrals.
52
     */
53
    public $followReferrals = false;
54
55
    /**
56
     * @var string The LDAP port to use when connecting to the domain controllers.
57
     */
58
    public $port = self::PORT;
59
60
    /**
61
     * @var bool Determines whether or not to use TLS with the current LDAP connection.
62
     */
63
    public $useTLS = true;
64
65
    /**
66
     * @var array the domain controllers to connect to.
67
     */
68
    public $dc = [];
69
70
    /**
71
     * @var string the username for establishing LDAP connection. Defaults to `null` meaning no username to use.
72
     */
73
    public $username;
74
75
    /**
76
     * @var string the password for establishing DB connection. Defaults to `null` meaning no password to use.
77
     */
78
    public $password;
79
80
    /**
81
     * @var int The page size for the paging operation.
82
     */
83
    public $pageSize = -1;
84
85
    /**
86
     * @var integer zero-based offset from where the records are to be returned. If not set or
87
     * less than 1, it means not filter values.
88
     */
89
    public $offset = -1;
90
91
    /**
92
     * @var bool whether to enable caching.
93
     * Note that in order to enable query caching, a valid cache component as specified
94
     * by [[cache]] must be enabled and [[enableCache]] must be set true.
95
     * Also, only the results of the queries enclosed within [[cache()]] will be cached.
96
     * @see cacheDuration
97
     * @see cache
98
     */
99
    public $enableCache = true;
100
101
    /**
102
     * @var integer number of seconds that table metadata can remain valid in cache.
103
     * Use 0 to indicate that the cached data will never expire.
104
     * @see enableCache
105
     */
106
    public $cacheDuration = 3600;
107
108
    /**
109
     * @var string the cache the ID of the cache application component that
110
     * is used to cache result query.
111
     * @see enableCache
112
     */
113
    public $cache = 'cache';
114
115
    /**
116
     * @var string the attribute for authentication
117
     */
118
    public $loginAttribute = "sAMAccountName";
119
120
    /**
121
     * @var bool stores the bool whether or not the current connection is bound.
122
     */
123
    protected $_bound = false;
124
125
    /**
126
     * @var resource|false
127
     */
128
    protected $resource;
129
130
    /**
131
     *
132
     * @var string
133
     */
134
    protected $userDN;
135
136
    # Create AD password (Microsoft Active Directory password format)
137
    protected static function encodePassword($password)
138
    {
139
        $password   = "\"" . $password . "\"";
140
        $adpassword = mb_convert_encoding($password, "UTF-16LE", "UTF-8");
141
        return $adpassword;
142
    }
143
144
    /**
145
     * Returns the current query cache information.
146
     * This method is used internally by [[Command]].
147
     * @param integer $duration the preferred caching duration. If null, it will be ignored.
148
     * @param \yii\caching\Dependency $dependency the preferred caching dependency. If null, it will be ignored.
149
     * @return array the current query cache information, or null if query cache is not enabled.
150
     * @internal
151
     */
152
    public function getCacheInfo($duration = 3600, $dependency = null)
153
    {
154
        if (!$this->enableCache) {
155
            return null;
156
        }
157
158
        if ($duration === 0 || $duration > 0) {
159
            if (Yii::$app) {
160
                $cache = Yii::$app->get($this->cache, false);
161
            }
162
            if ($cache instanceof Cache) {
163
                return [$cache, $duration, $dependency];
0 ignored issues
show
Bug introduced by
The variable $cache does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
164
            }
165
        }
166
167
        return null;
168
    }
169
170
    /**
171
     * Invalidates the cached data that are associated with any of the specified [[tags]] in this connection.
172
     * @param string|array $tags
173
     */
174
    public function clearCache($tags)
175
    {
176
        $cache = Yii::$app->get($this->cache, false);
177
        \yii\caching\TagDependency::invalidate($cache, $tags);
178
    }
179
180
    /**
181
     * Connects and Binds to the Domain Controller with a administrator credentials.
182
     * @return void
183
     */
184
    public function open($anonymous = false)
185
    {
186
        $token = 'Opening LDAP connection: ' . LdapHelper::recursive_implode($this->dc, ' or ');
187
        Yii::info($token, __METHOD__);
188
        Yii::beginProfile($token, __METHOD__);
189
        // Connect to the LDAP server.
190
        $this->connect($this->dc, $this->port);
191
        Yii::endProfile($token, __METHOD__);
192
193
        try {
194
            if ($anonymous) {
195
                $this->_bound = ldap_bind($this->resource);
196
            } else {
197
                $this->_bound = ldap_bind($this->resource, $this->username, $this->password);
198
            }
199
        } catch (\Exception $e) {
200
            throw new \Exception('Invalid credential for user manager in ldap.', 0);
201
        }
202
    }
203
204
    /**
205
     * Connection.
206
     * @param string|array $hostname
207
     * @param type $port
208
     * @return void
209
     */
210
    protected function connect($hostname = [], $port = '389')
211
    {
212
        if (is_array($hostname)) {
213
            $hostname = self::PROTOCOL . implode(' ' . self::PROTOCOL, $hostname);
214
        }
215
216
        $this->close();
217
        $this->resource = ldap_connect($hostname, $port);
218
219
        // Set the LDAP options.
220
        $this->setOption(LDAP_OPT_PROTOCOL_VERSION, 3);
221
        $this->setOption(LDAP_OPT_REFERRALS, $this->followReferrals);
222
        $this->setOption(LDAP_OPT_NETWORK_TIMEOUT, 2);
223
224
        if ($this->useTLS) {
225
            $this->startTLS();
226
        }
227
228
        $this->trigger(self::EVENT_AFTER_OPEN);
229
    }
230
231
    /**
232
     * Authenticate user
233
     * @param string $username
234
     * @param string $password
235
     * @return bool indicate occurrence of error.
236
     */
237
    public function auth($username, $password)
238
    {
239
        // Open connection with manager
240
        $this->open();
241
242
        # Search for user and get user DN
243
        $searchResult = ldap_search($this->resource, $this->baseDn, "(&(objectClass=person)($this->loginAttribute=$username))", [$this->loginAttribute]);
244
        $entry        = $this->getFirstEntry($searchResult);
245
        if ($entry) {
246
            $this->userDN = $this->getDn($entry);
247
        } else {
248
            // User not found.
249
            return false;
250
        }
251
252
        // Connect to the LDAP server.
253
        $this->connect($this->dc, $this->port);
254
255
        // Try to authenticate user, but ignore any PHP warnings.
256
        return @ldap_bind($this->resource, $this->userDN, $password);
257
    }
258
259
    /**
260
     * Change the password of the current user. This must be performed over TLS.
261
     * @param string $username User for change password
262
     * @param string $oldPassword The old password
263
     * @param string $newPassword The new password
264
     * @return bool return true if change password is success
265
     * @throws \Exception
266
     */
267
    public function changePasswordAsUser($username, $oldPassword, $newPassword)
268
    {
269
        if (!$this->useTLS) {
270
            $message = 'TLS must be configured on your web server and enabled to change passwords.';
271
            throw new \Exception($message);
272
        }
273
274
        // Open connection with user
275
        if (!$this->auth($username, $oldPassword)) {
276
            return false;
277
        }
278
279
        return $this->changePasswordAsManager($this->userDN, $newPassword);
280
    }
281
282
    /**
283
     * Change the password of the user as manager. This must be performed over TLS.
284
     * @param string $userDN User Distinguished Names (DN) for change password. Ex.: cn=admin,dc=example,dc=com
285
     * @param string $newPassword The new password
286
     * @return bool return true if change password is success
287
     * @throws \Exception
288
     */
289
    public function changePasswordAsManager($userDN, $newPassword)
290
    {
291
        if (!$this->useTLS) {
292
            $message = 'TLS must be configured on your web server and enabled to change passwords.';
293
            throw new \Exception($message);
294
        }
295
296
        // Open connection with manager
297
        $this->open();
298
299
        // Replace passowrd attribute for AD
300
        // The AD password change procedure is modifying the attribute unicodePwd
301
        $modifications['unicodePwd'] = self::encodePassword($newPassword);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$modifications was never initialized. Although not strictly required by PHP, it is generally a good practice to add $modifications = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
302
        return ldap_mod_replace($this->resource, $userDN, $modifications);
303
    }
304
305
    /**
306
     * Closes the current connection.
307
     *
308
     * @return bool
309
     */
310
    public function close()
311
    {
312
        if (is_resource($this->resource)) {
313
            ldap_close($this->resource);
314
        }
315
        return true;
316
    }
317
318
    /**
319
     * Execute ldap search like.
320
     *
321
     * @link http://php.net/manual/en/ref.ldap.php
322
     *
323
     * @param  string $function php LDAP function
324
     * @param  array $params params for execute ldap function
325
     * @return bool|DataReader
326
     */
327
    public function executeQuery($function, $params)
328
    {
329
        $this->open();
330
        $results = [];
331
        $cookie  = '';
332
        $token   = $function . ' - params: ' . LdapHelper::recursive_implode($params, ';');
333
334
        Yii::info($token, 'chrmorandi\ldap\Connection::query');
335
336
        Yii::beginProfile($token, 'chrmorandi\ldap\Connection::query');
337
        do {
338
            if ($this->pageSize > 0) {
339
                $this->setControlPagedResult($cookie);
340
            }
341
342
            // Run the search.
343
            $result = call_user_func($function, $this->resource, ...$params);
344
345
            if ($this->pageSize > 0) {
346
                $this->setControlPagedResultResponse($result, $cookie);
347
            }
348
349
            //Collect each resource result
350
            $results[] = $result;
351
        } while (!is_null($cookie) && !empty($cookie));
352
        Yii::endProfile($token, 'chrmorandi\ldap\Connection::query');
353
354
        return new DataReader($this, $results);
355
    }
356
357
    /**
358
     * Returns true/false if the current connection is bound.
359
     * @return bool
360
     */
361
    public function getBound()
362
    {
363
        return $this->_bound;
364
    }
365
366
    /**
367
     * Get the current resource of connection.
368
     * @return resource
369
     */
370
    public function getResource()
371
    {
372
        return $this->resource;
373
    }
374
375
    /**
376
     * Sorts an AD search result by the specified attribute.
377
     * @param resource $result
378
     * @param string   $attribute
379
     * @return bool
380
     */
381
    public function sort($result, $attribute)
382
    {
383
        return ldap_sort($this->resource, $result, $attribute);
384
    }
385
386
    /**
387
     * Adds an entry to the current connection.
388
     * @param string $dn
389
     * @param array  $entry
390
     * @return bool
391
     */
392
    public function add($dn, array $entry)
393
    {
394
        return ldap_add($this->resource, $dn, $entry);
395
    }
396
397
    /**
398
     * Deletes an entry on the current connection.
399
     * @param string $dn
400
     * @return bool
401
     */
402
    public function delete($dn)
403
    {
404
        return ldap_delete($this->resource, $dn);
405
    }
406
407
    /**
408
     * Modify the name of an entry on the current connection.
409
     *
410
     * @param string $dn
411
     * @param string $newRdn
412
     * @param string $newParent
413
     * @param bool   $deleteOldRdn
414
     * @return bool
415
     */
416
    public function rename($dn, $newRdn, $newParent, $deleteOldRdn = false)
417
    {
418
        return ldap_rename($this->resource, $dn, $newRdn, $newParent, $deleteOldRdn);
419
    }
420
421
    /**
422
     * Batch modifies an existing entry on the current connection.
423
     * The types of modifications:
424
     *      LDAP_MODIFY_BATCH_ADD - Each value specified through values is added.
425
     *      LDAP_MODIFY_BATCH_REMOVE - Each value specified through values is removed.
426
     *          Any value of the attribute not contained in the values array will remain untouched.
427
     *      LDAP_MODIFY_BATCH_REMOVE_ALL - All values are removed from the attribute named by attrib.
428
     *      LDAP_MODIFY_BATCH_REPLACE - All current values are replaced by new one.
429
     * @param string $dn
430
     * @param array  $values array associative with three keys: "attrib", "modtype" and "values".
431
     * ```php
432
     * [
433
     *     "attrib"  => "attribute",
434
     *     "modtype" => LDAP_MODIFY_BATCH_ADD,
435
     *     "values"  => ["attribute value one"],
436
     * ],
437
     * ```
438
     * @return mixed
439
     */
440
    public function modify($dn, array $values)
441
    {
442
        return ldap_modify_batch($this->resource, $dn, $values);
443
    }
444
445
    /**
446
     * Retrieve the entries from a search result.
447
     * @param resource $searchResult
448
     * @return array|bool
449
     */
450
    public function getEntries($searchResult)
451
    {
452
        return ldap_get_entries($this->resource, $searchResult);
453
    }
454
455
    /**
456
     * Retrieves the number of entries from a search result.
457
     * @param resource $searchResult
458
     * @return int
459
     */
460
    public function countEntries($searchResult)
461
    {
462
        return ldap_count_entries($this->resource, $searchResult);
463
    }
464
465
    /**
466
     * Retrieves the first entry from a search result.
467
     * @param resource $searchResult
468
     * @return resource link identifier
469
     */
470
    public function getFirstEntry($searchResult)
471
    {
472
        return ldap_first_entry($this->resource, $searchResult);
473
    }
474
475
    /**
476
     * Retrieves the next entry from a search result.
477
     * @param resource $entry link identifier
478
     * @return resource
479
     */
480
    public function getNextEntry($entry)
481
    {
482
        return ldap_next_entry($this->resource, $entry);
483
    }
484
485
    /**
486
     * Retrieves the ldap first entry attribute.
487
     * @param resource $entry
488
     * @return string
489
     */
490
    public function getFirstAttribute($entry)
491
    {
492
        return ldap_first_attribute($this->resource, $entry);
493
    }
494
495
    /**
496
     * Retrieves the ldap next entry attribute.
497
     * @param resource $entry
498
     * @return string
499
     */
500
    public function getNextAttribute($entry)
501
    {
502
        return ldap_next_attribute($this->resource, $entry);
503
    }
504
505
    /**
506
     * Retrieves the ldap entry's attributes.
507
     * @param resource $entry
508
     * @return array
509
     */
510
    public function getAttributes($entry)
511
    {
512
        return ldap_get_attributes($this->resource, $entry);
513
    }
514
515
    /**
516
     * Retrieves all binary values from a result entry. Individual values are accessed by integer index in the array.
517
     * The first index is 0. The number of values can be found by indexing "count" in the resultant array.
518
     *
519
     * @link https://www.php.net/manual/en/function.ldap-get-values-len.php
520
     *
521
     * @param resource $entry Link identifier
522
     * @param string $attribute Name of attribute
523
     * @return array Returns an array of values for the attribute on success and empty array on error.
524
     */
525
    public function getValuesLen($entry, $attribute)
526
    {
527
        $result = ldap_get_values_len($this->resource, $entry, $attribute);
528
        return is_array($result) ? $result : [];
529
    }
530
531
    /**
532
     * Retrieves the DN of a result entry.
533
     *
534
     * @link https://www.php.net/manual/en/function.ldap-get-dn.php
535
     *
536
     * @param resource $entry
537
     * @return string
538
     */
539
    public function getDn($entry)
540
    {
541
        return ldap_get_dn($this->resource, $entry);
542
    }
543
544
    /**
545
     * Free result memory.
546
     *
547
     * @link https://www.php.net/manual/en/function.ldap-free-result.php
548
     *
549
     * @param resource $searchResult
550
     * @return bool Returns TRUE on success or FALSE on failure.
551
     */
552
    public function freeResult($searchResult)
553
    {
554
        return ldap_free_result($searchResult);
555
    }
556
557
    /**
558
     * Sets an option on the current connection.
559
     *
560
     * @link https://www.php.net/manual/en/function.ldap-set-option.php
561
     *
562
     * @param int   $option The parameter.
563
     * @param mixed $value The new value for the specified option.
564
     * @return bool Returns TRUE on success or FALSE on failure.
565
     */
566
    public function setOption($option, $value)
567
    {
568
        return ldap_set_option($this->resource, $option, $value);
569
    }
570
571
    /**
572
     * Starts a connection using TLS.
573
     *
574
     * @link https://www.php.net/manual/en/function.ldap-start-tls.php
575
     *
576
     * @return bool
577
     */
578
    public function startTLS()
579
    {
580
        return ldap_start_tls($this->resource);
581
    }
582
583
    /**
584
     * Send LDAP pagination control.
585
     *
586
     * @link http://php.net/manual/en/function.ldap-control-paged-result.php
587
     *
588
     * @param string $cookie An opaque structure sent by the server
589
     * @param bool   $isCritical Indicates whether the pagination is critical or not. If true and if the server doesn't support pagination, the search will return no result.
590
     * @return bool Returns TRUE on success or FALSE on failure.
591
     */
592
    public function setControlPagedResult($cookie = '', $isCritical = false)
593
    {
594
        return ldap_control_paged_result($this->resource, $this->pageSize, $isCritical, $cookie);
595
    }
596
597
    /**
598
     * Retrieve a paginated result response.
599
     *
600
     * @link https://www.php.net/manual/en/function.ldap-control-paged-result-response.php
601
     *
602
     * @param resource $result
603
     * @param string $cookie An opaque structure sent by the server
604
     * @return bool Returns TRUE on success or FALSE on failure.
605
     */
606
    public function setControlPagedResultResponse($result, &$cookie)
607
    {
608
        return ldap_control_paged_result_response($this->resource, $result, $cookie);
609
    }
610
611
    /**
612
     * Return the LDAP error message of the last LDAP command.
613
     *
614
     * @link https://www.php.net/manual/en/function.ldap-error.php
615
     *
616
     * @return string Error message.
617
     */
618
    public function getLastError()
619
    {
620
        return ldap_error($this->resource);
621
    }
622
623
    /**
624
     * Returns the number of the last error on the current connection.
625
     *
626
     * @link https://www.php.net/manual/en/function.ldap-errno.php
627
     *
628
     * @return int Error number
629
     */
630
    public function getErrNo()
631
    {
632
        return ldap_errno($this->resource);
633
    }
634
635
    /**
636
     * Returns the error string of the specified error number.
637
     *
638
     * @link https://www.php.net/manual/en/function.ldap-err2str.php
639
     *
640
     * @param int $number The error number.
641
     * @return string  Error message.
642
     */
643
    public function err2Str($number)
644
    {
645
        return ldap_err2str($number);
646
    }
647
648
}
649