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