Failed Conditions
Push — interwiki-remove-golucky ( 52fcdb...768be5 )
by Henry
15:30 queued 12:50
created

auth_plugin_authad::getUserData()   F

Complexity

Conditions 20
Paths 387

Size

Total Lines 90

Duplication

Lines 0
Ratio 0 %

Importance

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