Completed
Push — master ( 26338d...808509 )
by Christopher
06:37
created

Connection::changePassword()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 3
nop 3
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
212
        // Authenticate user
213
        return ldap_bind($this->resource, $this->userDn, $password);
214
    }
215
    
216
    /**
217
     * Change the password of the current user. This must be performed over TLS.
218
     * @param string $username User for change password
219
     * @param string $oldPassword The old password
220
     * @param string $newPassword The new password
221
     * @return bool return true if change password is success
222
     * @throws \Exception
223
     */
224
    public function changePassword($username, $oldPassword, $newPassword)
225
    {        
226
        if (!$this->useTLS) {
227
            $message = 'TLS must be configured on your web server and enabled to change passwords.';
228
            throw new \Exception($message);
229
        }
230
231
        // Open connection with user
232
        if(!$this->auth($username, $oldPassword)){
233
            return false;
234
        }
235
        
236
        // Open connection with manager
237
        $this->open();
238
        
239
        // Replace passowrd
240
        $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...
241
        return ldap_mod_replace($this->resource, $this->userDn, $modifications);
242
    }
243
    
244
    /**
245
     * Closes the current connection.
246
     *
247
     * @return boolean
248
     */
249
    public function close()
250
    {
251
        if (is_resource($this->resource)) {
252
            ldap_close($this->resource);
253
        }
254
        return true;
255
    }
256
257
    /**
258
     * Execute ldap functions like.
259
     *
260
     * http://php.net/manual/en/ref.ldap.php
261
     *
262
     * @param  string $function php LDAP function
263
     * @param  array $params params for execute ldap function
264
     * @return bool|DataReader
265
     */
266
    public function executeQuery($function, $params)
267
    {
268
        $this->open();
269
        $results = [];
270
        $cookie = '';
271
        
272
        do {
273
            $this->setControlPagedResult($cookie);
274
275
            $result = call_user_func($function, $this->resource, ...$params);
276
277
            $this->setControlPagedResultResponse($result, $cookie);
278
            $results[] = $result;            
279
        } while ($cookie !== null && $cookie != '');        
280
281
        if($this->offset > 0){
282
            $results = $results[intval($this->offset/$this->pageSize -1)];
283
        }
284
        
285
        return new DataReader($this, $results);
286
    }
287
    
288
    /**
289
     * Returns true/false if the current connection is bound.
290
     * @return bool
291
     */
292
    public function getBound()
293
    {
294
        return $this->_bound;
295
    }
296
    
297
    /**
298
     * Get the current resource of connection.
299
     * @return resource
300
     */
301
    public function getResource()
302
    {
303
        return $this->resource;
304
    }
305
    
306
    /**
307
     * Sorts an AD search result by the specified attribute.
308
     * @param resource $result
309
     * @param string   $attribute
310
     * @return bool
311
     */
312
    public function sort($result, $attribute)
313
    {
314
        return ldap_sort($this->resource, $result, $attribute);
315
    }
316
317
    /**
318
     * Adds an entry to the current connection.
319
     * @param string $dn
320
     * @param array  $entry
321
     * @return bool
322
     */
323
    public function add($dn, array $entry)
324
    {
325
        return ldap_add($this->resource, $dn, $entry);
326
    }
327
328
    /**
329
     * Deletes an entry on the current connection.
330
     * @param string $dn
331
     * @return bool
332
     */
333
    public function delete($dn)
334
    {
335
        return ldap_delete($this->resource, $dn);
336
    }
337
338
    /**
339
     * Modify the name of an entry on the current connection.
340
     *
341
     * @param string $dn
342
     * @param string $newRdn
343
     * @param string $newParent
344
     * @param bool   $deleteOldRdn
345
     * @return bool
346
     */
347
    public function rename($dn, $newRdn, $newParent, $deleteOldRdn = false)
348
    {
349
        return ldap_rename($this->resource, $dn, $newRdn, $newParent, $deleteOldRdn);
350
    }
351
352
    /**
353
     * Batch modifies an existing entry on the current connection.
354
     * The types of modifications:
355
     *      LDAP_MODIFY_BATCH_ADD - Each value specified through values is added.
356
     *      LDAP_MODIFY_BATCH_REMOVE - Each value specified through values is removed. 
357
     *          Any value of the attribute not contained in the values array will remain untouched.
358
     *      LDAP_MODIFY_BATCH_REMOVE_ALL - All values are removed from the attribute named by attrib.
359
     *      LDAP_MODIFY_BATCH_REPLACE - All current values are replaced by new one.
360
     * @param string $dn
361
     * @param array  $values array associative with three keys: "attrib", "modtype" and "values".
362
     * ```php
363
     * [
364
     *     "attrib"  => "attribute",
365
     *     "modtype" => LDAP_MODIFY_BATCH_ADD,
366
     *     "values"  => ["attribute value one"],
367
     * ],
368
     * ```
369
     * @return mixed
370
     */
371
    public function modify($dn, array $values)
372
    {
373
        return ldap_modify_batch($this->resource, $dn, $values);
374
    }    
375
    
376
    /**
377
     * Retrieve the entries from a search result.
378
     * @param resource $searchResult
379
     * @return array|boolean
380
     */
381
    public function getEntries($searchResult)
382
    {
383
        return ldap_get_entries($this->resource, $searchResult);
384
    }
385
    
386
    /**
387
     * Retrieves the number of entries from a search result.
388
     * @param resource $searchResult
389
     * @return int
390
     */
391
    public function countEntries($searchResult)
392
    {
393
        return ldap_count_entries($this->resource, $searchResult);
394
    }
395
396
    /**
397
     * Retrieves the first entry from a search result.
398
     * @param resource $searchResult
399
     * @return resource link identifier
400
     */
401
    public function getFirstEntry($searchResult)
402
    {
403
        return ldap_first_entry($this->resource, $searchResult);
404
    }
405
406
    /**
407
     * Retrieves the next entry from a search result.
408
     * @param resource $entry link identifier
409
     * @return resource
410
     */
411
    public function getNextEntry($entry)
412
    {
413
        return ldap_next_entry($this->resource, $entry);
414
    }
415
    
416
    /**
417
     * Retrieves the ldap first entry attribute.
418
     * @param resource $entry
419
     * @return string
420
     */
421
    public function getFirstAttribute($entry)
422
    {
423
        return ldap_first_attribute($this->resource, $entry);
424
    }
425
    
426
    /**
427
     * Retrieves the ldap next entry attribute.
428
     * @param resource $entry
429
     * @return string
430
     */
431
    public function getNextAttribute($entry)
432
    {
433
        return ldap_next_attribute($this->resource, $entry);
434
    }
435
436
    /**
437
     * Retrieves the ldap entry's attributes.
438
     * @param resource $entry
439
     * @return array
440
     */
441
    public function getAttributes($entry)
442
    {
443
        return ldap_get_attributes($this->resource, $entry);
444
    }
445
    
446
    /**
447
     * Retrieves all binary values from a result entry.
448
     * @param resource $entry link identifier
449
     * @param string $attribute name of attribute
450
     * @return array
451
     */
452
    public function getValuesLen($entry, $attribute)
453
    {
454
        return ldap_get_values_len($this->resource, $entry, $attribute);
455
    }
456
    
457
    /**
458
     * Retrieves the DN of a result entry.
459
     * @param resource $entry
460
     * @return string
461
     */
462
    public function getDn($entry)
463
    {
464
        return ldap_get_dn($this->resource, $entry);
465
    }
466
467
    /**
468
     * Free result memory.
469
     * @param resource $searchResult
470
     * @return bool
471
     */
472
    public function freeResult($searchResult)
473
    {
474
        return ldap_free_result($searchResult);
475
    }
476
477
    /**
478
     * Sets an option on the current connection.
479
     * @param int   $option
480
     * @param mixed $value
481
     * @return boolean
482
     */
483
    public function setOption($option, $value)
484
    {
485
        return ldap_set_option($this->resource, $option, $value);
486
    }
487
488
    /**
489
     * Starts a connection using TLS.
490
     * @return bool
491
     */
492
    public function startTLS()
493
    {
494
        return ldap_start_tls($this->resource);
495
    }
496
    
497
    /**
498
     * Send LDAP pagination control.
499
     * @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...
500
     * @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...
501
     * @param string $cookie
502
     * @return bool
503
     */
504
    public function setControlPagedResult($cookie)
505
    {
506
        return ldap_control_paged_result($this->resource, $this->pageSize, false, $cookie);
507
    }
508
509
    /**
510
     * Retrieve a paginated result response.
511
     * @param resource $result
512
     * @param string $cookie
513
     * @return bool
514
     */
515
    public function setControlPagedResultResponse($result, &$cookie)
516
    {
517
        return ldap_control_paged_result_response($this->resource, $result, $cookie);
518
    }
519
       
520
    /**
521
     * Retrieve the last error on the current connection.
522
     * @return string
523
     */
524
    public function getLastError()
525
    {
526
        return ldap_error($this->resource);
527
    }
528
    
529
    /**
530
     * Returns the number of the last error on the current connection.
531
     * @return int
532
     */
533
    public function getErrNo()
534
    {
535
        return ldap_errno($this->resource);
536
    }
537
538
    /**
539
     * Returns the error string of the specified error number.
540
     * @param int $number
541
     * @return string
542
     */
543
    public function err2Str($number)
544
    {
545
        return ldap_err2str($number);
546
    }
547
}
548