Failed Conditions
Push — addomainless-stable ( 2f474a )
by Andreas
06:21 queued 03:25
created

auth_plugin_authad::getUserName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
// must be run within Dokuwiki
3
if(!defined('DOKU_INC')) die();
4
5
require_once(DOKU_PLUGIN.'authad/adLDAP/adLDAP.php');
6
require_once(DOKU_PLUGIN.'authad/adLDAP/classes/adLDAPUtils.php');
7
8
/**
9
 * Active Directory authentication backend for DokuWiki
10
 *
11
 * This makes authentication with a Active Directory server much easier
12
 * than when using the normal LDAP backend by utilizing the adLDAP library
13
 *
14
 * Usage:
15
 *   Set DokuWiki's local.protected.php auth setting to read
16
 *
17
 *   $conf['authtype']       = 'authad';
18
 *
19
 *   $conf['plugin']['authad']['account_suffix']     = '@my.domain.org';
20
 *   $conf['plugin']['authad']['base_dn']            = 'DC=my,DC=domain,DC=org';
21
 *   $conf['plugin']['authad']['domain_controllers'] = 'srv1.domain.org,srv2.domain.org';
22
 *
23
 *   //optional:
24
 *   $conf['plugin']['authad']['sso']                = 1;
25
 *   $conf['plugin']['authad']['admin_username']     = 'root';
26
 *   $conf['plugin']['authad']['admin_password']     = 'pass';
27
 *   $conf['plugin']['authad']['real_primarygroup']  = 1;
28
 *   $conf['plugin']['authad']['use_ssl']            = 1;
29
 *   $conf['plugin']['authad']['use_tls']            = 1;
30
 *   $conf['plugin']['authad']['debug']              = 1;
31
 *   // warn user about expiring password this many days in advance:
32
 *   $conf['plugin']['authad']['expirywarn']         = 5;
33
 *
34
 *   // get additional information to the userinfo array
35
 *   // add a list of comma separated ldap contact fields.
36
 *   $conf['plugin']['authad']['additional'] = 'field1,field2';
37
 *
38
 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
39
 * @author  James Van Lommel <[email protected]>
40
 * @link    http://www.nosq.com/blog/2005/08/ldap-activedirectory-and-dokuwiki/
41
 * @author  Andreas Gohr <[email protected]>
42
 * @author  Jan Schumann <[email protected]>
43
 */
44
class auth_plugin_authad extends DokuWiki_Auth_Plugin {
45
46
    /**
47
     * @var array hold connection data for a specific AD domain
48
     */
49
    protected $opts = array();
50
51
    /**
52
     * @var array open connections for each AD domain, as adLDAP objects
53
     */
54
    protected $adldap = array();
55
56
    /**
57
     * @var bool message state
58
     */
59
    protected $msgshown = false;
60
61
    /**
62
     * @var array user listing cache
63
     */
64
    protected $users = array();
65
66
    /**
67
     * @var array filter patterns for listing users
68
     */
69
    protected $_pattern = array();
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
        try {
160
            return $adldap->authenticate($this->getUserName($user), $pass);
161
        } 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...
162
            return false;
163
        }
164
    }
165
166
    /**
167
     * Return user info [required auth function]
168
     *
169
     * Returns info about the given user needs to contain
170
     * at least these fields:
171
     *
172
     * name    string  full name of the user
173
     * mail    string  email address of the user
174
     * grps    array   list of groups the user is in
175
     *
176
     * This AD specific function returns the following
177
     * addional fields:
178
     *
179
     * dn         string    distinguished name (DN)
180
     * uid        string    samaccountname
181
     * lastpwd    int       timestamp of the date when the password was set
182
     * expires    true      if the password expires
183
     * expiresin  int       seconds until the password expires
184
     * any fields specified in the 'additional' config option
185
     *
186
     * @author  James Van Lommel <[email protected]>
187
     * @param string $user
188
     * @param bool $requireGroups (optional) - ignored, groups are always supplied by this plugin
189
     * @return array|false
190
     */
191
    public function getUserData($user, $requireGroups=true) {
192
        global $conf;
193
        global $lang;
194
        global $ID;
195
        $adldap = $this->_adldap($this->_userDomain($user));
196
        if(!$adldap) return false;
197
198
        if($user == '') return array();
199
200
        $fields = array('mail', 'displayname', 'samaccountname', 'lastpwd', 'pwdlastset', 'useraccountcontrol');
201
202
        // add additional fields to read
203
        $fields = array_merge($fields, $this->conf['additional']);
204
        $fields = array_unique($fields);
205
        $fields = array_filter($fields);
206
207
        //get info for given user
208
        $result = $adldap->user()->info($this->getUserName($user), $fields);
209
        if($result == false){
210
            return array();
211
        }
212
213
        //general user info
214
        $info = array();
215
        $info['name'] = $result[0]['displayname'][0];
216
        $info['mail'] = $result[0]['mail'][0];
217
        $info['uid']  = $result[0]['samaccountname'][0];
218
        $info['dn']   = $result[0]['dn'];
219
        //last password set (Windows counts from January 1st 1601)
220
        $info['lastpwd'] = $result[0]['pwdlastset'][0] / 10000000 - 11644473600;
221
        //will it expire?
222
        $info['expires'] = !($result[0]['useraccountcontrol'][0] & 0x10000); //ADS_UF_DONT_EXPIRE_PASSWD
223
224
        // additional information
225
        foreach($this->conf['additional'] as $field) {
226
            if(isset($result[0][strtolower($field)])) {
227
                $info[$field] = $result[0][strtolower($field)][0];
228
            }
229
        }
230
231
        // handle ActiveDirectory memberOf
232
        $info['grps'] = $adldap->user()->groups($this->getUserName($user),(bool) $this->opts['recursive_groups']);
233
234
        if(is_array($info['grps'])) {
235
            foreach($info['grps'] as $ndx => $group) {
236
                $info['grps'][$ndx] = $this->cleanGroup($group);
237
            }
238
        }
239
240
        // always add the default group to the list of groups
241
        if(!is_array($info['grps']) || !in_array($conf['defaultgroup'], $info['grps'])) {
242
            $info['grps'][] = $conf['defaultgroup'];
243
        }
244
245
        // add the user's domain to the groups
246
        $domain = $this->_userDomain($user);
247
        if($domain && !in_array("domain-$domain", (array) $info['grps'])) {
248
            $info['grps'][] = $this->cleanGroup("domain-$domain");
249
        }
250
251
        // check expiry time
252
        if($info['expires'] && $this->conf['expirywarn']){
253
            $expiry = $adldap->user()->passwordExpiry($user);
254
            if(is_array($expiry)){
255
                $info['expiresat'] = $expiry['expiryts'];
256
                $info['expiresin'] = round(($info['expiresat'] - time())/(24*60*60));
257
258
                // if this is the current user, warn him (once per request only)
259
                if(($_SERVER['REMOTE_USER'] == $user) &&
260
                    ($info['expiresin'] <= $this->conf['expirywarn']) &&
261
                    !$this->msgshown
262
                ) {
263
                    $msg = sprintf($this->getLang('authpwdexpire'), $info['expiresin']);
264
                    if($this->canDo('modPass')) {
265
                        $url = wl($ID, array('do'=> 'profile'));
266
                        $msg .= ' <a href="'.$url.'">'.$lang['btn_profile'].'</a>';
267
                    }
268
                    msg($msg);
269
                    $this->msgshown = true;
270
                }
271
            }
272
        }
273
274
        return $info;
275
    }
276
277
    /**
278
     * Make AD group names usable by DokuWiki.
279
     *
280
     * Removes backslashes ('\'), pound signs ('#'), and converts spaces to underscores.
281
     *
282
     * @author  James Van Lommel ([email protected])
283
     * @param string $group
284
     * @return string
285
     */
286
    public function cleanGroup($group) {
287
        $group = str_replace('\\', '', $group);
288
        $group = str_replace('#', '', $group);
289
        $group = preg_replace('[\s]', '_', $group);
290
        $group = utf8_strtolower(trim($group));
291
        return $group;
292
    }
293
294
    /**
295
     * Sanitize user names
296
     *
297
     * Normalizes domain parts, does not modify the user name itself (unlike cleanGroup)
298
     *
299
     * @author Andreas Gohr <[email protected]>
300
     * @param string $user
301
     * @return string
302
     */
303
    public function cleanUser($user) {
304
        $domain = '';
305
306
        // get NTLM or Kerberos domain part
307
        list($dom, $user) = explode('\\', $user, 2);
308
        if(!$user) $user = $dom;
309
        if($dom) $domain = $dom;
310
        list($user, $dom) = explode('@', $user, 2);
311
        if($dom) $domain = $dom;
312
313
        // clean up both
314
        $domain = utf8_strtolower(trim($domain));
315
        $user   = utf8_strtolower(trim($user));
316
317
        // is this a known, valid domain or do we work without account suffix? if not discard
318
        if (!is_array($this->conf[$domain]) && $this->conf['account_suffix'] !== '') {
319
            $domain = '';
320
        }
321
322
        // reattach domain
323
        if($domain) $user = "$user@$domain";
324
        return $user;
325
    }
326
327
    /**
328
     * Most values in LDAP are case-insensitive
329
     *
330
     * @return bool
331
     */
332
    public function isCaseSensitive() {
333
        return false;
334
    }
335
336
    /**
337
     * Create a Search-String useable by adLDAPUsers::all($includeDescription = false, $search = "*", $sorted = true)
338
     *
339
     * @param array $filter
340
     * @return string
341
     */
342
    protected function _constructSearchString($filter){
343
        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...
344
            return '*';
345
        }
346
        $adldapUtils = new adLDAPUtils($this->_adldap(null));
347
        $result = '*';
348
        if (isset($filter['name'])) {
349
            $result .= ')(displayname=*' . $adldapUtils->ldapSlashes($filter['name']) . '*';
350
            unset($filter['name']);
351
        }
352
353
        if (isset($filter['user'])) {
354
            $result .= ')(samAccountName=*' . $adldapUtils->ldapSlashes($filter['user']) . '*';
355
            unset($filter['user']);
356
        }
357
358
        if (isset($filter['mail'])) {
359
            $result .= ')(mail=*' . $adldapUtils->ldapSlashes($filter['mail']) . '*';
360
            unset($filter['mail']);
361
        }
362
        return $result;
363
    }
364
365
    /**
366
     * Return a count of the number of user which meet $filter criteria
367
     *
368
     * @param array $filter  $filter array of field/pattern pairs, empty array for no filter
369
     * @return int number of users
370
     */
371
    public function getUserCount($filter = array()) {
372
        $adldap = $this->_adldap(null);
373
        if(!$adldap) {
374
            dbglog("authad/auth.php getUserCount(): _adldap not set.");
375
            return -1;
376
        }
377
        if ($filter == array()) {
378
            $result = $adldap->user()->all();
379
        } else {
380
            $searchString = $this->_constructSearchString($filter);
381
            $result = $adldap->user()->all(false, $searchString);
382
            if (isset($filter['grps'])) {
383
                $this->users = array_fill_keys($result, false);
384
                $usermanager = plugin_load("admin", "usermanager", false);
385
                $usermanager->setLastdisabled(true);
386
                if (!isset($this->_grpsusers[$this->_filterToString($filter)])){
387
                    $this->_fillGroupUserArray($filter,$usermanager->getStart() + 3*$usermanager->getPagesize());
388
                } elseif (count($this->_grpsusers[$this->_filterToString($filter)]) < $usermanager->getStart() + 3*$usermanager->getPagesize()) {
389
                    $this->_fillGroupUserArray($filter,$usermanager->getStart() + 3*$usermanager->getPagesize() - count($this->_grpsusers[$this->_filterToString($filter)]));
390
                }
391
                $result = $this->_grpsusers[$this->_filterToString($filter)];
392
            } else {
393
                $usermanager = plugin_load("admin", "usermanager", false);
394
                $usermanager->setLastdisabled(false);
395
            }
396
397
        }
398
399
        if (!$result) {
400
            return 0;
401
        }
402
        return count($result);
403
    }
404
405
    /**
406
     *
407
     * create a unique string for each filter used with a group
408
     *
409
     * @param array $filter
410
     * @return string
411
     */
412
    protected function _filterToString ($filter) {
413
        $result = '';
414
        if (isset($filter['user'])) {
415
            $result .= 'user-' . $filter['user'];
416
        }
417
        if (isset($filter['name'])) {
418
            $result .= 'name-' . $filter['name'];
419
        }
420
        if (isset($filter['mail'])) {
421
            $result .= 'mail-' . $filter['mail'];
422
        }
423
        if (isset($filter['grps'])) {
424
            $result .= 'grps-' . $filter['grps'];
425
        }
426
        return $result;
427
    }
428
429
    /**
430
     * Create an array of $numberOfAdds users passing a certain $filter, including belonging
431
     * to a certain group and save them to a object-wide array. If the array
432
     * already exists try to add $numberOfAdds further users to it.
433
     *
434
     * @param array $filter
435
     * @param int $numberOfAdds additional number of users requested
436
     * @return int number of Users actually add to Array
437
     */
438
    protected function _fillGroupUserArray($filter, $numberOfAdds)
439
    {
440
        if (isset($this->_grpsusers[$this->_filterToString($filter)])) {
441
            $actualstart = count($this->_grpsusers[$this->_filterToString($filter)]);
442
        } else {
443
            $this->_grpsusers[$this->_filterToString($filter)] = [];
444
            $actualstart = 0;
445
        }
446
447
        $i=0;
448
        $count = 0;
449
        foreach ($this->users as $user => &$info) {
450
            if ($i++ < $actualstart) {
451
                continue;
452
            }
453
            if($info === false) {
454
                $info = $this->getUserData($user);
455
            }
456
            if($this->_filter($user, $info)) {
457
                $this->_grpsusers[$this->_filterToString($filter)][$user] = $info;
458
                if(($numberOfAdds > 0) && (++$count >= $numberOfAdds)) break;
459
            }
460
        }
461
        return $count;
462
    }
463
464
    /**
465
     * Bulk retrieval of user data
466
     *
467
     * @author  Dominik Eckelmann <[email protected]>
468
     *
469
     * @param   int $start index of first user to be returned
470
     * @param   int $limit max number of users to be returned
471
     * @param   array $filter array of field/pattern pairs, null for no filter
472
     * @return array|false userinfo (refer getUserData for internal userinfo details)
473
     */
474
    public function retrieveUsers($start = 0, $limit = 0, $filter = array()) {
475
        $adldap = $this->_adldap(null);
476
        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...
477
478
        //if (!$this->users) {
479
        //get info for given user
480
        $result = $adldap->user()->all(false, $this->_constructSearchString($filter));
481
        if (!$result) return array();
482
        $this->users = array_fill_keys($result, false);
483
        //}
484
485
        $i     = 0;
486
        $count = 0;
487
        $result = array();
488
489
        if (!isset($filter['grps'])) {
490
            $usermanager = plugin_load("admin", "usermanager", false);
491
            $usermanager->setLastdisabled(false);
492
            $this->_constructPattern($filter);
493
            foreach($this->users as $user => &$info) {
494
                if($i++ < $start) {
495
                    continue;
496
                }
497
                if($info === false) {
498
                    $info = $this->getUserData($user);
499
                }
500
                $result[$user] = $info;
501
                if(($limit > 0) && (++$count >= $limit)) break;
502
            }
503
        } else {
504
            $usermanager = plugin_load("admin", "usermanager", false);
505
            $usermanager->setLastdisabled(true);
506
507
            if (!isset($this->_grpsusers[$this->_filterToString($filter)]) ||
508
                count($this->_grpsusers[$this->_filterToString($filter)]) < ($start+$limit)
509
            ) {
510
                if(!isset($this->_grpsusers[$this->_filterToString($filter)])) {
511
                    $this->_grpsusers[$this->_filterToString($filter)] = [];
512
                }
513
514
                $this->_fillGroupUserArray(
515
                    $filter,
516
                    $start+$limit - count($this->_grpsusers[$this->_filterToString($filter)]) +1
517
                );
518
            }
519
            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...
520
            foreach($this->_grpsusers[$this->_filterToString($filter)] as $user => &$info) {
521
                if($i++ < $start) {
522
                    continue;
523
                }
524
                $result[$user] = $info;
525
                if(($limit > 0) && (++$count >= $limit)) break;
526
            }
527
528
        }
529
        return $result;
530
    }
531
532
    /**
533
     * Modify user data
534
     *
535
     * @param   string $user      nick of the user to be changed
536
     * @param   array  $changes   array of field/value pairs to be changed
537
     * @return  bool
538
     */
539
    public function modifyUser($user, $changes) {
540
        $return = true;
541
        $adldap = $this->_adldap($this->_userDomain($user));
542
        if(!$adldap) {
543
            msg($this->getLang('connectfail'), -1);
544
            return false;
545
        }
546
547
        // password changing
548
        if(isset($changes['pass'])) {
549
            try {
550
                $return = $adldap->user()->password($this->getUserName($user),$changes['pass']);
551
            } 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...
552
                if ($this->conf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
553
                $return = false;
554
            }
555
            if(!$return) msg($this->getLang('passchangefail'), -1);
556
        }
557
558
        // changing user data
559
        $adchanges = array();
560
        if(isset($changes['name'])) {
561
            // get first and last name
562
            $parts                     = explode(' ', $changes['name']);
563
            $adchanges['surname']      = array_pop($parts);
564
            $adchanges['firstname']    = join(' ', $parts);
565
            $adchanges['display_name'] = $changes['name'];
566
        }
567
        if(isset($changes['mail'])) {
568
            $adchanges['email'] = $changes['mail'];
569
        }
570
        if(count($adchanges)) {
571
            try {
572
                $return = $return & $adldap->user()->modify($this->getUserName($user),$adchanges);
573
            } 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...
574
                if ($this->conf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
575
                $return = false;
576
            }
577
            if(!$return) msg($this->getLang('userchangefail'), -1);
578
        }
579
580
        return $return;
581
    }
582
583
    /**
584
     * Initialize the AdLDAP library and connect to the server
585
     *
586
     * When you pass null as domain, it will reuse any existing domain.
587
     * Eg. the one of the logged in user. It falls back to the default
588
     * domain if no current one is available.
589
     *
590
     * @param string|null $domain The AD domain to use
591
     * @return adLDAP|bool true if a connection was established
592
     */
593
    protected function _adldap($domain) {
594
        if(is_null($domain) && is_array($this->opts)) {
595
            $domain = $this->opts['domain'];
596
        }
597
598
        $this->opts = $this->_loadServerConfig((string) $domain);
599
        if(isset($this->adldap[$domain])) return $this->adldap[$domain];
600
601
        // connect
602
        try {
603
            $this->adldap[$domain] = new adLDAP($this->opts);
604
            return $this->adldap[$domain];
605
        } catch(Exception $e) {
606
            if($this->conf['debug']) {
607
                msg('AD Auth: '.$e->getMessage(), -1);
608
            }
609
            $this->success         = false;
610
            $this->adldap[$domain] = null;
611
        }
612
        return false;
613
    }
614
615
    /**
616
     * Get the domain part from a user
617
     *
618
     * @param string $user
619
     * @return string
620
     */
621
    public function _userDomain($user) {
622
        list(, $domain) = explode('@', $user, 2);
623
        return $domain;
624
    }
625
626
    /**
627
     * Get the user part from a user
628
     *
629
     * When an account suffix is set, we strip the domain part from the user
630
     *
631
     * @param string $user
632
     * @return string
633
     */
634
    public function getUserName($user)
635
    {
636
        if ($this->conf['account_suffix'] !== '') {
637
            list($user) = explode('@', $user, 2);
638
        }
639
        return $user;
640
    }
641
642
    /**
643
     * Fetch the configuration for the given AD domain
644
     *
645
     * @param string $domain current AD domain
646
     * @return array
647
     */
648
    protected function _loadServerConfig($domain) {
649
        // prepare adLDAP standard configuration
650
        $opts = $this->conf;
651
652
        $opts['domain'] = $domain;
653
654
        // add possible domain specific configuration
655
        if($domain && is_array($this->conf[$domain])) foreach($this->conf[$domain] as $key => $val) {
656
            $opts[$key] = $val;
657
        }
658
659
        // handle multiple AD servers
660
        $opts['domain_controllers'] = explode(',', $opts['domain_controllers']);
661
        $opts['domain_controllers'] = array_map('trim', $opts['domain_controllers']);
662
        $opts['domain_controllers'] = array_filter($opts['domain_controllers']);
663
664
        // compatibility with old option name
665
        if(empty($opts['admin_username']) && !empty($opts['ad_username'])) $opts['admin_username'] = $opts['ad_username'];
666
        if(empty($opts['admin_password']) && !empty($opts['ad_password'])) $opts['admin_password'] = $opts['ad_password'];
667
        $opts['admin_password'] = conf_decodeString($opts['admin_password']); // deobfuscate
668
669
        // we can change the password if SSL is set
670
        if($opts['use_ssl'] || $opts['use_tls']) {
671
            $this->cando['modPass'] = true;
672
        } else {
673
            $this->cando['modPass'] = false;
674
        }
675
676
        // adLDAP expects empty user/pass as NULL, we're less strict FS#2781
677
        if(empty($opts['admin_username'])) $opts['admin_username'] = null;
678
        if(empty($opts['admin_password'])) $opts['admin_password'] = null;
679
680
        // user listing needs admin priviledges
681
        if(!empty($opts['admin_username']) && !empty($opts['admin_password'])) {
682
            $this->cando['getUsers'] = true;
683
        } else {
684
            $this->cando['getUsers'] = false;
685
        }
686
687
        return $opts;
688
    }
689
690
    /**
691
     * Returns a list of configured domains
692
     *
693
     * The default domain has an empty string as key
694
     *
695
     * @return array associative array(key => domain)
696
     */
697
    public function _getConfiguredDomains() {
698
        $domains = array();
699
        if(empty($this->conf['account_suffix'])) return $domains; // not configured yet
700
701
        // add default domain, using the name from account suffix
702
        $domains[''] = ltrim($this->conf['account_suffix'], '@');
703
704
        // find additional domains
705
        foreach($this->conf as $key => $val) {
706
            if(is_array($val) && isset($val['account_suffix'])) {
707
                $domains[$key] = ltrim($val['account_suffix'], '@');
708
            }
709
        }
710
        ksort($domains);
711
712
        return $domains;
713
    }
714
715
    /**
716
     * Check provided user and userinfo for matching patterns
717
     *
718
     * The patterns are set up with $this->_constructPattern()
719
     *
720
     * @author Chris Smith <[email protected]>
721
     *
722
     * @param string $user
723
     * @param array  $info
724
     * @return bool
725
     */
726
    protected function _filter($user, $info) {
727
        foreach($this->_pattern as $item => $pattern) {
728
            if($item == 'user') {
729
                if(!preg_match($pattern, $user)) return false;
730
            } else if($item == 'grps') {
731
                if(!count(preg_grep($pattern, $info['grps']))) return false;
732
            } else {
733
                if(!preg_match($pattern, $info[$item])) return false;
734
            }
735
        }
736
        return true;
737
    }
738
739
    /**
740
     * Create a pattern for $this->_filter()
741
     *
742
     * @author Chris Smith <[email protected]>
743
     *
744
     * @param array $filter
745
     */
746
    protected function _constructPattern($filter) {
747
        $this->_pattern = array();
748
        foreach($filter as $item => $pattern) {
749
            $this->_pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters
750
        }
751
    }
752
}
753