xref: /dokuwiki/lib/plugins/authplain/auth.php (revision 76ce1169a0c8cbb18423b1581800b9aa1050ccd5)
1<?php
2// must be run within Dokuwiki
3if(!defined('DOKU_INC')) die();
4
5/**
6 * Plaintext authentication backend
7 *
8 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
9 * @author     Andreas Gohr <andi@splitbrain.org>
10 * @author     Chris Smith <chris@jalakai.co.uk>
11 * @author     Jan Schumann <js@schumann-it.com>
12 */
13class auth_plugin_authplain extends DokuWiki_Auth_Plugin {
14    var $users = null;
15    var $_pattern = array();
16
17    /**
18     * Constructor
19     *
20     * Carry out sanity checks to ensure the object is
21     * able to operate. Set capabilities.
22     *
23     * @author  Christopher Smith <chris@jalakai.co.uk>
24     */
25    function __construct() {
26        parent::__construct();
27        global $config_cascade;
28
29        if (!@is_readable($config_cascade['plainauth.users']['default'])){
30            $this->success = false;
31        }else{
32            if(@is_writable($config_cascade['plainauth.users']['default'])){
33                $this->cando['addUser']      = true;
34                $this->cando['delUser']      = true;
35                $this->cando['modLogin']     = true;
36                $this->cando['modPass']      = true;
37                $this->cando['modName']      = true;
38                $this->cando['modMail']      = true;
39                $this->cando['modGroups']    = true;
40            }
41            $this->cando['getUsers']     = true;
42            $this->cando['getUserCount'] = true;
43        }
44    }
45
46    /**
47     * Check user+password [required auth function]
48     *
49     * Checks if the given user exists and the given
50     * plaintext password is correct
51     *
52     * @author  Andreas Gohr <andi@splitbrain.org>
53     * @return  bool
54     */
55    function checkPass($user,$pass){
56
57        $userinfo = $this->getUserData($user);
58        if ($userinfo === false) return false;
59
60        return auth_verifyPassword($pass,$this->users[$user]['pass']);
61    }
62
63    /**
64     * Return user info
65     *
66     * Returns info about the given user needs to contain
67     * at least these fields:
68     *
69     * name string  full name of the user
70     * mail string  email addres of the user
71     * grps array   list of groups the user is in
72     *
73     * @author  Andreas Gohr <andi@splitbrain.org>
74     */
75    function getUserData($user){
76
77        if($this->users === null) $this->_loadUserData();
78        return isset($this->users[$user]) ? $this->users[$user] : false;
79    }
80
81    /**
82     * Create a new User
83     *
84     * Returns false if the user already exists, null when an error
85     * occurred and true if everything went well.
86     *
87     * The new user will be added to the default group by this
88     * function if grps are not specified (default behaviour).
89     *
90     * @author  Andreas Gohr <andi@splitbrain.org>
91     * @author  Chris Smith <chris@jalakai.co.uk>
92     */
93    function createUser($user,$pwd,$name,$mail,$grps=null){
94        global $conf;
95        global $config_cascade;
96
97        // user mustn't already exist
98        if ($this->getUserData($user) !== false) return false;
99
100        $pass = auth_cryptPassword($pwd);
101
102        // set default group if no groups specified
103        if (!is_array($grps)) $grps = array($conf['defaultgroup']);
104
105        // prepare user line
106        $groups = join(',',$grps);
107        $userline = join(':',array($user,$pass,$name,$mail,$groups))."\n";
108
109        if (io_saveFile($config_cascade['plainauth.users']['default'],$userline,true)) {
110            $this->users[$user] = compact('pass','name','mail','grps');
111            return $pwd;
112        }
113
114        msg('The '.$config_cascade['plainauth.users']['default'].
115                ' file is not writable. Please inform the Wiki-Admin',-1);
116        return null;
117    }
118
119    /**
120     * Modify user data
121     *
122     * @author  Chris Smith <chris@jalakai.co.uk>
123     * @param   $user      nick of the user to be changed
124     * @param   $changes   array of field/value pairs to be changed (password will be clear text)
125     * @return  bool
126     */
127    function modifyUser($user, $changes) {
128        global $conf;
129        global $ACT;
130        global $INFO;
131        global $config_cascade;
132
133        // sanity checks, user must already exist and there must be something to change
134        if (($userinfo = $this->getUserData($user)) === false) return false;
135        if (!is_array($changes) || !count($changes)) return true;
136
137        // update userinfo with new data, remembering to encrypt any password
138        $newuser = $user;
139        foreach ($changes as $field => $value) {
140            if ($field == 'user') {
141                $newuser = $value;
142                continue;
143            }
144            if ($field == 'pass') $value = auth_cryptPassword($value);
145            $userinfo[$field] = $value;
146        }
147
148        $groups = join(',',$userinfo['grps']);
149        $userline = join(':',array($newuser, $userinfo['pass'], $userinfo['name'], $userinfo['mail'], $groups))."\n";
150
151        if (!$this->deleteUsers(array($user))) {
152            msg('Unable to modify user data. Please inform the Wiki-Admin',-1);
153            return false;
154        }
155
156        if (!io_saveFile($config_cascade['plainauth.users']['default'],$userline,true)) {
157            msg('There was an error modifying your user data. You should register again.',-1);
158            // FIXME, user has been deleted but not recreated, should force a logout and redirect to login page
159            $ACT == 'register';
160            return false;
161        }
162
163        $this->users[$newuser] = $userinfo;
164        return true;
165    }
166
167    /**
168     * Remove one or more users from the list of registered users
169     *
170     * @author  Christopher Smith <chris@jalakai.co.uk>
171     * @param   array  $users   array of users to be deleted
172     * @return  int             the number of users deleted
173     */
174    function deleteUsers($users) {
175        global $config_cascade;
176
177        if (!is_array($users) || empty($users)) return 0;
178
179        if ($this->users === null) $this->_loadUserData();
180
181        $deleted = array();
182        foreach ($users as $user) {
183            if (isset($this->users[$user])) $deleted[] = preg_quote($user,'/');
184        }
185
186        if (empty($deleted)) return 0;
187
188        $pattern = '/^('.join('|',$deleted).'):/';
189
190        if (io_deleteFromFile($config_cascade['plainauth.users']['default'],$pattern,true)) {
191            foreach ($deleted as $user) unset($this->users[$user]);
192            return count($deleted);
193        }
194
195        // problem deleting, reload the user list and count the difference
196        $count = count($this->users);
197        $this->_loadUserData();
198        $count -= count($this->users);
199        return $count;
200    }
201
202    /**
203     * Return a count of the number of user which meet $filter criteria
204     *
205     * @author  Chris Smith <chris@jalakai.co.uk>
206     */
207    function getUserCount($filter=array()) {
208
209        if($this->users === null) $this->_loadUserData();
210
211        if (!count($filter)) return count($this->users);
212
213        $count = 0;
214        $this->_constructPattern($filter);
215
216        foreach ($this->users as $user => $info) {
217            $count += $this->_filter($user, $info);
218        }
219
220        return $count;
221    }
222
223    /**
224     * Bulk retrieval of user data
225     *
226     * @author  Chris Smith <chris@jalakai.co.uk>
227     * @param   start     index of first user to be returned
228     * @param   limit     max number of users to be returned
229     * @param   filter    array of field/pattern pairs
230     * @return  array of userinfo (refer getUserData for internal userinfo details)
231     */
232    function retrieveUsers($start=0,$limit=0,$filter=array()) {
233
234        if ($this->users === null) $this->_loadUserData();
235
236        ksort($this->users);
237
238        $i = 0;
239        $count = 0;
240        $out = array();
241        $this->_constructPattern($filter);
242
243        foreach ($this->users as $user => $info) {
244            if ($this->_filter($user, $info)) {
245                if ($i >= $start) {
246                    $out[$user] = $info;
247                    $count++;
248                    if (($limit > 0) && ($count >= $limit)) break;
249                }
250                $i++;
251            }
252        }
253
254        return $out;
255    }
256
257    /**
258     * Only valid pageid's (no namespaces) for usernames
259     */
260    function cleanUser($user){
261        global $conf;
262        return cleanID(str_replace(':',$conf['sepchar'],$user));
263    }
264
265    /**
266     * Only valid pageid's (no namespaces) for groupnames
267     */
268    function cleanGroup($group){
269        global $conf;
270        return cleanID(str_replace(':',$conf['sepchar'],$group));
271    }
272
273    /**
274     * Load all user data
275     *
276     * loads the user file into a datastructure
277     *
278     * @author  Andreas Gohr <andi@splitbrain.org>
279     */
280    function _loadUserData(){
281        global $config_cascade;
282
283        $this->users = array();
284
285        if(!@file_exists($config_cascade['plainauth.users']['default'])) return;
286
287        $lines = file($config_cascade['plainauth.users']['default']);
288        foreach($lines as $line){
289            $line = preg_replace('/#.*$/','',$line); //ignore comments
290            $line = trim($line);
291            if(empty($line)) continue;
292
293            $row    = explode(":",$line,5);
294            $groups = array_values(array_filter(explode(",",$row[4])));
295
296            $this->users[$row[0]]['pass'] = $row[1];
297            $this->users[$row[0]]['name'] = urldecode($row[2]);
298            $this->users[$row[0]]['mail'] = $row[3];
299            $this->users[$row[0]]['grps'] = $groups;
300        }
301    }
302
303    /**
304     * return 1 if $user + $info match $filter criteria, 0 otherwise
305     *
306     * @author   Chris Smith <chris@jalakai.co.uk>
307     */
308    function _filter($user, $info) {
309        // FIXME
310        foreach ($this->_pattern as $item => $pattern) {
311            if ($item == 'user') {
312                if (!preg_match($pattern, $user)) return 0;
313            } else if ($item == 'grps') {
314                if (!count(preg_grep($pattern, $info['grps']))) return 0;
315            } else {
316                if (!preg_match($pattern, $info[$item])) return 0;
317            }
318        }
319        return 1;
320    }
321
322    function _constructPattern($filter) {
323        $this->_pattern = array();
324        foreach ($filter as $item => $pattern) {
325            //        $this->_pattern[$item] = '/'.preg_quote($pattern,"/").'/i';          // don't allow regex characters
326            $this->_pattern[$item] = '/'.str_replace('/','\/',$pattern).'/i';    // allow regex characters
327        }
328    }
329}