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