Completed
Push — sidebaracl ( 7a112d...7c3e4a )
by Andreas
04:38
created

auth_plugin_authad::_loadServerConfig()   D

Complexity

Conditions 14
Paths 128

Size

Total Lines 40
Code Lines 21

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 40
rs 4.7877
cc 14
eloc 21
nc 128
nop 1

How to fix   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
// 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 $_actualstart = 0;
72
73
    protected $_grpsusers = array();
74
75
    /**
76
     * Constructor
77
     */
78
    public function __construct() {
79
        global $INPUT;
80
        parent::__construct();
81
82
        // we load the config early to modify it a bit here
83
        $this->loadConfig();
0 ignored issues
show
Deprecated Code introduced by
The method DokuWiki_Auth_Plugin::loadConfig() has been deprecated with message: 2012-11-09

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
84
85
        // additional information fields
86
        if(isset($this->conf['additional'])) {
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
87
            $this->conf['additional'] = str_replace(' ', '', $this->conf['additional']);
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
88
            $this->conf['additional'] = explode(',', $this->conf['additional']);
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
89
        } else $this->conf['additional'] = array();
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
90
91
        // ldap extension is needed
92
        if(!function_exists('ldap_connect')) {
93
            if($this->conf['debug'])
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
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
102
            // make sure the right encoding is used
103
            if($this->getConf('sso_charset')) {
104
                $_SERVER['REMOTE_USER'] = iconv($this->getConf('sso_charset'), 'UTF-8', $_SERVER['REMOTE_USER']);
105
            } elseif(!utf8_check($_SERVER['REMOTE_USER'])) {
106
                $_SERVER['REMOTE_USER'] = utf8_encode($_SERVER['REMOTE_USER']);
107
            }
108
109
            // trust the incoming user
110
            if($this->conf['sso']) {
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
111
                $_SERVER['REMOTE_USER'] = $this->cleanUser($_SERVER['REMOTE_USER']);
112
113
                // we need to simulate a login
114
                if(empty($_COOKIE[DOKU_COOKIE])) {
115
                    $INPUT->set('u', $_SERVER['REMOTE_USER']);
116
                    $INPUT->set('p', 'sso_only');
117
                }
118
            }
119
        }
120
121
        // other can do's are changed in $this->_loadServerConfig() base on domain setup
122
        $this->cando['modName'] = true;
123
        $this->cando['modMail'] = true;
124
        $this->cando['getUserCount'] = true;
125
    }
126
127
    /**
128
     * Load domain config on capability check
129
     *
130
     * @param string $cap
131
     * @return bool
132
     */
133
    public function canDo($cap) {
134
        //capabilities depend on config, which may change depending on domain
135
        $domain = $this->_userDomain($_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
        if($_SERVER['REMOTE_USER'] &&
154
            $_SERVER['REMOTE_USER'] == $user &&
155
            $this->conf['sso']
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
156
        ) return true;
157
158
        $adldap = $this->_adldap($this->_userDomain($user));
159
        if(!$adldap) return false;
160
161
        return $adldap->authenticate($this->_userName($user), $pass);
162
    }
163
164
    /**
165
     * Return user info [required auth function]
166
     *
167
     * Returns info about the given user needs to contain
168
     * at least these fields:
169
     *
170
     * name    string  full name of the user
171
     * mail    string  email address of the user
172
     * grps    array   list of groups the user is in
173
     *
174
     * This AD specific function returns the following
175
     * addional fields:
176
     *
177
     * dn         string    distinguished name (DN)
178
     * uid        string    samaccountname
179
     * lastpwd    int       timestamp of the date when the password was set
180
     * expires    true      if the password expires
181
     * expiresin  int       seconds until the password expires
182
     * any fields specified in the 'additional' config option
183
     *
184
     * @author  James Van Lommel <[email protected]>
185
     * @param string $user
186
     * @param bool $requireGroups (optional) - ignored, groups are always supplied by this plugin
187
     * @return array
188
     */
189
    public function getUserData($user, $requireGroups=true) {
190
        global $conf;
191
        global $lang;
192
        global $ID;
193
        $adldap = $this->_adldap($this->_userDomain($user));
194
        if(!$adldap) return false;
195
196
        if($user == '') return array();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array(); (array) is incompatible with the return type of the parent method DokuWiki_Auth_Plugin::getUserData of type boolean.

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...
197
198
        $fields = array('mail', 'displayname', 'samaccountname', 'lastpwd', 'pwdlastset', 'useraccountcontrol');
199
200
        // add additional fields to read
201
        $fields = array_merge($fields, $this->conf['additional']);
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
202
        $fields = array_unique($fields);
203
        $fields = array_filter($fields);
204
205
        //get info for given user
206
        $result = $adldap->user()->info($this->_userName($user), $fields);
207
        if($result == false){
208
            return array();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array(); (array) is incompatible with the return type of the parent method DokuWiki_Auth_Plugin::getUserData of type boolean.

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...
209
        }
210
211
        //general user info
212
        $info = array();
213
        $info['name'] = $result[0]['displayname'][0];
214
        $info['mail'] = $result[0]['mail'][0];
215
        $info['uid']  = $result[0]['samaccountname'][0];
216
        $info['dn']   = $result[0]['dn'];
217
        //last password set (Windows counts from January 1st 1601)
218
        $info['lastpwd'] = $result[0]['pwdlastset'][0] / 10000000 - 11644473600;
219
        //will it expire?
220
        $info['expires'] = !($result[0]['useraccountcontrol'][0] & 0x10000); //ADS_UF_DONT_EXPIRE_PASSWD
221
222
        // additional information
223
        foreach($this->conf['additional'] as $field) {
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
224
            if(isset($result[0][strtolower($field)])) {
225
                $info[$field] = $result[0][strtolower($field)][0];
226
            }
227
        }
228
229
        // handle ActiveDirectory memberOf
230
        $info['grps'] = $adldap->user()->groups($this->_userName($user),(bool) $this->opts['recursive_groups']);
231
232
        if(is_array($info['grps'])) {
233
            foreach($info['grps'] as $ndx => $group) {
234
                $info['grps'][$ndx] = $this->cleanGroup($group);
235
            }
236
        }
237
238
        // always add the default group to the list of groups
239
        if(!is_array($info['grps']) || !in_array($conf['defaultgroup'], $info['grps'])) {
240
            $info['grps'][] = $conf['defaultgroup'];
241
        }
242
243
        // add the user's domain to the groups
244
        $domain = $this->_userDomain($user);
245
        if($domain && !in_array("domain-$domain", (array) $info['grps'])) {
246
            $info['grps'][] = $this->cleanGroup("domain-$domain");
247
        }
248
249
        // check expiry time
250
        if($info['expires'] && $this->conf['expirywarn']){
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
251
            $expiry = $adldap->user()->passwordExpiry($user);
252
            if(is_array($expiry)){
253
                $info['expiresat'] = $expiry['expiryts'];
254
                $info['expiresin'] = round(($info['expiresat'] - time())/(24*60*60));
255
256
                // if this is the current user, warn him (once per request only)
257
                if(($_SERVER['REMOTE_USER'] == $user) &&
258
                    ($info['expiresin'] <= $this->conf['expirywarn']) &&
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
259
                    !$this->msgshown
260
                ) {
261
                    $msg = sprintf($this->getLang('authpwdexpire'), $info['expiresin']);
262
                    if($this->canDo('modPass')) {
263
                        $url = wl($ID, array('do'=> 'profile'));
264
                        $msg .= ' <a href="'.$url.'">'.$lang['btn_profile'].'</a>';
265
                    }
266
                    msg($msg);
267
                    $this->msgshown = true;
268
                }
269
            }
270
        }
271
272
        return $info;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $info; (array) is incompatible with the return type of the parent method DokuWiki_Auth_Plugin::getUserData of type boolean.

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...
273
    }
274
275
    /**
276
     * Make AD group names usable by DokuWiki.
277
     *
278
     * Removes backslashes ('\'), pound signs ('#'), and converts spaces to underscores.
279
     *
280
     * @author  James Van Lommel ([email protected])
281
     * @param string $group
282
     * @return string
283
     */
284
    public function cleanGroup($group) {
285
        $group = str_replace('\\', '', $group);
286
        $group = str_replace('#', '', $group);
287
        $group = preg_replace('[\s]', '_', $group);
288
        $group = utf8_strtolower(trim($group));
289
        return $group;
290
    }
291
292
    /**
293
     * Sanitize user names
294
     *
295
     * Normalizes domain parts, does not modify the user name itself (unlike cleanGroup)
296
     *
297
     * @author Andreas Gohr <[email protected]>
298
     * @param string $user
299
     * @return string
300
     */
301
    public function cleanUser($user) {
302
        $domain = '';
303
304
        // get NTLM or Kerberos domain part
305
        list($dom, $user) = explode('\\', $user, 2);
306
        if(!$user) $user = $dom;
307
        if($dom) $domain = $dom;
308
        list($user, $dom) = explode('@', $user, 2);
309
        if($dom) $domain = $dom;
310
311
        // clean up both
312
        $domain = utf8_strtolower(trim($domain));
313
        $user   = utf8_strtolower(trim($user));
314
315
        // is this a known, valid domain? if not discard
316
        if(!is_array($this->conf[$domain])) {
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
317
            $domain = '';
318
        }
319
320
        // reattach domain
321
        if($domain) $user = "$user@$domain";
322
        return $user;
323
    }
324
325
    /**
326
     * Most values in LDAP are case-insensitive
327
     *
328
     * @return bool
329
     */
330
    public function isCaseSensitive() {
331
        return false;
332
    }
333
334
    /**
335
     * Create a Search-String useable by adLDAPUsers::all($includeDescription = false, $search = "*", $sorted = true)
336
     *
337
     * @param array $filter
338
     * @return string
339
     */
340
    protected function _constructSearchString($filter){
341
        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...
342
            return '*';
343
        }
344
        $adldapUtils = new adLDAPUtils($this->_adldap(null));
345
        $result = '*';
346
        if (isset($filter['name'])) {
347
            $result .= ')(displayname=*' . $adldapUtils->ldapSlashes($filter['name']) . '*';
348
            unset($filter['name']);
349
        }
350
351
        if (isset($filter['user'])) {
352
            $result .= ')(samAccountName=*' . $adldapUtils->ldapSlashes($filter['user']) . '*';
353
            unset($filter['user']);
354
        }
355
356
        if (isset($filter['mail'])) {
357
            $result .= ')(mail=*' . $adldapUtils->ldapSlashes($filter['mail']) . '*';
358
            unset($filter['mail']);
359
        }
360
        return $result;
361
    }
362
363
    /**
364
     * Return a count of the number of user which meet $filter criteria
365
     *
366
     * @param array $filter  $filter array of field/pattern pairs, empty array for no filter
367
     * @return int number of users
368
     */
369
    public function getUserCount($filter = array()) {
370
        $adldap = $this->_adldap(null);
371
        if(!$adldap) {
372
            dbglog("authad/auth.php getUserCount(): _adldap not set.");
373
            return -1;
374
        }
375
        if ($filter == array()) {
376
            $result = $adldap->user()->all();
377
        } else {
378
            $searchString = $this->_constructSearchString($filter);
379
            $result = $adldap->user()->all(false, $searchString);
380
            if (isset($filter['grps'])) {
381
                $this->users = array_fill_keys($result, false);
382
                $usermanager = plugin_load("admin", "usermanager", false);
383
                $usermanager->setLastdisabled(true);
0 ignored issues
show
Documentation Bug introduced by
The method setLastdisabled does not exist on object<DokuWiki_Plugin>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
384
                if (!isset($this->_grpsusers[$this->_filterToString($filter)])){
385
                    $this->_fillGroupUserArray($filter,$usermanager->getStart() + 3*$usermanager->getPagesize());
0 ignored issues
show
Documentation Bug introduced by
The method getStart does not exist on object<DokuWiki_Plugin>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
Documentation Bug introduced by
The method getPagesize does not exist on object<DokuWiki_Plugin>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
386
                } elseif (count($this->_grpsusers[$this->_filterToString($filter)]) < $usermanager->getStart() + 3*$usermanager->getPagesize()) {
0 ignored issues
show
Documentation Bug introduced by
The method getStart does not exist on object<DokuWiki_Plugin>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
Documentation Bug introduced by
The method getPagesize does not exist on object<DokuWiki_Plugin>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
387
                    $this->_fillGroupUserArray($filter,$usermanager->getStart() + 3*$usermanager->getPagesize() - count($this->_grpsusers[$this->_filterToString($filter)]));
0 ignored issues
show
Documentation Bug introduced by
The method getStart does not exist on object<DokuWiki_Plugin>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
Documentation Bug introduced by
The method getPagesize does not exist on object<DokuWiki_Plugin>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
388
                }
389
                $result = $this->_grpsusers[$this->_filterToString($filter)];
390
            } else {
391
                $usermanager = plugin_load("admin", "usermanager", false);
392
                $usermanager->setLastdisabled(false);
0 ignored issues
show
Documentation Bug introduced by
The method setLastdisabled does not exist on object<DokuWiki_Plugin>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
393
            }
394
395
        }
396
397
        if (!$result) {
398
            return 0;
399
        }
400
        return count($result);
401
    }
402
403
    /**
404
     *
405
     * create a unique string for each filter used with a group
406
     *
407
     * @param array $filter
408
     * @return string
409
     */
410
    protected function _filterToString ($filter) {
411
        $result = '';
412
        if (isset($filter['user'])) {
413
            $result .= 'user-' . $filter['user'];
414
        }
415
        if (isset($filter['name'])) {
416
            $result .= 'name-' . $filter['name'];
417
        }
418
        if (isset($filter['mail'])) {
419
            $result .= 'mail-' . $filter['mail'];
420
        }
421
        if (isset($filter['grps'])) {
422
            $result .= 'grps-' . $filter['grps'];
423
        }
424
        return $result;
425
    }
426
427
    /**
428
     * Create an array of $numberOfAdds users passing a certain $filter, including belonging
429
     * to a certain group and save them to a object-wide array. If the array
430
     * already exists try to add $numberOfAdds further users to it.
431
     *
432
     * @param array $filter
433
     * @param int $numberOfAdds additional number of users requested
434
     * @return int number of Users actually add to Array
435
     */
436
    protected function _fillGroupUserArray($filter, $numberOfAdds){
437
        $this->_grpsusers[$this->_filterToString($filter)];
438
        $i = 0;
439
        $count = 0;
440
        $this->_constructPattern($filter);
441
        foreach ($this->users as $user => &$info) {
442
            if($i++ < $this->_actualstart) {
443
                continue;
444
            }
445
            if($info === false) {
446
                $info = $this->getUserData($user);
447
            }
448
            if($this->_filter($user, $info)) {
449
                $this->_grpsusers[$this->_filterToString($filter)][$user] = $info;
450
                if(($numberOfAdds > 0) && (++$count >= $numberOfAdds)) break;
451
            }
452
        }
453
        $this->_actualstart = $i;
454
        return $count;
455
    }
456
457
    /**
458
     * Bulk retrieval of user data
459
     *
460
     * @author  Dominik Eckelmann <[email protected]>
461
     *
462
     * @param   int $start index of first user to be returned
463
     * @param   int $limit max number of users to be returned
464
     * @param   array $filter array of field/pattern pairs, null for no filter
465
     * @return array userinfo (refer getUserData for internal userinfo details)
466
     */
467
    public function retrieveUsers($start = 0, $limit = 0, $filter = array()) {
468
        $adldap = $this->_adldap(null);
469
        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...
470
471
        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...
472
            //get info for given user
473
            $result = $adldap->user()->all(false, $this->_constructSearchString($filter));
474
            if (!$result) return array();
475
            $this->users = array_fill_keys($result, false);
476
        }
477
478
        $i     = 0;
479
        $count = 0;
480
        $result = array();
481
482
        if (!isset($filter['grps'])) {
483
            $usermanager = plugin_load("admin", "usermanager", false);
484
            $usermanager->setLastdisabled(false);
0 ignored issues
show
Documentation Bug introduced by
The method setLastdisabled does not exist on object<DokuWiki_Plugin>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
485
            $this->_constructPattern($filter);
486
            foreach($this->users as $user => &$info) {
487
                if($i++ < $start) {
488
                    continue;
489
                }
490
                if($info === false) {
491
                    $info = $this->getUserData($user);
492
                }
493
                $result[$user] = $info;
494
                if(($limit > 0) && (++$count >= $limit)) break;
495
            }
496
        } else {
497
            $usermanager = plugin_load("admin", "usermanager", false);
498
            $usermanager->setLastdisabled(true);
0 ignored issues
show
Documentation Bug introduced by
The method setLastdisabled does not exist on object<DokuWiki_Plugin>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
499
            if (!isset($this->_grpsusers[$this->_filterToString($filter)]) || count($this->_grpsusers[$this->_filterToString($filter)]) < ($start+$limit)) {
500
                $this->_fillGroupUserArray($filter,$start+$limit - count($this->_grpsusers[$this->_filterToString($filter)]) +1);
501
            }
502
            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...
503
            foreach($this->_grpsusers[$this->_filterToString($filter)] as $user => &$info) {
504
                if($i++ < $start) {
505
                    continue;
506
                }
507
                $result[$user] = $info;
508
                if(($limit > 0) && (++$count >= $limit)) break;
509
            }
510
511
        }
512
        return $result;
513
    }
514
515
    /**
516
     * Modify user data
517
     *
518
     * @param   string $user      nick of the user to be changed
519
     * @param   array  $changes   array of field/value pairs to be changed
520
     * @return  bool
521
     */
522
    public function modifyUser($user, $changes) {
523
        $return = true;
524
        $adldap = $this->_adldap($this->_userDomain($user));
525
        if(!$adldap) {
526
            msg($this->getLang('connectfail'), -1);
527
            return false;
528
        }
529
530
        // password changing
531
        if(isset($changes['pass'])) {
532
            try {
533
                $return = $adldap->user()->password($this->_userName($user),$changes['pass']);
534
            } 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...
535
                if ($this->conf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
536
                $return = false;
537
            }
538
            if(!$return) msg($this->getLang('passchangefail'), -1);
539
        }
540
541
        // changing user data
542
        $adchanges = array();
543
        if(isset($changes['name'])) {
544
            // get first and last name
545
            $parts                     = explode(' ', $changes['name']);
546
            $adchanges['surname']      = array_pop($parts);
547
            $adchanges['firstname']    = join(' ', $parts);
548
            $adchanges['display_name'] = $changes['name'];
549
        }
550
        if(isset($changes['mail'])) {
551
            $adchanges['email'] = $changes['mail'];
552
        }
553
        if(count($adchanges)) {
554
            try {
555
                $return = $return & $adldap->user()->modify($this->_userName($user),$adchanges);
556
            } 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...
557
                if ($this->conf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
558
                $return = false;
559
            }
560
            if(!$return) msg($this->getLang('userchangefail'), -1);
561
        }
562
563
        return $return;
564
    }
565
566
    /**
567
     * Initialize the AdLDAP library and connect to the server
568
     *
569
     * When you pass null as domain, it will reuse any existing domain.
570
     * Eg. the one of the logged in user. It falls back to the default
571
     * domain if no current one is available.
572
     *
573
     * @param string|null $domain The AD domain to use
574
     * @return adLDAP|bool true if a connection was established
575
     */
576
    protected function _adldap($domain) {
577
        if(is_null($domain) && is_array($this->opts)) {
578
            $domain = $this->opts['domain'];
579
        }
580
581
        $this->opts = $this->_loadServerConfig((string) $domain);
582
        if(isset($this->adldap[$domain])) return $this->adldap[$domain];
583
584
        // connect
585
        try {
586
            $this->adldap[$domain] = new adLDAP($this->opts);
587
            return $this->adldap[$domain];
588
        } 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...
589
            if($this->conf['debug']) {
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
590
                msg('AD Auth: '.$e->getMessage(), -1);
591
            }
592
            $this->success         = false;
593
            $this->adldap[$domain] = null;
594
        }
595
        return false;
596
    }
597
598
    /**
599
     * Get the domain part from a user
600
     *
601
     * @param string $user
602
     * @return string
603
     */
604
    public function _userDomain($user) {
605
        list(, $domain) = explode('@', $user, 2);
606
        return $domain;
607
    }
608
609
    /**
610
     * Get the user part from a user
611
     *
612
     * @param string $user
613
     * @return string
614
     */
615
    public function _userName($user) {
616
        list($name) = explode('@', $user, 2);
617
        return $name;
618
    }
619
620
    /**
621
     * Fetch the configuration for the given AD domain
622
     *
623
     * @param string $domain current AD domain
624
     * @return array
625
     */
626
    protected function _loadServerConfig($domain) {
627
        // prepare adLDAP standard configuration
628
        $opts = $this->conf;
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
629
630
        $opts['domain'] = $domain;
631
632
        // add possible domain specific configuration
633
        if($domain && is_array($this->conf[$domain])) foreach($this->conf[$domain] as $key => $val) {
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
634
            $opts[$key] = $val;
635
        }
636
637
        // handle multiple AD servers
638
        $opts['domain_controllers'] = explode(',', $opts['domain_controllers']);
639
        $opts['domain_controllers'] = array_map('trim', $opts['domain_controllers']);
640
        $opts['domain_controllers'] = array_filter($opts['domain_controllers']);
641
642
        // compatibility with old option name
643
        if(empty($opts['admin_username']) && !empty($opts['ad_username'])) $opts['admin_username'] = $opts['ad_username'];
644
        if(empty($opts['admin_password']) && !empty($opts['ad_password'])) $opts['admin_password'] = $opts['ad_password'];
645
646
        // we can change the password if SSL is set
647
        if($opts['use_ssl'] || $opts['use_tls']) {
648
            $this->cando['modPass'] = true;
649
        } else {
650
            $this->cando['modPass'] = false;
651
        }
652
653
        // adLDAP expects empty user/pass as NULL, we're less strict FS#2781
654
        if(empty($opts['admin_username'])) $opts['admin_username'] = null;
655
        if(empty($opts['admin_password'])) $opts['admin_password'] = null;
656
657
        // user listing needs admin priviledges
658
        if(!empty($opts['admin_username']) && !empty($opts['admin_password'])) {
659
            $this->cando['getUsers'] = true;
660
        } else {
661
            $this->cando['getUsers'] = false;
662
        }
663
664
        return $opts;
665
    }
666
667
    /**
668
     * Returns a list of configured domains
669
     *
670
     * The default domain has an empty string as key
671
     *
672
     * @return array associative array(key => domain)
673
     */
674
    public function _getConfiguredDomains() {
675
        $domains = array();
676
        if(empty($this->conf['account_suffix'])) return $domains; // not configured yet
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
677
678
        // add default domain, using the name from account suffix
679
        $domains[''] = ltrim($this->conf['account_suffix'], '@');
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
680
681
        // find additional domains
682
        foreach($this->conf as $key => $val) {
0 ignored issues
show
Bug introduced by
The property conf cannot be accessed from this context as it is declared private in class DokuWiki_Plugin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
683
            if(is_array($val) && isset($val['account_suffix'])) {
684
                $domains[$key] = ltrim($val['account_suffix'], '@');
685
            }
686
        }
687
        ksort($domains);
688
689
        return $domains;
690
    }
691
692
    /**
693
     * Check provided user and userinfo for matching patterns
694
     *
695
     * The patterns are set up with $this->_constructPattern()
696
     *
697
     * @author Chris Smith <[email protected]>
698
     *
699
     * @param string $user
700
     * @param array  $info
701
     * @return bool
702
     */
703
    protected function _filter($user, $info) {
704
        foreach($this->_pattern as $item => $pattern) {
705
            if($item == 'user') {
706
                if(!preg_match($pattern, $user)) return false;
707
            } else if($item == 'grps') {
708
                if(!count(preg_grep($pattern, $info['grps']))) return false;
709
            } else {
710
                if(!preg_match($pattern, $info[$item])) return false;
711
            }
712
        }
713
        return true;
714
    }
715
716
    /**
717
     * Create a pattern for $this->_filter()
718
     *
719
     * @author Chris Smith <[email protected]>
720
     *
721
     * @param array $filter
722
     */
723
    protected function _constructPattern($filter) {
724
        $this->_pattern = array();
725
        foreach($filter as $item => $pattern) {
726
            $this->_pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters
727
        }
728
    }
729
}
730