Failed Conditions
Push — psr2 ( 64159a )
by Andreas
07:54 queued 04:15
created

auth_plugin_authad::retrieveUsers()   C

Complexity

Conditions 17
Paths 34

Size

Total Lines 53
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 37
nc 34
nop 3
dl 0
loc 53
rs 6.2566
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
require_once(DOKU_PLUGIN.'authad/adLDAP/adLDAP.php');
4
require_once(DOKU_PLUGIN.'authad/adLDAP/classes/adLDAPUtils.php');
5
6
/**
7
 * Active Directory authentication backend for DokuWiki
8
 *
9
 * This makes authentication with a Active Directory server much easier
10
 * than when using the normal LDAP backend by utilizing the adLDAP library
11
 *
12
 * Usage:
13
 *   Set DokuWiki's local.protected.php auth setting to read
14
 *
15
 *   $conf['authtype']       = 'authad';
16
 *
17
 *   $conf['plugin']['authad']['account_suffix']     = '@my.domain.org';
18
 *   $conf['plugin']['authad']['base_dn']            = 'DC=my,DC=domain,DC=org';
19
 *   $conf['plugin']['authad']['domain_controllers'] = 'srv1.domain.org,srv2.domain.org';
20
 *
21
 *   //optional:
22
 *   $conf['plugin']['authad']['sso']                = 1;
23
 *   $conf['plugin']['authad']['admin_username']     = 'root';
24
 *   $conf['plugin']['authad']['admin_password']     = 'pass';
25
 *   $conf['plugin']['authad']['real_primarygroup']  = 1;
26
 *   $conf['plugin']['authad']['use_ssl']            = 1;
27
 *   $conf['plugin']['authad']['use_tls']            = 1;
28
 *   $conf['plugin']['authad']['debug']              = 1;
29
 *   // warn user about expiring password this many days in advance:
30
 *   $conf['plugin']['authad']['expirywarn']         = 5;
31
 *
32
 *   // get additional information to the userinfo array
33
 *   // add a list of comma separated ldap contact fields.
34
 *   $conf['plugin']['authad']['additional'] = 'field1,field2';
35
 *
36
 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
37
 * @author  James Van Lommel <[email protected]>
38
 * @link    http://www.nosq.com/blog/2005/08/ldap-activedirectory-and-dokuwiki/
39
 * @author  Andreas Gohr <[email protected]>
40
 * @author  Jan Schumann <[email protected]>
41
 */
42
class auth_plugin_authad extends DokuWiki_Auth_Plugin {
43
44
    /**
45
     * @var array hold connection data for a specific AD domain
46
     */
47
    protected $opts = array();
48
49
    /**
50
     * @var array open connections for each AD domain, as adLDAP objects
51
     */
52
    protected $adldap = array();
53
54
    /**
55
     * @var bool message state
56
     */
57
    protected $msgshown = false;
58
59
    /**
60
     * @var array user listing cache
61
     */
62
    protected $users = array();
63
64
    /**
65
     * @var array filter patterns for listing users
66
     */
67
    protected $_pattern = array();
68
69
    protected $_actualstart = 0;
70
71
    protected $_grpsusers = array();
72
73
    /**
74
     * Constructor
75
     */
76
    public function __construct() {
77
        global $INPUT;
78
        parent::__construct();
79
80
        // we load the config early to modify it a bit here
81
        $this->loadConfig();
82
83
        // additional information fields
84
        if(isset($this->conf['additional'])) {
85
            $this->conf['additional'] = str_replace(' ', '', $this->conf['additional']);
86
            $this->conf['additional'] = explode(',', $this->conf['additional']);
87
        } else $this->conf['additional'] = array();
88
89
        // ldap extension is needed
90
        if(!function_exists('ldap_connect')) {
91
            if($this->conf['debug'])
92
                msg("AD Auth: PHP LDAP extension not found.", -1);
93
            $this->success = false;
94
            return;
95
        }
96
97
        // Prepare SSO
98
        if(!empty($_SERVER['REMOTE_USER'])) {
99
100
            // make sure the right encoding is used
101
            if($this->getConf('sso_charset')) {
102
                $_SERVER['REMOTE_USER'] = iconv($this->getConf('sso_charset'), 'UTF-8', $_SERVER['REMOTE_USER']);
103
            } elseif(!utf8_check($_SERVER['REMOTE_USER'])) {
104
                $_SERVER['REMOTE_USER'] = utf8_encode($_SERVER['REMOTE_USER']);
105
            }
106
107
            // trust the incoming user
108
            if($this->conf['sso']) {
109
                $_SERVER['REMOTE_USER'] = $this->cleanUser($_SERVER['REMOTE_USER']);
110
111
                // we need to simulate a login
112
                if(empty($_COOKIE[DOKU_COOKIE])) {
113
                    $INPUT->set('u', $_SERVER['REMOTE_USER']);
114
                    $INPUT->set('p', 'sso_only');
115
                }
116
            }
117
        }
118
119
        // other can do's are changed in $this->_loadServerConfig() base on domain setup
120
        $this->cando['modName'] = (bool)$this->conf['update_name'];
121
        $this->cando['modMail'] = (bool)$this->conf['update_mail'];
122
        $this->cando['getUserCount'] = true;
123
    }
124
125
    /**
126
     * Load domain config on capability check
127
     *
128
     * @param string $cap
129
     * @return bool
130
     */
131
    public function canDo($cap) {
132
        //capabilities depend on config, which may change depending on domain
133
        $domain = $this->_userDomain($_SERVER['REMOTE_USER']);
134
        $this->_loadServerConfig($domain);
135
        return parent::canDo($cap);
136
    }
137
138
    /**
139
     * Check user+password [required auth function]
140
     *
141
     * Checks if the given user exists and the given
142
     * plaintext password is correct by trying to bind
143
     * to the LDAP server
144
     *
145
     * @author  James Van Lommel <[email protected]>
146
     * @param string $user
147
     * @param string $pass
148
     * @return  bool
149
     */
150
    public function checkPass($user, $pass) {
151
        if($_SERVER['REMOTE_USER'] &&
152
            $_SERVER['REMOTE_USER'] == $user &&
153
            $this->conf['sso']
154
        ) return true;
155
156
        $adldap = $this->_adldap($this->_userDomain($user));
157
        if(!$adldap) return false;
158
159
        return $adldap->authenticate($this->_userName($user), $pass);
160
    }
161
162
    /**
163
     * Return user info [required auth function]
164
     *
165
     * Returns info about the given user needs to contain
166
     * at least these fields:
167
     *
168
     * name    string  full name of the user
169
     * mail    string  email address of the user
170
     * grps    array   list of groups the user is in
171
     *
172
     * This AD specific function returns the following
173
     * addional fields:
174
     *
175
     * dn         string    distinguished name (DN)
176
     * uid        string    samaccountname
177
     * lastpwd    int       timestamp of the date when the password was set
178
     * expires    true      if the password expires
179
     * expiresin  int       seconds until the password expires
180
     * any fields specified in the 'additional' config option
181
     *
182
     * @author  James Van Lommel <[email protected]>
183
     * @param string $user
184
     * @param bool $requireGroups (optional) - ignored, groups are always supplied by this plugin
185
     * @return array
186
     */
187
    public function getUserData($user, $requireGroups=true) {
188
        global $conf;
189
        global $lang;
190
        global $ID;
191
        $adldap = $this->_adldap($this->_userDomain($user));
192
        if(!$adldap) return false;
193
194
        if($user == '') return array();
195
196
        $fields = array('mail', 'displayname', 'samaccountname', 'lastpwd', 'pwdlastset', 'useraccountcontrol');
197
198
        // add additional fields to read
199
        $fields = array_merge($fields, $this->conf['additional']);
200
        $fields = array_unique($fields);
201
        $fields = array_filter($fields);
202
203
        //get info for given user
204
        $result = $adldap->user()->info($this->_userName($user), $fields);
205
        if($result == false){
206
            return array();
207
        }
208
209
        //general user info
210
        $info = array();
211
        $info['name'] = $result[0]['displayname'][0];
212
        $info['mail'] = $result[0]['mail'][0];
213
        $info['uid']  = $result[0]['samaccountname'][0];
214
        $info['dn']   = $result[0]['dn'];
215
        //last password set (Windows counts from January 1st 1601)
216
        $info['lastpwd'] = $result[0]['pwdlastset'][0] / 10000000 - 11644473600;
217
        //will it expire?
218
        $info['expires'] = !($result[0]['useraccountcontrol'][0] & 0x10000); //ADS_UF_DONT_EXPIRE_PASSWD
219
220
        // additional information
221
        foreach($this->conf['additional'] as $field) {
222
            if(isset($result[0][strtolower($field)])) {
223
                $info[$field] = $result[0][strtolower($field)][0];
224
            }
225
        }
226
227
        // handle ActiveDirectory memberOf
228
        $info['grps'] = $adldap->user()->groups($this->_userName($user),(bool) $this->opts['recursive_groups']);
229
230
        if(is_array($info['grps'])) {
231
            foreach($info['grps'] as $ndx => $group) {
232
                $info['grps'][$ndx] = $this->cleanGroup($group);
233
            }
234
        }
235
236
        // always add the default group to the list of groups
237
        if(!is_array($info['grps']) || !in_array($conf['defaultgroup'], $info['grps'])) {
238
            $info['grps'][] = $conf['defaultgroup'];
239
        }
240
241
        // add the user's domain to the groups
242
        $domain = $this->_userDomain($user);
243
        if($domain && !in_array("domain-$domain", (array) $info['grps'])) {
244
            $info['grps'][] = $this->cleanGroup("domain-$domain");
245
        }
246
247
        // check expiry time
248
        if($info['expires'] && $this->conf['expirywarn']){
249
            $expiry = $adldap->user()->passwordExpiry($user);
250
            if(is_array($expiry)){
251
                $info['expiresat'] = $expiry['expiryts'];
252
                $info['expiresin'] = round(($info['expiresat'] - time())/(24*60*60));
253
254
                // if this is the current user, warn him (once per request only)
255
                if(($_SERVER['REMOTE_USER'] == $user) &&
256
                    ($info['expiresin'] <= $this->conf['expirywarn']) &&
257
                    !$this->msgshown
258
                ) {
259
                    $msg = sprintf($this->getLang('authpwdexpire'), $info['expiresin']);
260
                    if($this->canDo('modPass')) {
261
                        $url = wl($ID, array('do'=> 'profile'));
262
                        $msg .= ' <a href="'.$url.'">'.$lang['btn_profile'].'</a>';
263
                    }
264
                    msg($msg);
265
                    $this->msgshown = true;
266
                }
267
            }
268
        }
269
270
        return $info;
271
    }
272
273
    /**
274
     * Make AD group names usable by DokuWiki.
275
     *
276
     * Removes backslashes ('\'), pound signs ('#'), and converts spaces to underscores.
277
     *
278
     * @author  James Van Lommel ([email protected])
279
     * @param string $group
280
     * @return string
281
     */
282
    public function cleanGroup($group) {
283
        $group = str_replace('\\', '', $group);
284
        $group = str_replace('#', '', $group);
285
        $group = preg_replace('[\s]', '_', $group);
286
        $group = utf8_strtolower(trim($group));
287
        return $group;
288
    }
289
290
    /**
291
     * Sanitize user names
292
     *
293
     * Normalizes domain parts, does not modify the user name itself (unlike cleanGroup)
294
     *
295
     * @author Andreas Gohr <[email protected]>
296
     * @param string $user
297
     * @return string
298
     */
299
    public function cleanUser($user) {
300
        $domain = '';
301
302
        // get NTLM or Kerberos domain part
303
        list($dom, $user) = explode('\\', $user, 2);
304
        if(!$user) $user = $dom;
305
        if($dom) $domain = $dom;
306
        list($user, $dom) = explode('@', $user, 2);
307
        if($dom) $domain = $dom;
308
309
        // clean up both
310
        $domain = utf8_strtolower(trim($domain));
311
        $user   = utf8_strtolower(trim($user));
312
313
        // is this a known, valid domain? if not discard
314
        if(!is_array($this->conf[$domain])) {
315
            $domain = '';
316
        }
317
318
        // reattach domain
319
        if($domain) $user = "$user@$domain";
320
        return $user;
321
    }
322
323
    /**
324
     * Most values in LDAP are case-insensitive
325
     *
326
     * @return bool
327
     */
328
    public function isCaseSensitive() {
329
        return false;
330
    }
331
332
    /**
333
     * Create a Search-String useable by adLDAPUsers::all($includeDescription = false, $search = "*", $sorted = true)
334
     *
335
     * @param array $filter
336
     * @return string
337
     */
338
    protected function _constructSearchString($filter){
339
        if (!$filter){
0 ignored issues
show
Bug Best Practice introduced by
The expression $filter of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
340
            return '*';
341
        }
342
        $adldapUtils = new adLDAPUtils($this->_adldap(null));
343
        $result = '*';
344
        if (isset($filter['name'])) {
345
            $result .= ')(displayname=*' . $adldapUtils->ldapSlashes($filter['name']) . '*';
346
            unset($filter['name']);
347
        }
348
349
        if (isset($filter['user'])) {
350
            $result .= ')(samAccountName=*' . $adldapUtils->ldapSlashes($filter['user']) . '*';
351
            unset($filter['user']);
352
        }
353
354
        if (isset($filter['mail'])) {
355
            $result .= ')(mail=*' . $adldapUtils->ldapSlashes($filter['mail']) . '*';
356
            unset($filter['mail']);
357
        }
358
        return $result;
359
    }
360
361
    /**
362
     * Return a count of the number of user which meet $filter criteria
363
     *
364
     * @param array $filter  $filter array of field/pattern pairs, empty array for no filter
365
     * @return int number of users
366
     */
367
    public function getUserCount($filter = array()) {
368
        $adldap = $this->_adldap(null);
369
        if(!$adldap) {
370
            dbglog("authad/auth.php getUserCount(): _adldap not set.");
371
            return -1;
372
        }
373
        if ($filter == array()) {
374
            $result = $adldap->user()->all();
375
        } else {
376
            $searchString = $this->_constructSearchString($filter);
377
            $result = $adldap->user()->all(false, $searchString);
378
            if (isset($filter['grps'])) {
379
                $this->users = array_fill_keys($result, false);
380
                $usermanager = plugin_load("admin", "usermanager", false);
381
                $usermanager->setLastdisabled(true);
382
                if (!isset($this->_grpsusers[$this->_filterToString($filter)])){
383
                    $this->_fillGroupUserArray($filter,$usermanager->getStart() + 3*$usermanager->getPagesize());
384
                } elseif (
385
                    count($this->_grpsusers[$this->_filterToString($filter)]) <
386
                    $usermanager->getStart() + 3*$usermanager->getPagesize()
387
                ) {
388
                    $this->_fillGroupUserArray(
389
                        $filter,
390
                        $usermanager->getStart() +
391
                        3*$usermanager->getPagesize() -
392
                        count($this->_grpsusers[$this->_filterToString($filter)])
393
                    );
394
                }
395
                $result = $this->_grpsusers[$this->_filterToString($filter)];
396
            } else {
397
                $usermanager = plugin_load("admin", "usermanager", false);
398
                $usermanager->setLastdisabled(false);
399
            }
400
401
        }
402
403
        if (!$result) {
404
            return 0;
405
        }
406
        return count($result);
407
    }
408
409
    /**
410
     *
411
     * create a unique string for each filter used with a group
412
     *
413
     * @param array $filter
414
     * @return string
415
     */
416
    protected function _filterToString ($filter) {
417
        $result = '';
418
        if (isset($filter['user'])) {
419
            $result .= 'user-' . $filter['user'];
420
        }
421
        if (isset($filter['name'])) {
422
            $result .= 'name-' . $filter['name'];
423
        }
424
        if (isset($filter['mail'])) {
425
            $result .= 'mail-' . $filter['mail'];
426
        }
427
        if (isset($filter['grps'])) {
428
            $result .= 'grps-' . $filter['grps'];
429
        }
430
        return $result;
431
    }
432
433
    /**
434
     * Create an array of $numberOfAdds users passing a certain $filter, including belonging
435
     * to a certain group and save them to a object-wide array. If the array
436
     * already exists try to add $numberOfAdds further users to it.
437
     *
438
     * @param array $filter
439
     * @param int $numberOfAdds additional number of users requested
440
     * @return int number of Users actually add to Array
441
     */
442
    protected function _fillGroupUserArray($filter, $numberOfAdds){
443
        $this->_grpsusers[$this->_filterToString($filter)];
444
        $i = 0;
445
        $count = 0;
446
        $this->_constructPattern($filter);
447
        foreach ($this->users as $user => &$info) {
448
            if($i++ < $this->_actualstart) {
449
                continue;
450
            }
451
            if($info === false) {
452
                $info = $this->getUserData($user);
453
            }
454
            if($this->_filter($user, $info)) {
455
                $this->_grpsusers[$this->_filterToString($filter)][$user] = $info;
456
                if(($numberOfAdds > 0) && (++$count >= $numberOfAdds)) break;
457
            }
458
        }
459
        $this->_actualstart = $i;
460
        return $count;
461
    }
462
463
    /**
464
     * Bulk retrieval of user data
465
     *
466
     * @author  Dominik Eckelmann <[email protected]>
467
     *
468
     * @param   int $start index of first user to be returned
469
     * @param   int $limit max number of users to be returned
470
     * @param   array $filter array of field/pattern pairs, null for no filter
471
     * @return array userinfo (refer getUserData for internal userinfo details)
472
     */
473
    public function retrieveUsers($start = 0, $limit = 0, $filter = array()) {
474
        $adldap = $this->_adldap(null);
475
        if(!$adldap) return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method DokuWiki_Auth_Plugin::retrieveUsers of type array.

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...
476
477
        if(!$this->users) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->users of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
478
            //get info for given user
479
            $result = $adldap->user()->all(false, $this->_constructSearchString($filter));
480
            if (!$result) return array();
481
            $this->users = array_fill_keys($result, false);
482
        }
483
484
        $i     = 0;
485
        $count = 0;
486
        $result = array();
487
488
        if (!isset($filter['grps'])) {
489
            $usermanager = plugin_load("admin", "usermanager", false);
490
            $usermanager->setLastdisabled(false);
491
            $this->_constructPattern($filter);
492
            foreach($this->users as $user => &$info) {
493
                if($i++ < $start) {
494
                    continue;
495
                }
496
                if($info === false) {
497
                    $info = $this->getUserData($user);
498
                }
499
                $result[$user] = $info;
500
                if(($limit > 0) && (++$count >= $limit)) break;
501
            }
502
        } else {
503
            $usermanager = plugin_load("admin", "usermanager", false);
504
            $usermanager->setLastdisabled(true);
505
            if (
506
                !isset($this->_grpsusers[$this->_filterToString($filter)]) ||
507
                count($this->_grpsusers[$this->_filterToString($filter)]) < ($start+$limit)
508
            ) {
509
                $this->_fillGroupUserArray(
510
                    $filter,
511
                    $start+$limit - count($this->_grpsusers[$this->_filterToString($filter)]) +1
512
                );
513
            }
514
            if (!$this->_grpsusers[$this->_filterToString($filter)]) return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method DokuWiki_Auth_Plugin::retrieveUsers of type array.

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...
515
            foreach($this->_grpsusers[$this->_filterToString($filter)] as $user => &$info) {
516
                if($i++ < $start) {
517
                    continue;
518
                }
519
                $result[$user] = $info;
520
                if(($limit > 0) && (++$count >= $limit)) break;
521
            }
522
523
        }
524
        return $result;
525
    }
526
527
    /**
528
     * Modify user data
529
     *
530
     * @param   string $user      nick of the user to be changed
531
     * @param   array  $changes   array of field/value pairs to be changed
532
     * @return  bool
533
     */
534
    public function modifyUser($user, $changes) {
535
        $return = true;
536
        $adldap = $this->_adldap($this->_userDomain($user));
537
        if(!$adldap) {
538
            msg($this->getLang('connectfail'), -1);
539
            return false;
540
        }
541
542
        // password changing
543
        if(isset($changes['pass'])) {
544
            try {
545
                $return = $adldap->user()->password($this->_userName($user),$changes['pass']);
546
            } catch (adLDAPException $e) {
0 ignored issues
show
Bug introduced by
The class adLDAPException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
547
                if ($this->conf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
548
                $return = false;
549
            }
550
            if(!$return) msg($this->getLang('passchangefail'), -1);
551
        }
552
553
        // changing user data
554
        $adchanges = array();
555
        if(isset($changes['name'])) {
556
            // get first and last name
557
            $parts                     = explode(' ', $changes['name']);
558
            $adchanges['surname']      = array_pop($parts);
559
            $adchanges['firstname']    = join(' ', $parts);
560
            $adchanges['display_name'] = $changes['name'];
561
        }
562
        if(isset($changes['mail'])) {
563
            $adchanges['email'] = $changes['mail'];
564
        }
565
        if(count($adchanges)) {
566
            try {
567
                $return = $return & $adldap->user()->modify($this->_userName($user),$adchanges);
568
            } catch (adLDAPException $e) {
0 ignored issues
show
Bug introduced by
The class adLDAPException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
569
                if ($this->conf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
570
                $return = false;
571
            }
572
            if(!$return) msg($this->getLang('userchangefail'), -1);
573
        }
574
575
        return $return;
576
    }
577
578
    /**
579
     * Initialize the AdLDAP library and connect to the server
580
     *
581
     * When you pass null as domain, it will reuse any existing domain.
582
     * Eg. the one of the logged in user. It falls back to the default
583
     * domain if no current one is available.
584
     *
585
     * @param string|null $domain The AD domain to use
586
     * @return adLDAP|bool true if a connection was established
587
     */
588
    protected function _adldap($domain) {
589
        if(is_null($domain) && is_array($this->opts)) {
590
            $domain = $this->opts['domain'];
591
        }
592
593
        $this->opts = $this->_loadServerConfig((string) $domain);
594
        if(isset($this->adldap[$domain])) return $this->adldap[$domain];
595
596
        // connect
597
        try {
598
            $this->adldap[$domain] = new adLDAP($this->opts);
599
            return $this->adldap[$domain];
600
        } catch(adLDAPException $e) {
0 ignored issues
show
Bug introduced by
The class adLDAPException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
601
            if($this->conf['debug']) {
602
                msg('AD Auth: '.$e->getMessage(), -1);
603
            }
604
            $this->success         = false;
605
            $this->adldap[$domain] = null;
606
        }
607
        return false;
608
    }
609
610
    /**
611
     * Get the domain part from a user
612
     *
613
     * @param string $user
614
     * @return string
615
     */
616
    public function _userDomain($user) {
617
        list(, $domain) = explode('@', $user, 2);
618
        return $domain;
619
    }
620
621
    /**
622
     * Get the user part from a user
623
     *
624
     * @param string $user
625
     * @return string
626
     */
627
    public function _userName($user) {
628
        list($name) = explode('@', $user, 2);
629
        return $name;
630
    }
631
632
    /**
633
     * Fetch the configuration for the given AD domain
634
     *
635
     * @param string $domain current AD domain
636
     * @return array
637
     */
638
    protected function _loadServerConfig($domain) {
639
        // prepare adLDAP standard configuration
640
        $opts = $this->conf;
641
642
        $opts['domain'] = $domain;
643
644
        // add possible domain specific configuration
645
        if($domain && is_array($this->conf[$domain])) foreach($this->conf[$domain] as $key => $val) {
646
            $opts[$key] = $val;
647
        }
648
649
        // handle multiple AD servers
650
        $opts['domain_controllers'] = explode(',', $opts['domain_controllers']);
651
        $opts['domain_controllers'] = array_map('trim', $opts['domain_controllers']);
652
        $opts['domain_controllers'] = array_filter($opts['domain_controllers']);
653
654
        // compatibility with old option name
655
        if(empty($opts['admin_username']) && !empty($opts['ad_username'])) {
656
            $opts['admin_username'] = $opts['ad_username'];
657
        }
658
        if(empty($opts['admin_password']) && !empty($opts['ad_password'])) {
659
            $opts['admin_password'] = $opts['ad_password'];
660
        }
661
        $opts['admin_password'] = conf_decodeString($opts['admin_password']); // deobfuscate
662
663
        // we can change the password if SSL is set
664
        if($opts['use_ssl'] || $opts['use_tls']) {
665
            $this->cando['modPass'] = true;
666
        } else {
667
            $this->cando['modPass'] = false;
668
        }
669
670
        // adLDAP expects empty user/pass as NULL, we're less strict FS#2781
671
        if(empty($opts['admin_username'])) $opts['admin_username'] = null;
672
        if(empty($opts['admin_password'])) $opts['admin_password'] = null;
673
674
        // user listing needs admin priviledges
675
        if(!empty($opts['admin_username']) && !empty($opts['admin_password'])) {
676
            $this->cando['getUsers'] = true;
677
        } else {
678
            $this->cando['getUsers'] = false;
679
        }
680
681
        return $opts;
682
    }
683
684
    /**
685
     * Returns a list of configured domains
686
     *
687
     * The default domain has an empty string as key
688
     *
689
     * @return array associative array(key => domain)
690
     */
691
    public function _getConfiguredDomains() {
692
        $domains = array();
693
        if(empty($this->conf['account_suffix'])) return $domains; // not configured yet
694
695
        // add default domain, using the name from account suffix
696
        $domains[''] = ltrim($this->conf['account_suffix'], '@');
697
698
        // find additional domains
699
        foreach($this->conf as $key => $val) {
700
            if(is_array($val) && isset($val['account_suffix'])) {
701
                $domains[$key] = ltrim($val['account_suffix'], '@');
702
            }
703
        }
704
        ksort($domains);
705
706
        return $domains;
707
    }
708
709
    /**
710
     * Check provided user and userinfo for matching patterns
711
     *
712
     * The patterns are set up with $this->_constructPattern()
713
     *
714
     * @author Chris Smith <[email protected]>
715
     *
716
     * @param string $user
717
     * @param array  $info
718
     * @return bool
719
     */
720
    protected function _filter($user, $info) {
721
        foreach($this->_pattern as $item => $pattern) {
722
            if($item == 'user') {
723
                if(!preg_match($pattern, $user)) return false;
724
            } else if($item == 'grps') {
725
                if(!count(preg_grep($pattern, $info['grps']))) return false;
726
            } else {
727
                if(!preg_match($pattern, $info[$item])) return false;
728
            }
729
        }
730
        return true;
731
    }
732
733
    /**
734
     * Create a pattern for $this->_filter()
735
     *
736
     * @author Chris Smith <[email protected]>
737
     *
738
     * @param array $filter
739
     */
740
    protected function _constructPattern($filter) {
741
        $this->_pattern = array();
742
        foreach($filter as $item => $pattern) {
743
            $this->_pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters
744
        }
745
    }
746
}
747