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