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