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

auth_plugin_authpgsql   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 415
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2
Metric Value
wmc 53
lcom 1
cbo 2
dl 0
loc 415
rs 7.4757

13 Methods

Rating   Name   Duplication   Size   Complexity  
A _queryDB() 0 15 4
B __construct() 0 73 5
A _chkcnf() 0 6 3
A getUserCount() 0 14 3
B retrieveUsers() 0 22 6
D _addUserToGroup() 0 37 9
C _addUser() 0 37 8
B _openDB() 0 22 4
A _closeDB() 0 6 2
A _modifyDB() 0 11 3
A _lockTables() 0 7 2
A _unlockTables() 0 7 2
A _escape() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like auth_plugin_authpgsql 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_authpgsql, and based on these observations, apply Extract Interface, too.

1
<?php
2
// must be run within Dokuwiki
3
if(!defined('DOKU_INC')) die();
4
5
/**
6
 * PostgreSQL authentication backend
7
 *
8
 * This class inherits much functionality from the MySQL class
9
 * and just reimplements the Postgres specific parts.
10
 *
11
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
12
 * @author     Andreas Gohr <[email protected]>
13
 * @author     Chris Smith <[email protected]>
14
 * @author     Matthias Grimm <[email protected]>
15
 * @author     Jan Schumann <[email protected]>
16
 */
17
class auth_plugin_authpgsql extends auth_plugin_authmysql {
18
19
    /**
20
     * Constructor
21
     *
22
     * checks if the pgsql interface is available, otherwise it will
23
     * set the variable $success of the basis class to false
24
     *
25
     * @author Matthias Grimm <[email protected]>
26
     * @author Andreas Gohr <[email protected]>
27
     */
28
    public function __construct() {
29
        // we don't want the stuff the MySQL constructor does, but the grandparent might do something
30
        DokuWiki_Auth_Plugin::__construct();
31
32
        if(!function_exists('pg_connect')) {
33
            $this->_debug("PgSQL err: PHP Postgres extension not found.", -1, __LINE__, __FILE__);
34
            $this->success = false;
35
            return;
36
        }
37
38
        $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...
39
40
        // set capabilities based upon config strings set
41
        if(empty($this->conf['user']) ||
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...
42
            empty($this->conf['password']) || empty($this->conf['database'])
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...
43
        ) {
44
            $this->_debug("PgSQL err: insufficient configuration.", -1, __LINE__, __FILE__);
45
            $this->success = false;
46
            return;
47
        }
48
49
        $this->cando['addUser']   = $this->_chkcnf(
50
            array(
51
                 'getUserInfo',
52
                 'getGroups',
53
                 'addUser',
54
                 'getUserID',
55
                 'getGroupID',
56
                 'addGroup',
57
                 'addUserGroup'
58
            )
59
        );
60
        $this->cando['delUser']   = $this->_chkcnf(
61
            array(
62
                 'getUserID',
63
                 'delUser',
64
                 'delUserRefs'
65
            )
66
        );
67
        $this->cando['modLogin']  = $this->_chkcnf(
68
            array(
69
                 'getUserID',
70
                 'updateUser',
71
                 'UpdateTarget'
72
            )
73
        );
74
        $this->cando['modPass']   = $this->cando['modLogin'];
75
        $this->cando['modName']   = $this->cando['modLogin'];
76
        $this->cando['modMail']   = $this->cando['modLogin'];
77
        $this->cando['modGroups'] = $this->_chkcnf(
78
            array(
79
                 'getUserID',
80
                 'getGroups',
81
                 'getGroupID',
82
                 'addGroup',
83
                 'addUserGroup',
84
                 'delGroup',
85
                 'getGroupID',
86
                 'delUserGroup'
87
            )
88
        );
89
        /* getGroups is not yet supported
90
           $this->cando['getGroups']    = $this->_chkcnf(array('getGroups',
91
           'getGroupID')); */
92
        $this->cando['getUsers']     = $this->_chkcnf(
93
            array(
94
                 'getUsers',
95
                 'getUserInfo',
96
                 'getGroups'
97
            )
98
        );
99
        $this->cando['getUserCount'] = $this->_chkcnf(array('getUsers'));
100
    }
101
102
    /**
103
     * Check if the given config strings are set
104
     *
105
     * @author  Matthias Grimm <[email protected]>
106
     *
107
     * @param   string[] $keys
108
     * @param   bool  $wop
109
     * @return  bool
110
     */
111
    protected function _chkcnf($keys, $wop = false) {
112
        foreach($keys as $key) {
113
            if(empty($this->conf[$key])) return false;
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...
114
        }
115
        return true;
116
    }
117
118
    /**
119
     * Counts users which meet certain $filter criteria.
120
     *
121
     * @author  Matthias Grimm <[email protected]>
122
     *
123
     * @param  array  $filter  filter criteria in item/pattern pairs
124
     * @return int count of found users.
125
     */
126
    public function getUserCount($filter = array()) {
127
        $rc = 0;
128
129
        if($this->_openDB()) {
130
            $sql = $this->_createSQLFilter($this->conf['getUsers'], $filter);
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...
131
132
            // no equivalent of SQL_CALC_FOUND_ROWS in pgsql?
133
            if(($result = $this->_queryDB($sql))) {
134
                $rc = count($result);
135
            }
136
            $this->_closeDB();
137
        }
138
        return $rc;
139
    }
140
141
    /**
142
     * Bulk retrieval of user data
143
     *
144
     * @author  Matthias Grimm <[email protected]>
145
     *
146
     * @param   int   $first     index of first user to be returned
147
     * @param   int   $limit     max number of users to be returned
148
     * @param   array $filter    array of field/pattern pairs
149
     * @return  array userinfo (refer getUserData for internal userinfo details)
150
     */
151
    public function retrieveUsers($first = 0, $limit = 0, $filter = array()) {
152
        $out = array();
153
154
        if($this->_openDB()) {
155
            $this->_lockTables("READ");
156
            $sql = $this->_createSQLFilter($this->conf['getUsers'], $filter);
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...
157
            $sql .= " ".$this->conf['SortOrder'];
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...
158
            if($limit) $sql .= " LIMIT $limit";
159
            if($first) $sql .= " OFFSET $first";
160
            $result = $this->_queryDB($sql);
161
162
            foreach($result as $user) {
0 ignored issues
show
Bug introduced by
The expression $result of type array|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
163
                if(($info = $this->_getUserInfo($user['user']))) {
164
                    $out[$user['user']] = $info;
165
                }
166
            }
167
168
            $this->_unlockTables();
169
            $this->_closeDB();
170
        }
171
        return $out;
172
    }
173
174
    // @inherit function joinGroup($user, $group)
175
    // @inherit function leaveGroup($user, $group) {
176
177
    /**
178
     * Adds a user to a group.
179
     *
180
     * If $force is set to true non existing groups would be created.
181
     *
182
     * The database connection must already be established. Otherwise
183
     * this function does nothing and returns 'false'.
184
     *
185
     * @author Matthias Grimm <[email protected]>
186
     * @author Andreas Gohr   <[email protected]>
187
     *
188
     * @param   string $user    user to add to a group
189
     * @param   string $group   name of the group
190
     * @param   bool   $force   create missing groups
191
     * @return  bool   true on success, false on error
192
     */
193
    protected function _addUserToGroup($user, $group, $force = false) {
194
        $newgroup = 0;
195
196
        if(($this->dbcon) && ($user)) {
197
            $gid = $this->_getGroupID($group);
198
            if(!$gid) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $gid of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
199
                if($force) { // create missing groups
200
                    $sql = str_replace('%{group}', addslashes($group), $this->conf['addGroup']);
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...
201
                    $this->_modifyDB($sql);
202
                    //group should now exists try again to fetch it
203
                    $gid      = $this->_getGroupID($group);
204
                    $newgroup = 1; // group newly created
205
                }
206
            }
207
            if(!$gid) return false; // group didn't exist and can't be created
0 ignored issues
show
Bug Best Practice introduced by
The expression $gid of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
208
209
            $sql = $this->conf['addUserGroup'];
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...
210
            if(strpos($sql, '%{uid}') !== false) {
211
                $uid = $this->_getUserID($user);
212
                $sql = str_replace('%{uid}', addslashes($uid), $sql);
213
            }
214
            $sql = str_replace('%{user}', addslashes($user), $sql);
215
            $sql = str_replace('%{gid}', addslashes($gid), $sql);
216
            $sql = str_replace('%{group}', addslashes($group), $sql);
217
            if($this->_modifyDB($sql) !== false) {
218
                $this->_flushUserInfoCache($user);
219
                return true;
220
            }
221
222
            if($newgroup) { // remove previously created group on error
223
                $sql = str_replace('%{gid}', addslashes($gid), $this->conf['delGroup']);
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
                $sql = str_replace('%{group}', addslashes($group), $sql);
225
                $this->_modifyDB($sql);
226
            }
227
        }
228
        return false;
229
    }
230
231
    // @inherit function _delUserFromGroup($user $group)
232
    // @inherit function _getGroups($user)
233
    // @inherit function _getUserID($user)
234
235
    /**
236
     * Adds a new User to the database.
237
     *
238
     * The database connection must already be established
239
     * for this function to work. Otherwise it will return
240
     * 'false'.
241
     *
242
     * @param  string $user  login of the user
243
     * @param  string $pwd   encrypted password
244
     * @param  string $name  full name of the user
245
     * @param  string $mail  email address
246
     * @param  array  $grps  array of groups the user should become member of
247
     * @return bool
248
     *
249
     * @author  Andreas Gohr <[email protected]>
250
     * @author  Chris Smith <[email protected]>
251
     * @author  Matthias Grimm <[email protected]>
252
     */
253
    protected function _addUser($user, $pwd, $name, $mail, $grps) {
254
        if($this->dbcon && is_array($grps)) {
255
            $sql = str_replace('%{user}', addslashes($user), $this->conf['addUser']);
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...
256
            $sql = str_replace('%{pass}', addslashes($pwd), $sql);
257
            $sql = str_replace('%{name}', addslashes($name), $sql);
258
            $sql = str_replace('%{email}', addslashes($mail), $sql);
259
            if($this->_modifyDB($sql)) {
260
                $uid = $this->_getUserID($user);
261
            } else {
262
                return false;
263
            }
264
265
            $group = '';
266
            $gid = false;
267
268
            if($uid) {
269
                foreach($grps as $group) {
270
                    $gid = $this->_addUserToGroup($user, $group, true);
271
                    if($gid === false) break;
272
                }
273
274
                if($gid !== false){
275
                    $this->_flushUserInfoCache($user);
276
                    return true;
277
                } else {
278
                    /* remove the new user and all group relations if a group can't
279
                     * be assigned. Newly created groups will remain in the database
280
                     * and won't be removed. This might create orphaned groups but
281
                     * is not a big issue so we ignore this problem here.
282
                     */
283
                    $this->_delUser($user);
284
                    $this->_debug("PgSQL err: Adding user '$user' to group '$group' failed.", -1, __LINE__, __FILE__);
285
                }
286
            }
287
        }
288
        return false;
289
    }
290
291
    /**
292
     * Opens a connection to a database and saves the handle for further
293
     * usage in the object. The successful call to this functions is
294
     * essential for most functions in this object.
295
     *
296
     * @author Matthias Grimm <[email protected]>
297
     *
298
     * @return bool
299
     */
300
    protected function _openDB() {
301
        if(!$this->dbcon) {
302
            $dsn = $this->conf['server'] ? 'host='.$this->conf['server'] : '';
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...
303
            $dsn .= ' port='.$this->conf['port'];
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...
304
            $dsn .= ' dbname='.$this->conf['database'];
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...
305
            $dsn .= ' user='.$this->conf['user'];
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...
306
            $dsn .= ' password='.$this->conf['password'];
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...
307
308
            $con = @pg_connect($dsn);
309
            if($con) {
310
                $this->dbcon = $con;
311
                return true; // connection and database successfully opened
312
            } else {
313
                $this->_debug(
314
                        "PgSQL err: Connection to {$this->conf['user']}@{$this->conf['server']} not possible.",
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...
315
                        -1, __LINE__, __FILE__
316
                    );
317
            }
318
            return false; // connection failed
319
        }
320
        return true; // connection already open
321
    }
322
323
    /**
324
     * Closes a database connection.
325
     *
326
     * @author Matthias Grimm <[email protected]>
327
     */
328
    protected function _closeDB() {
329
        if($this->dbcon) {
330
            pg_close($this->dbcon);
331
            $this->dbcon = 0;
0 ignored issues
show
Documentation Bug introduced by
It seems like 0 of type integer is incompatible with the declared type resource of property $dbcon.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
332
        }
333
    }
334
335
    /**
336
     * Sends a SQL query to the database and transforms the result into
337
     * an associative array.
338
     *
339
     * This function is only able to handle queries that returns a
340
     * table such as SELECT.
341
     *
342
     * @author Matthias Grimm <[email protected]>
343
     *
344
     * @param  string $query  SQL string that contains the query
345
     * @return array|false the result table
346
     */
347
    protected function _queryDB($query) {
348
        $resultarray = array();
349
        if($this->dbcon) {
350
            $result = @pg_query($this->dbcon, $query);
351
            if($result) {
352
                while(($t = pg_fetch_assoc($result)) !== false)
353
                    $resultarray[] = $t;
354
                pg_free_result($result);
355
                return $resultarray;
356
            } else{
357
                $this->_debug('PgSQL err: '.pg_last_error($this->dbcon), -1, __LINE__, __FILE__);
358
            }
359
        }
360
        return false;
361
    }
362
363
    /**
364
     * Executes an update or insert query. This differs from the
365
     * MySQL one because it does NOT return the last insertID
366
     *
367
     * @author Andreas Gohr <[email protected]>
368
     *
369
     * @param string $query
370
     * @return bool
371
     */
372
    protected function _modifyDB($query) {
373
        if($this->dbcon) {
374
            $result = @pg_query($this->dbcon, $query);
375
            if($result) {
376
                pg_free_result($result);
377
                return true;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return true; (boolean) is incompatible with the return type of the parent method auth_plugin_authmysql::_modifyDB of type integer|false.

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...
378
            }
379
            $this->_debug('PgSQL err: '.pg_last_error($this->dbcon), -1, __LINE__, __FILE__);
380
        }
381
        return false;
382
    }
383
384
    /**
385
     * Start a transaction
386
     *
387
     * @author Matthias Grimm <[email protected]>
388
     *
389
     * @param string $mode  could be 'READ' or 'WRITE'
390
     * @return bool
391
     */
392
    protected function _lockTables($mode) {
393
        if($this->dbcon) {
394
            $this->_modifyDB('BEGIN');
395
            return true;
396
        }
397
        return false;
398
    }
399
400
    /**
401
     * Commit a transaction
402
     *
403
     * @author Matthias Grimm <[email protected]>
404
     *
405
     * @return bool
406
     */
407
    protected function _unlockTables() {
408
        if($this->dbcon) {
409
            $this->_modifyDB('COMMIT');
410
            return true;
411
        }
412
        return false;
413
    }
414
415
    /**
416
     * Escape a string for insertion into the database
417
     *
418
     * @author Andreas Gohr <[email protected]>
419
     *
420
     * @param  string  $string The string to escape
421
     * @param  bool    $like   Escape wildcard chars as well?
422
     * @return string
423
     */
424
    protected function _escape($string, $like = false) {
425
        $string = pg_escape_string($string);
426
        if($like) {
427
            $string = addcslashes($string, '%_');
428
        }
429
        return $string;
430
    }
431
}