1<?php
2// must be run within Dokuwiki
3if(!defined('DOKU_INC')) die();
4
5/**
6* Chained authentication backend
7*
8* @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
9* @author     Philipp Neuser <pneuser@physik.fu-berlin.de>
10* @author     Christian Marg <marg@rz.tu-clausthal.de>
11*
12* Based on "Chained authentication backend"
13* by Grant Gardner <grant@lastweekend.com.au>
14* see https://www.dokuwiki.org/auth:ggauth
15*
16*/
17class auth_plugin_authchained extends DokuWiki_Auth_Plugin {
18    public $success = true;
19    //array with authentication plugins
20    protected $chained_plugins = array();
21    protected $chained_auth = NULL;
22    protected $usermanager_auth = NULL;
23    protected $any_external = false;
24
25    /**
26    * Constructor.
27    *
28    * Loads all configured plugins or the authentication plugin of the
29    * logged in user.
30    *
31    * @author  Philipp Neuser <pneuser@physik.fu-berlin.de>
32    * @author  Christian Marg <marg@rz.tu-clausthal.de>
33    */
34    public function __construct() {
35        global $conf;
36        // call parent
37        #      parent::__constructor();
38
39        //check if there is already an authentication plugin selected
40        if(     isset($_SESSION[DOKU_COOKIE]['plugin']['authchained']['module']) &&
41                !empty($_SESSION[DOKU_COOKIE]['plugin']['authchained']['module']) ) {
42
43            //get previously selected authentication plugin
44            $this->chained_auth =& plugin_load('auth',$_SESSION[DOKU_COOKIE]['plugin']['authchained']['module']);
45            if ( is_null($this->chained_auth) || !$this->chained_auth->success ) {
46                $this->success = false;
47            }
48        }
49
50        //get authentication plugins
51        if($this->getConf('authtypes')){
52            foreach(explode(":",$this->getConf('authtypes')) as $tmp_plugin){
53                $tmp_class =& plugin_load('auth',$tmp_plugin);
54
55                if ( !is_null($tmp_class) || $tmp_class->success ) {
56                    $tmp_module = array($tmp_plugin,$tmp_class);
57                    array_push($this->chained_plugins, $tmp_module);
58                    $this->any_external |= $tmp_class->canDo('external');
59                } else {
60                    msg("Problem constructing $tmp_plugin",-1);
61                    $this->success = false;
62                }
63            }
64        } else {
65            $success = false;
66        }
67
68        // If defined, instantiate usermanager authtype.
69        // No need to check for duplicates, "plugin_load" does that for us.
70        if($this->getConf('usermanager_authtype')){
71            $this->usermanager_auth =& plugin_load('auth',$this->getConf('usermanager_authtype'));
72            if(is_null($this->usermanager_auth) || !$this->usermanager_auth->success ) {
73                    msg("Problem constructing usermanager authtype: ".$this->getConf('usermanager_authtype'),-1);
74                    $this->success = false;
75            }
76        } else {
77            $this->usermanager_auth =& $this->chained_auth;
78        }
79
80        //debug
81        // print_r($chained_plugins);
82    }
83
84    /**
85    * Forwards the authentication to configured authplugins.
86    * Returns true, if the usermanager authtype has the capability and no user
87    * is logged in.
88    *
89    * @author  Philipp Neuser <pneuser@physik.fu-berlin.de>
90    * @author  Christian Marg <marg@rz.tu-clausthal.de>
91    * @param   string $cap the capability to check
92    * @return  bool
93    */
94    public function canDo($cap) {
95        global $ACT;
96        #      print_r($cap);
97        if(is_null($this->chained_auth)) {
98            if ($cap == "external") {
99                return $this->any_external;
100            }
101            if (!is_null($this->usermanager_auth)) {
102                return $this->usermanager_auth->canDo($cap);
103            } else {
104                return parent::canDo($cap);
105            }
106        } else {
107            switch($cap) {
108                case 'Profile':
109                case 'logout':
110                case 'external':
111                    //Depends on current user.
112                    return $this->chained_auth->canDo($cap);
113                case 'UserMod':
114                case 'addUser':
115                case 'delUser':
116                case 'getUsers':
117                case 'getUserCount':
118                case 'getGroups':
119                    //Depends on the auth for use with user manager
120                    return $this->usermanager_auth->canDo($cap);
121                case 'modPass':
122                case 'modName':
123                case 'modLogin':
124                case 'modGroups':
125                case 'modMail':
126                    /**
127                    * Use request attributes to guess whether we are in the Profile or UserManager
128                    * and return the appropriate auth capabilities
129                    */
130                    if ($ACT == "admin" && isset($_REQUEST['page']) && $_REQUEST['page']=="usermanager") {
131                        return $this->usermanager_auth->canDo($cap);
132                    } else {
133                        // assume we want profile info.
134                        return $this->chained_auth->canDo($cap);
135                    }
136                default:
137                    //Everything else (false)
138                    return parent::canDo($cap);
139            }
140            #echo "canDo $cap ".$this->chained_auth->canDo($cap)."\n";
141        }
142    }
143
144    /**
145    * Forwards the result of the auth plugin of the logged in user and
146    * unsets our session variable.
147    * @see     auth_logoff()
148    * @author  Philipp Neuser <pneuser@physik.fu-berlin.de
149    * @author  Christian Marg <marg@rz.tu-clausthal.de>
150    */
151    public function logOff() {
152        if(!is_null($this->chained_auth))
153            $this->chained_auth->logOff();
154        unset($_SESSION[DOKU_COOKIE]['plugin']['authchained']['module']);
155    }
156
157    /**
158    * Do all authentication [ OPTIONAL ]
159    * If the current plugin is external, be external.
160    *
161    * @see     auth_login()
162    * @author  Philipp Neuser <pneuser@physik.fu-berlin.de>
163    * @author  Christian Marg <marg@rz.tu-clausthal.de>
164    *
165    * @param   string  $user    Username
166    * @param   string  $pass    Cleartext Password
167    * @param   bool    $sticky  Cookie should not expire
168    * @return  bool             true on successful auth
169    */
170    public function trustExternal($user, $pass, $sticky = false) {
171        global $INPUT;
172        foreach($this->chained_plugins as $module) {
173            if($module[1]->canDo('external') && $module[1]->trustExternal($user, $pass, $sticky)) {
174                $_SESSION[DOKU_COOKIE]['plugin']['authchained']['module'] = $module[0];
175                $this->chained_auth = $module[1];
176                return true;
177            }
178        }
179        $evdata = array(
180            'user'     => $INPUT->str('u'),
181            'password' => $INPUT->str('p'),
182            'sticky'   => $INPUT->bool('r'),
183            'silent'   => $INPUT->bool('http_credentials')
184        );
185        trigger_event('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper');
186        return false;
187    }
188
189    /**
190    * Check user+password [ MUST BE OVERRIDDEN ]
191    *
192    * Checks if the given user exists in one of the plugins and checks
193    * against the given password. The first plugin returning true becomes
194    * auth plugin of the user session.
195    *
196    * @author  Philipp Neuser <pneuser@physik.fu-berlin.de
197    * @author  Christian Marg <marg@rz.tu-clausthal.de>
198    * @param   string $user the user name
199    * @param   string $pass the clear text password
200    * @return  bool
201    */
202    public function checkPass($user, $pass) {
203        //debug
204        // print_r($this->chained_plugins);
205        if(!is_null($this->chained_auth))
206            return $this->chained_auth->checkPass($user, $pass);
207        foreach($this->chained_plugins as $module) {
208            if($module[1]->canDo('external') && $module[1]->trustExternal($user, $pass)) {
209                $_SESSION[DOKU_COOKIE]['plugin']['authchained']['module'] = $module[0];
210                $this->chained_auth = $module[1];
211                return true;
212            }
213            if($module[1]->checkPass($user, $pass)) {
214                $_SESSION[DOKU_COOKIE]['plugin']['authchained']['module'] = $module[0];
215                $this->chained_auth = $module[1];
216                return true;
217            }
218        }
219        return false;
220    }
221
222    /**
223    * Forwards the result of the auth plugin of the logged in user or
224    * checks all plugins if the users exists. The first plugin returning
225    * data is used.
226    *
227    * name string  full name of the user
228    * mail string  email addres of the user
229    * grps array   list of groups the user is in
230    *
231    * @author  Philipp Neuser <pneuser@physik.fu-berlin.de>
232    * @author  Christian Marg <marg@rz.tu-clausthal.de>
233    * @param   string $user the user name
234    * @return  array containing user data or false
235    */
236    public function getUserData($user, $requireGroups=true) {
237        global $ACT, $INPUT;
238
239        //if(!$this->cando['external']) msg("no valid authorisation system in use", -1);
240        //       echo "TESTSETEST";
241
242        //print_r($this->chained_auth);
243        if ($ACT == "admin" && isset($_REQUEST['page']) && $_REQUEST['page']=="usermanager") {
244            if(!is_null($this->usermanager_auth))
245                return $this->usermanager_auth->getUserData($user);
246	}
247
248        if(is_null($this->chained_auth)||(!is_null($INPUT->server) && $user != $INPUT->server->str('REMOTE_USER'))) {
249            foreach($this->chained_plugins as $module) {
250                $tmp_array = $module[1]->getUserData($user);
251                if(!is_bool($tmp_array))
252                    $tmp_chk_arr =array_filter($tmp_array);
253                if(!empty($tmp_chk_arr) && $tmp_array)
254                    return $tmp_array;
255            }
256            return false;
257        } else {
258            return $this->chained_auth->getUserData($user);
259        }
260    }
261
262    /**
263    * Forwards the result of the auth plugin of the logged in user or
264    * returns null.
265    *
266    * @author  Philipp Neuser <pneuser@physik.fu-berlin.de>
267    * @author  Christian Marg <marg@rz.tu-clausthal.de>
268    * @param   string     $user
269    * @param   string     $pass
270    * @param   string     $name
271    * @param   string     $mail
272    * @param   null|array $grps
273    * @return  bool|null
274    */
275    public function createUser($user, $pass, $name, $mail, $grps = null) {
276        if(!is_null($this->usermanager_auth) && $this->canDo('addUser')) {
277            return $this->usermanager_auth->createUser($user, $pass, $name, $mail, $grps);
278        } else {
279            msg("authorisation method does not allow creation of new users", -1);
280            return null;
281        }
282    }
283
284    /**
285    * Forwards the result of the auth plugin of the logged in user or
286    * returns false
287    *
288    * @author  Philipp Neuser <pneuser@physik.fu-berlin.de>
289    * @author  Christian Marg <marg@rz.tu-clausthal.de>
290    * @param   string $user    nick of the user to be changed
291    * @param   array  $changes array of field/value pairs to be changed (password will be clear text)
292    * @return  bool
293    */
294    public function modifyUser($user, $changes) {
295        if(!is_null($this->usermanager_auth) && $this->canDo('UserMod') ) {
296            return $this->usermanager_auth->modifyUser($user, $changes);
297        } else {
298            msg("authorisation method does not allow modifying of user data", -1);
299            return null;
300        }
301    }
302
303    /**
304    * Forwards the result of the auth plugin of the logged in user or
305    * returns false
306    *
307    * @author  Philipp Neuser <pneuser@physik.fu-berlin.de>
308    * @author  Christian Marg <marg@rz.tu-clausthal.de>
309    * @param   array  $users
310    * @return  int    number of users deleted
311    */
312    public function deleteUsers($users) {
313        if(!is_null($this->usermanager_auth) && $this->canDo('delUser') ) {
314            return $this->usermanager_auth->deleteUsers($users);
315        }else{
316            msg("authorisation method does not allow deleting of users", -1);
317            return false;
318        }
319    }
320
321    /**
322    * Forwards the result of the auth plugin of the logged in user or
323    * returns 0
324    *
325    * @author Philipp Neuser <pneuser@physik.fu-berlin.de>
326    * @author Christian Marg <marg@rz.tu-clausthal.de>
327    * @param  array $filter array of field/pattern pairs, empty array for no filter
328    * @return int
329    */
330    public function getUserCount($filter = array()) {
331        if(!is_null($this->usermanager_auth) && $this->canDo('getUserCount') ){
332            return $this->usermanager_auth->getUserCount($filter);
333        } else {
334            msg("authorisation method does not provide user counts", -1);
335            return 0;
336        }
337    }
338
339    /**
340    * Forwards the result of the auth plugin of the logged in user or
341    * returns empty array
342    *
343    * @author  Philipp Neuser <pneuser@physik.fu-berlin.de>
344    * @author  Christian Marg <marg@rz.tu-clausthal.de>
345    * @param   int   $start     index of first user to be returned
346    * @param   int   $limit     max number of users to be returned
347    * @param   array $filter    array of field/pattern pairs, null for no filter
348    * @return  array list of userinfo (refer getUserData for internal userinfo details)
349    */
350    public function retrieveUsers($start = 0, $limit = -1, $filter = null) {
351        if(!is_null($this->usermanager_auth) && $this->canDo('getUsers') ) {
352            //msg("RetrieveUsers is using ".get_class($this->usermanager_auth));
353            return $this->usermanager_auth->retrieveUsers($start, $limit, $filter);
354        } else {
355            msg("authorisation method does not support mass retrievals", -1);
356            return array();
357        }
358    }
359
360    /**
361    * Forwards the result of the auth plugin of the logged in user or
362    * returns false
363    *
364    * @author  Philipp Neuser <pneuser@physik.fu-berlin.de>
365    * @author  Christian Marg <marg@rz.tu-clausthal.de>
366    * @param   string $group
367    * @return  bool
368    */
369    public function addGroup($group) {
370        if(!is_null($this->usermanager_auth) && $this->canDo('addGroup') ) {
371            return $this->usermanager_auth->addGroup($group);
372        } else {
373            msg("authorisation method does not support independent group creation", -1);
374            return false;
375        }
376    }
377
378    /**
379    * Forwards the result of the auth plugin of the logged in user or
380    * returns empty array
381    *
382    * @author  Philipp Neuser <pneuser@physik.fu-berlin.de>
383    * @author  Christian Marg <marg@rz.tu-clausthal.de>
384    * @param   int $start
385    * @param   int $limit
386    * @return  array
387    */
388    public function retrieveGroups($start = 0, $limit = 0) {
389        if(!is_null($this->usermanager_auth) && $this->canDo('getGroups') ) {
390                return $this->usermanager_auth->retrieveGroups($start,$limit);
391        } else {
392            msg("authorisation method does not support group list retrieval", -1);
393            return array();
394        }
395    }
396
397    /**
398    * Forwards the result of the auth plugin of the logged in user or
399    * returns true
400    *
401    * @return bool
402    */
403    public function isCaseSensitive() {
404        if(is_null($this->chained_auth))
405            return parent::isCaseSensitive();
406        else
407            return $this->chained_auth->isCaseSensitive();
408    }
409
410    /**
411    * Sanitize a given username [OPTIONAL]
412    * Forwards the result of the auth plugin of the logged in user or
413    * returns false
414    *
415    *
416    * @author Philipp Neuser <pneuser@physik.fu-berlin.de>
417    * @author Christian Marg <marg@rz.tu-clausthal.de>
418    * @param  string $user username
419    * @return string the cleaned username
420    */
421    public function cleanUser($user) {
422        global $ACT;
423        //print_r($this->chained_auth);
424        if ($ACT == "admin" && isset($_REQUEST['page']) && $_REQUEST['page']=="usermanager") {
425            if(!is_null($this->usermanager_auth))
426                return $this->usermanager_auth->cleanUser($user);
427        } else {
428            if(!is_null($this->chained_auth))
429                return $this->chained_auth->cleanUser($user);
430        }
431        return parent::cleanUser($user);
432    }
433
434    /**
435    * Sanitize a given groupname [OPTIONAL]
436    * Forwards the result of the auth plugin of the logged in user or
437    * returns false
438    *
439    * @author Philipp Neuser <pneuser@physik.fu-berlin.de>
440    * @author Christian Marg <marg@rz.tu-clausthal.de>
441    * @param  string $group groupname
442    * @return string the cleaned groupname
443    */
444    public function cleanGroup($group) {
445        global $ACT;
446        if ($ACT == "admin" && isset($_REQUEST['page']) && $_REQUEST['page']=="usermanager") {
447            if(!is_null($this->usermanager_auth))
448                return $this->usermanager_auth->cleanGroup($group);
449        } else {
450            if(!is_null($this->chained_auth))
451                return $this->chained_auth->cleanGroup($group);
452        }
453        return parent::cleanGroup($group);
454    }
455
456
457    public function useSessionCache($user) {
458        global $conf;
459        if(is_null($this->chained_auth))
460            return parent::useSessionCache($user);
461        else
462            return $this->chained_auth->useSessionCache($user);
463    }
464
465}
466