Failed Conditions
Push — stable ( 017e16...b83837 )
by
unknown
07:54 queued 02:55
created

auth_plugin_authad::canDo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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