Failed Conditions
Push — psr2-config ( c6639e )
by Andreas
06:39 queued 03:33
created

auth_plugin_authplain   C

Complexity

Total Complexity 66

Size/Duplication

Total Lines 460
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 0
loc 460
rs 5.7474
c 0
b 0
f 0
wmc 66
lcom 1
cbo 1

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 23 3
A checkPass() 0 7 2
A getUserData() 0 5 3
A createUserLine() 0 9 1
B createUser() 0 27 4
C modifyUser() 0 48 9
D deleteUsers() 0 32 9
A getUserCount() 0 16 4
C retrieveUsers() 0 25 7
A cleanUser() 0 5 1
A cleanGroup() 0 5 1
A loadUserData() 0 15 3
B readUserFile() 0 24 4
B splitUserData() 0 27 6
B filter() 0 13 7
A constructPattern() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like auth_plugin_authplain often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use auth_plugin_authplain, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Plaintext authentication backend
5
 *
6
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
7
 * @author     Andreas Gohr <[email protected]>
8
 * @author     Chris Smith <[email protected]>
9
 * @author     Jan Schumann <[email protected]>
10
 */
11
class auth_plugin_authplain extends DokuWiki_Auth_Plugin
12
{
13
    /** @var array user cache */
14
    protected $users = null;
15
16
    /** @var array filter pattern */
17
    protected $pattern = array();
18
19
    /** @var bool safe version of preg_split */
20
    protected $pregsplit_safe = false;
21
22
    /**
23
     * Constructor
24
     *
25
     * Carry out sanity checks to ensure the object is
26
     * able to operate. Set capabilities.
27
     *
28
     * @author  Christopher Smith <[email protected]>
29
     */
30
    public function __construct()
31
    {
32
        parent::__construct();
33
        global $config_cascade;
34
35
        if (!@is_readable($config_cascade['plainauth.users']['default'])) {
36
            $this->success = false;
37
        } else {
38
            if (@is_writable($config_cascade['plainauth.users']['default'])) {
39
                $this->cando['addUser']   = true;
40
                $this->cando['delUser']   = true;
41
                $this->cando['modLogin']  = true;
42
                $this->cando['modPass']   = true;
43
                $this->cando['modName']   = true;
44
                $this->cando['modMail']   = true;
45
                $this->cando['modGroups'] = true;
46
            }
47
            $this->cando['getUsers']     = true;
48
            $this->cando['getUserCount'] = true;
49
        }
50
51
        $this->pregsplit_safe = version_compare(PCRE_VERSION, '6.7', '>=');
52
    }
53
54
    /**
55
     * Check user+password
56
     *
57
     * Checks if the given user exists and the given
58
     * plaintext password is correct
59
     *
60
     * @author  Andreas Gohr <[email protected]>
61
     * @param string $user
62
     * @param string $pass
63
     * @return  bool
64
     */
65
    public function checkPass($user, $pass)
66
    {
67
        $userinfo = $this->getUserData($user);
68
        if ($userinfo === false) return false;
69
70
        return auth_verifyPassword($pass, $this->users[$user]['pass']);
71
    }
72
73
    /**
74
     * Return user info
75
     *
76
     * Returns info about the given user needs to contain
77
     * at least these fields:
78
     *
79
     * name string  full name of the user
80
     * mail string  email addres of the user
81
     * grps array   list of groups the user is in
82
     *
83
     * @author  Andreas Gohr <[email protected]>
84
     * @param string $user
85
     * @param bool $requireGroups  (optional) ignored by this plugin, grps info always supplied
86
     * @return array|false
87
     */
88
    public function getUserData($user, $requireGroups = true)
89
    {
90
        if ($this->users === null) $this->loadUserData();
91
        return isset($this->users[$user]) ? $this->users[$user] : false;
92
    }
93
94
    /**
95
     * Creates a string suitable for saving as a line
96
     * in the file database
97
     * (delimiters escaped, etc.)
98
     *
99
     * @param string $user
100
     * @param string $pass
101
     * @param string $name
102
     * @param string $mail
103
     * @param array  $grps list of groups the user is in
104
     * @return string
105
     */
106
    protected function createUserLine($user, $pass, $name, $mail, $grps)
107
    {
108
        $groups   = join(',', $grps);
109
        $userline = array($user, $pass, $name, $mail, $groups);
110
        $userline = str_replace('\\', '\\\\', $userline); // escape \ as \\
111
        $userline = str_replace(':', '\\:', $userline); // escape : as \:
112
        $userline = join(':', $userline)."\n";
113
        return $userline;
114
    }
115
116
    /**
117
     * Create a new User
118
     *
119
     * Returns false if the user already exists, null when an error
120
     * occurred and true if everything went well.
121
     *
122
     * The new user will be added to the default group by this
123
     * function if grps are not specified (default behaviour).
124
     *
125
     * @author  Andreas Gohr <[email protected]>
126
     * @author  Chris Smith <[email protected]>
127
     *
128
     * @param string $user
129
     * @param string $pwd
130
     * @param string $name
131
     * @param string $mail
132
     * @param array  $grps
133
     * @return bool|null|string
134
     */
135
    public function createUser($user, $pwd, $name, $mail, $grps = null)
136
    {
137
        global $conf;
138
        global $config_cascade;
139
140
        // user mustn't already exist
141
        if ($this->getUserData($user) !== false) {
142
            msg($this->getLang('userexists'), -1);
143
            return false;
144
        }
145
146
        $pass = auth_cryptPassword($pwd);
147
148
        // set default group if no groups specified
149
        if (!is_array($grps)) $grps = array($conf['defaultgroup']);
150
151
        // prepare user line
152
        $userline = $this->createUserLine($user, $pass, $name, $mail, $grps);
153
154
        if (!io_saveFile($config_cascade['plainauth.users']['default'], $userline, true)) {
155
            msg($this->getLang('writefail'), -1);
156
            return null;
157
        }
158
159
        $this->users[$user] = compact('pass', 'name', 'mail', 'grps');
160
        return $pwd;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $pwd; (string) is incompatible with the return type of the parent method DokuWiki_Auth_Plugin::createUser of type boolean|null.

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...
161
    }
162
163
    /**
164
     * Modify user data
165
     *
166
     * @author  Chris Smith <[email protected]>
167
     * @param   string $user      nick of the user to be changed
168
     * @param   array  $changes   array of field/value pairs to be changed (password will be clear text)
169
     * @return  bool
170
     */
171
    public function modifyUser($user, $changes)
172
    {
173
        global $ACT;
174
        global $config_cascade;
175
176
        // sanity checks, user must already exist and there must be something to change
177
        if (($userinfo = $this->getUserData($user)) === false) {
178
            msg($this->getLang('usernotexists'), -1);
179
            return false;
180
        }
181
182
        // don't modify protected users
183
        if (!empty($userinfo['protected'])) {
184
            msg(sprintf($this->getLang('protected'), hsc($user)), -1);
185
            return false;
186
        }
187
188
        if (!is_array($changes) || !count($changes)) return true;
189
190
        // update userinfo with new data, remembering to encrypt any password
191
        $newuser = $user;
192
        foreach ($changes as $field => $value) {
193
            if ($field == 'user') {
194
                $newuser = $value;
195
                continue;
196
            }
197
            if ($field == 'pass') $value = auth_cryptPassword($value);
198
            $userinfo[$field] = $value;
199
        }
200
201
        $userline = $this->createUserLine(
202
            $newuser,
203
            $userinfo['pass'],
204
            $userinfo['name'],
205
            $userinfo['mail'],
206
            $userinfo['grps']
207
        );
208
209
        if (!io_replaceInFile($config_cascade['plainauth.users']['default'], '/^'.$user.':/', $userline, true)) {
210
            msg('There was an error modifying your user data. You may need to register again.', -1);
211
            // FIXME, io functions should be fail-safe so existing data isn't lost
212
            $ACT = 'register';
213
            return false;
214
        }
215
216
        $this->users[$newuser] = $userinfo;
217
        return true;
218
    }
219
220
    /**
221
     * Remove one or more users from the list of registered users
222
     *
223
     * @author  Christopher Smith <[email protected]>
224
     * @param   array  $users   array of users to be deleted
225
     * @return  int             the number of users deleted
226
     */
227
    public function deleteUsers($users)
228
    {
229
        global $config_cascade;
230
231
        if (!is_array($users) || empty($users)) return 0;
232
233
        if ($this->users === null) $this->loadUserData();
234
235
        $deleted = array();
236
        foreach ($users as $user) {
237
            // don't delete protected users
238
            if (!empty($this->users[$user]['protected'])) {
239
                msg(sprintf($this->getLang('protected'), hsc($user)), -1);
240
                continue;
241
            }
242
            if (isset($this->users[$user])) $deleted[] = preg_quote($user, '/');
243
        }
244
245
        if (empty($deleted)) return 0;
246
247
        $pattern = '/^('.join('|', $deleted).'):/';
248
        if (!io_deleteFromFile($config_cascade['plainauth.users']['default'], $pattern, true)) {
249
            msg($this->getLang('writefail'), -1);
250
            return 0;
251
        }
252
253
        // reload the user list and count the difference
254
        $count = count($this->users);
255
        $this->loadUserData();
256
        $count -= count($this->users);
257
        return $count;
258
    }
259
260
    /**
261
     * Return a count of the number of user which meet $filter criteria
262
     *
263
     * @author  Chris Smith <[email protected]>
264
     *
265
     * @param array $filter
266
     * @return int
267
     */
268
    public function getUserCount($filter = array())
269
    {
270
271
        if ($this->users === null) $this->loadUserData();
272
273
        if (!count($filter)) return count($this->users);
274
275
        $count = 0;
276
        $this->constructPattern($filter);
277
278
        foreach ($this->users as $user => $info) {
279
            $count += $this->filter($user, $info);
280
        }
281
282
        return $count;
283
    }
284
285
    /**
286
     * Bulk retrieval of user data
287
     *
288
     * @author  Chris Smith <[email protected]>
289
     *
290
     * @param   int   $start index of first user to be returned
291
     * @param   int   $limit max number of users to be returned
292
     * @param   array $filter array of field/pattern pairs
293
     * @return  array userinfo (refer getUserData for internal userinfo details)
294
     */
295
    public function retrieveUsers($start = 0, $limit = 0, $filter = array())
296
    {
297
298
        if ($this->users === null) $this->loadUserData();
299
300
        ksort($this->users);
301
302
        $i     = 0;
303
        $count = 0;
304
        $out   = array();
305
        $this->constructPattern($filter);
306
307
        foreach ($this->users as $user => $info) {
308
            if ($this->filter($user, $info)) {
309
                if ($i >= $start) {
310
                    $out[$user] = $info;
311
                    $count++;
312
                    if (($limit > 0) && ($count >= $limit)) break;
313
                }
314
                $i++;
315
            }
316
        }
317
318
        return $out;
319
    }
320
321
    /**
322
     * Only valid pageid's (no namespaces) for usernames
323
     *
324
     * @param string $user
325
     * @return string
326
     */
327
    public function cleanUser($user)
328
    {
329
        global $conf;
330
        return cleanID(str_replace(':', $conf['sepchar'], $user));
331
    }
332
333
    /**
334
     * Only valid pageid's (no namespaces) for groupnames
335
     *
336
     * @param string $group
337
     * @return string
338
     */
339
    public function cleanGroup($group)
340
    {
341
        global $conf;
342
        return cleanID(str_replace(':', $conf['sepchar'], $group));
343
    }
344
345
    /**
346
     * Load all user data
347
     *
348
     * loads the user file into a datastructure
349
     *
350
     * @author  Andreas Gohr <[email protected]>
351
     */
352
    protected function loadUserData()
353
    {
354
        global $config_cascade;
355
356
        $this->users = $this->readUserFile($config_cascade['plainauth.users']['default']);
357
358
        // support protected users
359
        if (!empty($config_cascade['plainauth.users']['protected'])) {
360
            $protected = $this->readUserFile($config_cascade['plainauth.users']['protected']);
361
            foreach (array_keys($protected) as $key) {
362
                $protected[$key]['protected'] = true;
363
            }
364
            $this->users = array_merge($this->users, $protected);
365
        }
366
    }
367
368
    /**
369
     * Read user data from given file
370
     *
371
     * ignores non existing files
372
     *
373
     * @param string $file the file to load data from
374
     * @return array
375
     */
376
    protected function readUserFile($file)
377
    {
378
        $users = array();
379
        if (!file_exists($file)) return $users;
380
381
        $lines = file($file);
382
        foreach ($lines as $line) {
383
            $line = preg_replace('/#.*$/', '', $line); //ignore comments
384
            $line = trim($line);
385
            if (empty($line)) continue;
386
387
            $row = $this->splitUserData($line);
388
            $row = str_replace('\\:', ':', $row);
389
            $row = str_replace('\\\\', '\\', $row);
390
391
            $groups = array_values(array_filter(explode(",", $row[4])));
392
393
            $users[$row[0]]['pass'] = $row[1];
394
            $users[$row[0]]['name'] = urldecode($row[2]);
395
            $users[$row[0]]['mail'] = $row[3];
396
            $users[$row[0]]['grps'] = $groups;
397
        }
398
        return $users;
399
    }
400
401
    /**
402
     * Get the user line split into it's parts
403
     *
404
     * @param string $line
405
     * @return string[]
406
     */
407
    protected function splitUserData($line)
408
    {
409
        // due to a bug in PCRE 6.6, preg_split will fail with the regex we use here
410
        // refer github issues 877 & 885
411
        if ($this->pregsplit_safe) {
412
            return preg_split('/(?<![^\\\\]\\\\)\:/', $line, 5);       // allow for : escaped as \:
413
        }
414
415
        $row = array();
416
        $piece = '';
417
        $len = strlen($line);
418
        for ($i=0; $i<$len; $i++) {
419
            if ($line[$i]=='\\') {
420
                $piece .= $line[$i];
421
                $i++;
422
                if ($i>=$len) break;
423
            } elseif ($line[$i]==':') {
424
                $row[] = $piece;
425
                $piece = '';
426
                continue;
427
            }
428
            $piece .= $line[$i];
429
        }
430
        $row[] = $piece;
431
432
        return $row;
433
    }
434
435
    /**
436
     * return true if $user + $info match $filter criteria, false otherwise
437
     *
438
     * @author   Chris Smith <[email protected]>
439
     *
440
     * @param string $user User login
441
     * @param array  $info User's userinfo array
442
     * @return bool
443
     */
444
    protected function filter($user, $info)
445
    {
446
        foreach ($this->pattern as $item => $pattern) {
447
            if ($item == 'user') {
448
                if (!preg_match($pattern, $user)) return false;
449
            } elseif ($item == 'grps') {
450
                if (!count(preg_grep($pattern, $info['grps']))) return false;
451
            } else {
452
                if (!preg_match($pattern, $info[$item])) return false;
453
            }
454
        }
455
        return true;
456
    }
457
458
    /**
459
     * construct a filter pattern
460
     *
461
     * @param array $filter
462
     */
463
    protected function constructPattern($filter)
464
    {
465
        $this->pattern = array();
466
        foreach ($filter as $item => $pattern) {
467
            $this->pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters
468
        }
469
    }
470
}
471