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