Completed
Push — master ( 808509...f69da3 )
by Christopher
06:05
created

Connection::rename()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
182
    }
183
184
    /**
185
     * Connects and Binds to the Domain Controller with a administrator credentials.
186
     * @return void
187
     */
188
    protected function open($anonymous = false)
189
    {
190
        $token = 'Opening LDAP connection: ' . LdapUtils::recursive_implode($this->dc, ' or ');
191
        Yii::info($token, __METHOD__);
192
        Yii::beginProfile($token, __METHOD__);
193
        // Connect to the LDAP server.
194
        $this->connect($this->dc, $this->port);
195
        Yii::endProfile($token, __METHOD__);
196
197
        try {
198
            if ($anonymous) {
199
                $this->_bound = ldap_bind($this->resource);
200
            } else {
201
                $this->_bound = ldap_bind($this->resource, $this->username, $this->password);
202
            }
203
        } catch (\Exception $e) {
204
            throw new \Exception('Invalid credential for user manager in ldap.', 0);
205
        }
206
    }
207
208
    /**
209
     * Connection.
210
     * @param string|array $hostname
211
     * @param type $port
212
     * @return void
213
     */
214
    public function connect($hostname = [], $port = '389')
215
    {
216
        if (is_array($hostname)) {
217
            $hostname = self::PROTOCOL.implode(' '.self::PROTOCOL, $hostname);
218
        }
219
        
220
        $this->close();
221
        $this->resource = ldap_connect($hostname, $port);
222
223
        // Set the LDAP options.     
224
        $this->setOption(LDAP_OPT_PROTOCOL_VERSION, 3);
225
        $this->setOption(LDAP_OPT_REFERRALS, $this->followReferrals);
226
        if ($this->useTLS) {
227
            $this->startTLS();
228
        }
229
230
        $this->trigger(self::EVENT_AFTER_OPEN);
231
    }
232
    
233
    /**
234
     * Authenticate user
235
     * @param string $username
236
     * @param string $password
237
     * @return int indicate occurrence of error. 
238
     */
239
    public function auth($username, $password)
240
    {
241
        // Open connection with manager
242
        $this->open();
243
        
244
        # Search for user and get user DN
245
        $searchResult = ldap_search($this->resource, $this->baseDn, "(&(objectClass=person)($this->loginAttribute=$username))", [$this->loginAttribute]);
246
        $entry = $this->getFirstEntry($searchResult);
247
        if($entry) {
248
            $this->userDn = $this->getDn($entry);        
249
        } else {
250
            $this->userDn = null;
251
        }
252
253
        // Connect to the LDAP server.
254
        $this->connect($this->dc, $this->port);
255
256
        // Authenticate user
257
        return ldap_bind($this->resource, $this->userDn, $password);
258
    }
259
    
260
    /**
261
     * Change the password of the current user. This must be performed over TLS.
262
     * @param string $username User for change password
263
     * @param string $oldPassword The old password
264
     * @param string $newPassword The new password
265
     * @return bool return true if change password is success
266
     * @throws \Exception
267
     */
268
    public function changePassword($username, $oldPassword, $newPassword)
269
    {        
270
        if (!$this->useTLS) {
271
            $message = 'TLS must be configured on your web server and enabled to change passwords.';
272
            throw new \Exception($message);
273
        }
274
275
        // Open connection with user
276
        if(!$this->auth($username, $oldPassword)){
277
            return false;
278
        }
279
        
280
        // Open connection with manager
281
        $this->open();
282
        
283
        // Replace passowrd
284
        $modifications[$this->unicodePassword] = 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...
Bug introduced by
The method encodePassword() does not seem to exist on object<chrmorandi\ldap\Connection>.

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...
285
        return ldap_mod_replace($this->resource, $this->userDn, $modifications);
286
    }
287
    
288
    /**
289
     * Closes the current connection.
290
     *
291
     * @return boolean
292
     */
293
    public function close()
294
    {
295
        if (is_resource($this->resource)) {
296
            ldap_close($this->resource);
297
        }
298
        return true;
299
    }
300
301
    /**
302
     * Execute ldap functions like.
303
     *
304
     * http://php.net/manual/en/ref.ldap.php
305
     *
306
     * @param  string $function php LDAP function
307
     * @param  array $params params for execute ldap function
308
     * @return bool|DataReader
309
     */
310
    public function executeQuery($function, $params)
311
    {
312
        $this->open();
313
        $results = [];
314
        $cookie = '';        
315
        $token = $function . ' - params: ' . LdapUtils::recursive_implode($params, ';');
316
317
        Yii::info($token , 'chrmorandi\ldap\Connection::query');
318
       
319
        Yii::beginProfile($token, 'chrmorandi\ldap\Connection::query');
320
        do {
321
            if($this->pageSize > 0) {
322
                $this->setControlPagedResult($cookie);
323
            }
324
            
325
            // Run the search.
326
            $result = call_user_func($function, $this->resource, ...$params);
327
            
328
            if($this->pageSize > 0) {
329
                $this->setControlPagedResultResponse($result, $cookie);
330
            }
331
            
332
            //Collect each resource result
333
            $results[] = $result;            
334
        } while (!is_null($cookie) && !empty($cookie));
335
        Yii::endProfile($token, 'chrmorandi\ldap\Connection::query');
336
337
        return new DataReader($this, $results);
338
    }
339
    
340
    /**
341
     * Returns true/false if the current connection is bound.
342
     * @return bool
343
     */
344
    public function getBound()
345
    {
346
        return $this->_bound;
347
    }
348
    
349
    /**
350
     * Get the current resource of connection.
351
     * @return resource
352
     */
353
    public function getResource()
354
    {
355
        return $this->resource;
356
    }
357
    
358
    /**
359
     * Sorts an AD search result by the specified attribute.
360
     * @param resource $result
361
     * @param string   $attribute
362
     * @return bool
363
     */
364
    public function sort($result, $attribute)
365
    {
366
        return ldap_sort($this->resource, $result, $attribute);
367
    }
368
369
    /**
370
     * Adds an entry to the current connection.
371
     * @param string $dn
372
     * @param array  $entry
373
     * @return bool
374
     */
375
    public function add($dn, array $entry)
376
    {
377
        return ldap_add($this->resource, $dn, $entry);
378
    }
379
380
    /**
381
     * Deletes an entry on the current connection.
382
     * @param string $dn
383
     * @return bool
384
     */
385
    public function delete($dn)
386
    {
387
        return ldap_delete($this->resource, $dn);
388
    }
389
390
    /**
391
     * Modify the name of an entry on the current connection.
392
     *
393
     * @param string $dn
394
     * @param string $newRdn
395
     * @param string $newParent
396
     * @param bool   $deleteOldRdn
397
     * @return bool
398
     */
399
    public function rename($dn, $newRdn, $newParent, $deleteOldRdn = false)
400
    {
401
        return ldap_rename($this->resource, $dn, $newRdn, $newParent, $deleteOldRdn);
402
    }
403
404
    /**
405
     * Batch modifies an existing entry on the current connection.
406
     * The types of modifications:
407
     *      LDAP_MODIFY_BATCH_ADD - Each value specified through values is added.
408
     *      LDAP_MODIFY_BATCH_REMOVE - Each value specified through values is removed. 
409
     *          Any value of the attribute not contained in the values array will remain untouched.
410
     *      LDAP_MODIFY_BATCH_REMOVE_ALL - All values are removed from the attribute named by attrib.
411
     *      LDAP_MODIFY_BATCH_REPLACE - All current values are replaced by new one.
412
     * @param string $dn
413
     * @param array  $values array associative with three keys: "attrib", "modtype" and "values".
414
     * ```php
415
     * [
416
     *     "attrib"  => "attribute",
417
     *     "modtype" => LDAP_MODIFY_BATCH_ADD,
418
     *     "values"  => ["attribute value one"],
419
     * ],
420
     * ```
421
     * @return mixed
422
     */
423
    public function modify($dn, array $values)
424
    {
425
        return ldap_modify_batch($this->resource, $dn, $values);
426
    }    
427
    
428
    /**
429
     * Retrieve the entries from a search result.
430
     * @param resource $searchResult
431
     * @return array|boolean
432
     */
433
    public function getEntries($searchResult)
434
    {
435
        return ldap_get_entries($this->resource, $searchResult);
436
    }
437
    
438
    /**
439
     * Retrieves the number of entries from a search result.
440
     * @param resource $searchResult
441
     * @return int
442
     */
443
    public function countEntries($searchResult)
444
    {
445
        return ldap_count_entries($this->resource, $searchResult);
446
    }
447
    
448
    public function countEntriesBySearch($db)
0 ignored issues
show
Unused Code introduced by
The parameter $db is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
449
    {
450
        return ldap_count_entries($this->resource, $searchResult);
0 ignored issues
show
Bug introduced by
The variable $searchResult does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
451
    }
452
453
    /**
454
     * Retrieves the first entry from a search result.
455
     * @param resource $searchResult
456
     * @return resource link identifier
457
     */
458
    public function getFirstEntry($searchResult)
459
    {
460
        return ldap_first_entry($this->resource, $searchResult);
461
    }
462
463
    /**
464
     * Retrieves the next entry from a search result.
465
     * @param resource $entry link identifier
466
     * @return resource
467
     */
468
    public function getNextEntry($entry)
469
    {
470
        return ldap_next_entry($this->resource, $entry);
471
    }
472
    
473
    /**
474
     * Retrieves the ldap first entry attribute.
475
     * @param resource $entry
476
     * @return string
477
     */
478
    public function getFirstAttribute($entry)
479
    {
480
        return ldap_first_attribute($this->resource, $entry);
481
    }
482
    
483
    /**
484
     * Retrieves the ldap next entry attribute.
485
     * @param resource $entry
486
     * @return string
487
     */
488
    public function getNextAttribute($entry)
489
    {
490
        return ldap_next_attribute($this->resource, $entry);
491
    }
492
493
    /**
494
     * Retrieves the ldap entry's attributes.
495
     * @param resource $entry
496
     * @return array
497
     */
498
    public function getAttributes($entry)
499
    {
500
        return ldap_get_attributes($this->resource, $entry);
501
    }
502
    
503
    /**
504
     * Retrieves all binary values from a result entry.
505
     * @param resource $entry link identifier
506
     * @param string $attribute name of attribute
507
     * @return array
508
     */
509
    public function getValuesLen($entry, $attribute)
510
    {
511
        return ldap_get_values_len($this->resource, $entry, $attribute);
512
    }
513
    
514
    /**
515
     * Retrieves the DN of a result entry.
516
     * @param resource $entry
517
     * @return string
518
     */
519
    public function getDn($entry)
520
    {
521
        return ldap_get_dn($this->resource, $entry);
522
    }
523
524
    /**
525
     * Free result memory.
526
     * @param resource $searchResult
527
     * @return bool
528
     */
529
    public function freeResult($searchResult)
530
    {
531
        return ldap_free_result($searchResult);
532
    }
533
534
    /**
535
     * Sets an option on the current connection.
536
     * @param int   $option
537
     * @param mixed $value
538
     * @return boolean
539
     */
540
    public function setOption($option, $value)
541
    {
542
        return ldap_set_option($this->resource, $option, $value);
543
    }
544
545
    /**
546
     * Starts a connection using TLS.
547
     * @return bool
548
     */
549
    public function startTLS()
550
    {
551
        return ldap_start_tls($this->resource);
552
    }
553
    
554
    /**
555
     * Send LDAP pagination control.
556
     * @param int    $pageSize
0 ignored issues
show
Bug introduced by
There is no parameter named $pageSize. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
557
     * @param bool   $isCritical
0 ignored issues
show
Bug introduced by
There is no parameter named $isCritical. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
558
     * @param string $cookie
559
     * @return bool
560
     */
561
    public function setControlPagedResult($cookie)
562
    {
563
        return ldap_control_paged_result($this->resource, $this->pageSize, false, $cookie);
564
    }
565
566
    /**
567
     * Retrieve a paginated result response.
568
     * @param resource $result
569
     * @param string $cookie
570
     * @return bool
571
     */
572
    public function setControlPagedResultResponse($result, &$cookie)
573
    {
574
        return ldap_control_paged_result_response($this->resource, $result, $cookie);
575
    }
576
       
577
    /**
578
     * Retrieve the last error on the current connection.
579
     * @return string
580
     */
581
    public function getLastError()
582
    {
583
        return ldap_error($this->resource);
584
    }
585
    
586
    /**
587
     * Returns the number of the last error on the current connection.
588
     * @return int
589
     */
590
    public function getErrNo()
591
    {
592
        return ldap_errno($this->resource);
593
    }
594
595
    /**
596
     * Returns the error string of the specified error number.
597
     * @param int $number
598
     * @return string
599
     */
600
    public function err2Str($number)
601
    {
602
        return ldap_err2str($number);
603
    }
604
}
605