Completed
Push — master ( 80d590...9ffcbb )
by Christopher
01:56
created

Connection::getNextEntry()   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 1
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\base\Component;
12
13
/**
14
 * @property resource $resource
15
 * @property boolean  $bount
16
 * @property int      $errNo Error number of the last command
17
 * @property string   $lastError Error message of the last command
18
 *
19
 * @author Christopher Mota <[email protected]>
20
 * @since 1.0
21
 */
22
class Connection extends Component
23
{
24
    /**
25
     * LDAP protocol string.
26
     * @var string
27
     */
28
    const PROTOCOL = 'ldap://';
29
30
    /**
31
     * LDAP port number.
32
     * @var string
33
     */
34
    const PORT = '389';
35
36
    /**
37
     * @event Event an event that is triggered after a DB connection is established
38
     */
39
    const EVENT_AFTER_OPEN = 'afterOpen';
40
41
    /**
42
     * @var string the LDAP base dn.
43
     */
44
    public $baseDn;
45
46
    /**
47
     * https://msdn.microsoft.com/en-us/library/ms677913(v=vs.85).aspx
48
     * @var bool the integer to instruct the LDAP connection whether or not to follow referrals.
49
     */
50
    public $followReferrals = false;
51
52
    /**
53
     * @var string The LDAP port to use when connecting to the domain controllers.
54
     */
55
    public $port = self::PORT;
56
57
    /**
58
     * @var bool Determines whether or not to use TLS with the current LDAP connection.
59
     */
60
    public $useTLS = true;
61
62
    /**
63
     * @var array the domain controllers to connect to.
64
     */
65
    public $dc = [];
66
    
67
    /**
68
     * @var string the username for establishing LDAP connection. Defaults to `null` meaning no username to use.
69
     */
70
    public $username;
71
72
    /**
73
     * @var string the password for establishing DB connection. Defaults to `null` meaning no password to use.
74
     */
75
    public $password;
76
    
77
    /**
78
     * @var int The page size for the paging operation.
79
     */
80
    public $pageSize = -1;
81
    
82
    /**
83
     * @var integer zero-based offset from where the records are to be returned. If not set or
84
     * less than 1, it means not filter values.
85
     */
86
    public $offset = -1;
87
    
88
    /**
89
     * @var boolean whether to enable caching.
90
     * Note that in order to enable truly caching, a valid cache component as specified
91
     * by [[cache]] must be enabled and [[enableCache]] must be set true.
92
     * @see cacheDuration
93
     * @see cache
94
     */
95
    public $enableCache = false;
96
    
97
    /**
98
     * @var integer number of seconds that table metadata can remain valid in cache.
99
     * Use 0 to indicate that the cached data will never expire.
100
     * @see enableCache
101
     */
102
    public $cacheDuration = 3600;
103
104
    /**
105
     * @var Cache|string the cache object or the ID of the cache application component that
106
     * is used to cache result query.
107
     * @see enableCache
108
     */
109
    public $cache = 'cache';
110
    
111
    /**
112
     * @var string the attribute for authentication
113
     */    
114
    public $loginAttribute = "sAMAccountName";
115
116
    /**
117
     * @var string the attribute for password default is unicodePwd (AD passoword)
118
     */
119
    public $unicodePassword = 'unicodePwd';
120
    
121
    /**
122
     * @var bool stores the bool whether or not the current connection is bound.
123
     */
124
    protected $_bound = false;
125
126
    /**
127
     * @var resource|false
128
     */
129
    protected $resource;
130
    
131
    /**
132
     *
133
     * @var string 
134
     */
135
    protected $userDn;
136
    
137
    # Create AD password (Microsoft Active Directory password format)
138
    protected static function makeAdPassword($password) {
139
        $password = "\"" . $password . "\"";
140
        $adpassword = mb_convert_encoding($password, "UTF-16LE", "UTF-8");
141
        return $adpassword;
142
    }
143
144
    /**
145
     * Connects and Binds to the Domain Controller with a administrator credentials.
146
     * @return void
147
     */
148
    protected function open($anonymous = false)
149
    {
150
        // Connect to the LDAP server.
151
        $this->connect($this->dc, $this->port);
152
153
        try {
154
            if ($anonymous) {
155
                $this->_bound = ldap_bind($this->resource);
156
            } else {
157
                $this->_bound = ldap_bind($this->resource, $this->username, $this->password);
158
            }
159
        } catch (\Exception $e) {
160
            throw new \Exception('Invalid credential for user manager in ldap.', 0);
161
        }
162
    }
163
164
    /**
165
     * Connection.
166
     * @param string|array $hostname
167
     * @param type $port
168
     * @return void
169
     */
170
    public function connect($hostname = [], $port = '389')
171
    {
172
        if (is_array($hostname)) {
173
            $hostname = self::PROTOCOL.implode(' '.self::PROTOCOL, $hostname);
174
        }
175
        
176
        $this->close();
177
        $this->resource = ldap_connect($hostname, $port);
178
179
        // Set the LDAP options.     
180
        $this->setOption(LDAP_OPT_PROTOCOL_VERSION, 3);
181
        $this->setOption(LDAP_OPT_REFERRALS, $this->followReferrals);
182
        if ($this->useTLS) {
183
            $this->startTLS();
184
        }
185
186
        $this->trigger(self::EVENT_AFTER_OPEN);
187
    }
188
    
189
    /**
190
     * Authenticate user
191
     * @param string $username
192
     * @param string $password
193
     * @return int indicate occurrence of error. 
194
     */
195
    public function auth($username, $password)
196
    {
197
        // Open connection with manager
198
        $this->open();
199
        
200
         # Search for user and get user DN
201
        $searchResult = ldap_search($this->resource, $this->baseDn, "(&(objectClass=person)($this->loginAttribute=$username))", [$this->loginAttribute]);
202
        $entry = $this->getFirstEntry($searchResult);
203
        if($entry) {
204
            $this->userDn = $this->getDn($entry);        
205
        } else {
206
            $this->userDn = null;
207
        }
208
        
209
        // Connect to the LDAP server.
210
        $this->connect($this->dc, $this->port);
211
        // Authenticate user
212
        $bound = ldap_bind($this->resource, $this->userDn, $password);
213
        
214
        return $bound;
215
    }
216
    
217
    /**
218
     * Change the password of the current user. This must be performed over TLS.
219
     * @param string $username User for change password
220
     * @param string $oldPassword The old password
221
     * @param string $newPassword The new password
222
     * @return mixed return true if change password is success
223
     * @throws \Exception
224
     */
225
    public function changePassword($username, $oldPassword, $newPassword) {
0 ignored issues
show
Unused Code introduced by
The parameter $newPassword 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...
226
        // Open connection with user
227
        $this->auth($username, $oldPassword);
228
229
        if (!$this->useTLS) {
230
            $message = 'TLS must be configured on your web server and enabled to change passwords.';
231
            throw new \Exception($message);
232
        }
233
        
234
        $modifications = [
235
            [ // Create batch modification for removing the old password.
236
                "attrib" => $this->unicodePassword,
237
                "modtype" => LDAP_MODIFY_BATCH_REMOVE,
238
                "values" => [self::encodePassword($oldPassword)],
0 ignored issues
show
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...
239
            ],
240
            [ // Create batch modification for adding the new password.
241
                "attrib" => $this->unicodePassword,
242
                "modtype" => LDAP_MODIFY_BATCH_ADD,
243
                "values" => [self::encodePassword($oldPassword)],
0 ignored issues
show
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...
244
            ],                
245
        ];
246
        
247
        return $this->modify($this->userDn, $modifications);
248
    }
249
    
250
    /**
251
     * Closes the current connection.
252
     *
253
     * @return boolean
254
     */
255
    public function close()
256
    {
257
        if (is_resource($this->resource)) {
258
            ldap_close($this->resource);
259
        }
260
        return true;
261
    }
262
263
    /**
264
     * Execute ldap functions like.
265
     *
266
     * http://php.net/manual/en/ref.ldap.php
267
     *
268
     * @param  string $function php LDAP function
269
     * @param  array $params params for execute ldap function
270
     * @return bool|DataReader
271
     */
272
    public function executeQuery($function, $params)
273
    {
274
        $this->open();
275
        $results = [];
276
        $cookie = '';
277
        
278
        do {
279
            $this->setControlPagedResult($cookie);
280
281
            $result = call_user_func($function, $this->resource, ...$params);
282
283
            $this->setControlPagedResultResponse($result, $cookie);
284
            $results[] = $result;            
285
        } while ($cookie !== null && $cookie != '');        
286
287
        if($this->offset > 0){
288
            $results = $results[intval($this->offset/$this->pageSize -1)];
289
        }
290
        
291
        return new DataReader($this, $results);
292
    }
293
    
294
    /**
295
     * Returns true/false if the current connection is bound.
296
     * @return bool
297
     */
298
    public function getBound()
299
    {
300
        return $this->_bound;
301
    }
302
    
303
    /**
304
     * Get the current resource of connection.
305
     * @return resource
306
     */
307
    public function getResource()
308
    {
309
        return $this->resource;
310
    }
311
    
312
    /**
313
     * Sorts an AD search result by the specified attribute.
314
     * @param resource $result
315
     * @param string   $attribute
316
     * @return bool
317
     */
318
    public function sort($result, $attribute)
319
    {
320
        return ldap_sort($this->resource, $result, $attribute);
321
    }
322
323
    /**
324
     * Adds an entry to the current connection.
325
     * @param string $dn
326
     * @param array  $entry
327
     * @return bool
328
     */
329
    public function add($dn, array $entry)
330
    {
331
        return ldap_add($this->resource, $dn, $entry);
332
    }
333
334
    /**
335
     * Deletes an entry on the current connection.
336
     * @param string $dn
337
     * @return bool
338
     */
339
    public function delete($dn)
340
    {
341
        return ldap_delete($this->resource, $dn);
342
    }
343
344
    /**
345
     * Modify the name of an entry on the current connection.
346
     *
347
     * @param string $dn
348
     * @param string $newRdn
349
     * @param string $newParent
350
     * @param bool   $deleteOldRdn
351
     * @return bool
352
     */
353
    public function rename($dn, $newRdn, $newParent, $deleteOldRdn = false)
354
    {
355
        return ldap_rename($this->resource, $dn, $newRdn, $newParent, $deleteOldRdn);
356
    }
357
358
    /**
359
     * Batch modifies an existing entry on the current connection.
360
     * The types of modifications:
361
     *      LDAP_MODIFY_BATCH_ADD - Each value specified through values is added.
362
     *      LDAP_MODIFY_BATCH_REMOVE - Each value specified through values is removed. 
363
     *          Any value of the attribute not contained in the values array will remain untouched.
364
     *      LDAP_MODIFY_BATCH_REMOVE_ALL - All values are removed from the attribute named by attrib.
365
     *      LDAP_MODIFY_BATCH_REPLACE - All current values are replaced by new one.
366
     * @param string $dn
367
     * @param array  $values array associative with three keys: "attrib", "modtype" and "values".
368
     * ```php
369
     * [
370
     *     "attrib"  => "attribute",
371
     *     "modtype" => LDAP_MODIFY_BATCH_ADD,
372
     *     "values"  => ["attribute value one"],
373
     * ],
374
     * ```
375
     * @return mixed
376
     */
377
    public function modify($dn, array $values)
378
    {
379
        return ldap_modify_batch($this->resource, $dn, $values);
380
    }    
381
    
382
    /**
383
     * Retrieve the entries from a search result.
384
     * @param resource $searchResult
385
     * @return array|boolean
386
     */
387
    public function getEntries($searchResult)
388
    {
389
        return ldap_get_entries($this->resource, $searchResult);
390
    }
391
    
392
    /**
393
     * Retrieves the number of entries from a search result.
394
     * @param resource $searchResult
395
     * @return int
396
     */
397
    public function countEntries($searchResult)
398
    {
399
        return ldap_count_entries($this->resource, $searchResult);
400
    }
401
402
    /**
403
     * Retrieves the first entry from a search result.
404
     * @param resource $searchResult
405
     * @return resource link identifier
406
     */
407
    public function getFirstEntry($searchResult)
408
    {
409
        return ldap_first_entry($this->resource, $searchResult);
410
    }
411
412
    /**
413
     * Retrieves the next entry from a search result.
414
     * @param resource $entry link identifier
415
     * @return resource
416
     */
417
    public function getNextEntry($entry)
418
    {
419
        return ldap_next_entry($this->resource, $entry);
420
    }
421
    
422
    /**
423
     * Retrieves the ldap first entry attribute.
424
     * @param resource $entry
425
     * @return string
426
     */
427
    public function getFirstAttribute($entry)
428
    {
429
        return ldap_first_attribute($this->resource, $entry);
430
    }
431
    
432
    /**
433
     * Retrieves the ldap next entry attribute.
434
     * @param resource $entry
435
     * @return string
436
     */
437
    public function getNextAttribute($entry)
438
    {
439
        return ldap_next_attribute($this->resource, $entry);
440
    }
441
442
    /**
443
     * Retrieves the ldap entry's attributes.
444
     * @param resource $entry
445
     * @return array
446
     */
447
    public function getAttributes($entry)
448
    {
449
        return ldap_get_attributes($this->resource, $entry);
450
    }
451
    
452
    /**
453
     * Retrieves all binary values from a result entry.
454
     * @param resource $entry link identifier
455
     * @param string $attribute name of attribute
456
     * @return array
457
     */
458
    public function getValuesLen($entry, $attribute)
459
    {
460
        return ldap_get_values_len($this->resource, $entry, $attribute);
461
    }
462
    
463
    /**
464
     * Retrieves the DN of a result entry.
465
     * @param resource $entry
466
     * @return string
467
     */
468
    public function getDn($entry)
469
    {
470
        return ldap_get_dn($this->resource, $entry);
471
    }
472
473
    /**
474
     * Free result memory.
475
     * @param resource $searchResult
476
     * @return bool
477
     */
478
    public function freeResult($searchResult)
479
    {
480
        return ldap_free_result($searchResult);
481
    }
482
483
    /**
484
     * Sets an option on the current connection.
485
     * @param int   $option
486
     * @param mixed $value
487
     * @return boolean
488
     */
489
    public function setOption($option, $value)
490
    {
491
        return ldap_set_option($this->resource, $option, $value);
492
    }
493
494
    /**
495
     * Starts a connection using TLS.
496
     * @return bool
497
     */
498
    public function startTLS()
499
    {
500
        return ldap_start_tls($this->resource);
501
    }
502
    
503
    /**
504
     * Send LDAP pagination control.
505
     * @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...
506
     * @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...
507
     * @param string $cookie
508
     * @return bool
509
     */
510
    public function setControlPagedResult($cookie)
511
    {
512
        return ldap_control_paged_result($this->resource, $this->pageSize, false, $cookie);
513
    }
514
515
    /**
516
     * Retrieve a paginated result response.
517
     * @param resource $result
518
     * @param string $cookie
519
     * @return bool
520
     */
521
    public function setControlPagedResultResponse($result, &$cookie)
522
    {
523
        return ldap_control_paged_result_response($this->resource, $result, $cookie);
524
    }
525
       
526
    /**
527
     * Retrieve the last error on the current connection.
528
     * @return string
529
     */
530
    public function getLastError()
531
    {
532
        return ldap_error($this->resource);
533
    }
534
    
535
    /**
536
     * Returns the number of the last error on the current connection.
537
     * @return int
538
     */
539
    public function getErrNo()
540
    {
541
        return ldap_errno($this->resource);
542
    }
543
544
    /**
545
     * Returns the error string of the specified error number.
546
     * @param int $number
547
     * @return string
548
     */
549
    public function err2Str($number)
550
    {
551
        return ldap_err2str($number);
552
    }
553
}
554