xref: /dokuwiki/lib/plugins/auth.php (revision c5cec702c5deebbeddeb00147d906a0cd0fc7ac0)
1<?php
2// must be run within Dokuwiki
3if(!defined('DOKU_INC')) die();
4
5/**
6 * Auth Plugin Prototype
7 *
8 * foundation authorisation class
9 * all auth classes should inherit from this class
10 *
11 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
12 * @author     Chris Smith <chris@jalakai.co.uk>
13 * @author     Jan Schumann <js@jschumann-it.com>
14 */
15class DokuWiki_Auth_Plugin extends DokuWiki_Plugin {
16    public $success = true;
17
18    /**
19     * Possible things an auth backend module may be able to
20     * do. The things a backend can do need to be set to true
21     * in the constructor.
22     */
23    protected $cando = array(
24        'addUser'      => false, // can Users be created?
25        'delUser'      => false, // can Users be deleted?
26        'modLogin'     => false, // can login names be changed?
27        'modPass'      => false, // can passwords be changed?
28        'modName'      => false, // can real names be changed?
29        'modMail'      => false, // can emails be changed?
30        'modGroups'    => false, // can groups be changed?
31        'getUsers'     => false, // can a (filtered) list of users be retrieved?
32        'getUserCount' => false, // can the number of users be retrieved?
33        'getGroups'    => false, // can a list of available groups be retrieved?
34        'external'     => false, // does the module do external auth checking?
35        'logout'       => true, // can the user logout again? (eg. not possible with HTTP auth)
36    );
37
38    /**
39     * Constructor.
40     *
41     * Carry out sanity checks to ensure the object is
42     * able to operate. Set capabilities in $this->cando
43     * array here
44     *
45     * For future compatibility, sub classes should always include a call
46     * to parent::__constructor() in their constructors!
47     *
48     * Set $this->success to false if checks fail
49     *
50     * @author  Christopher Smith <chris@jalakai.co.uk>
51     */
52    public function __construct() {
53        // the base class constructor does nothing, derived class
54        // constructors do the real work
55    }
56
57    /**
58     * Available Capabilities. [ DO NOT OVERRIDE ]
59     *
60     * For introspection/debugging
61     *
62     * @author  Christopher Smith <chris@jalakai.co.uk>
63     * @return  array
64     */
65    public function getCapabilities(){
66        return array_keys($this->cando);
67    }
68
69    /**
70     * Capability check. [ DO NOT OVERRIDE ]
71     *
72     * Checks the capabilities set in the $this->cando array and
73     * some pseudo capabilities (shortcutting access to multiple
74     * ones)
75     *
76     * ususal capabilities start with lowercase letter
77     * shortcut capabilities start with uppercase letter
78     *
79     * @author  Andreas Gohr <andi@splitbrain.org>
80     * @param   string $cap the capability to check
81     * @return  bool
82     */
83    public function canDo($cap) {
84        switch($cap) {
85            case 'Profile':
86                // can at least one of the user's properties be changed?
87                return ($this->cando['modPass'] ||
88                    $this->cando['modName'] ||
89                    $this->cando['modMail']);
90                break;
91            case 'UserMod':
92                // can at least anything be changed?
93                return ($this->cando['modPass'] ||
94                    $this->cando['modName'] ||
95                    $this->cando['modMail'] ||
96                    $this->cando['modLogin'] ||
97                    $this->cando['modGroups'] ||
98                    $this->cando['modMail']);
99                break;
100            default:
101                // print a helping message for developers
102                if(!isset($this->cando[$cap])) {
103                    msg("Check for unknown capability '$cap' - Do you use an outdated Plugin?", -1);
104                }
105                return $this->cando[$cap];
106        }
107    }
108
109    /**
110     * Trigger the AUTH_USERDATA_CHANGE event and call the modification function. [ DO NOT OVERRIDE ]
111     *
112     * You should use this function instead of calling createUser, modifyUser or
113     * deleteUsers directly. The event handlers can prevent the modification, for
114     * example for enforcing a user name schema.
115     *
116     * @author Gabriel Birke <birke@d-scribe.de>
117     * @param string $type   Modification type ('create', 'modify', 'delete')
118     * @param array  $params Parameters for the createUser, modifyUser or deleteUsers method. The content of this array depends on the modification type
119     * @return bool|null|int Result from the modification function or false if an event handler has canceled the action
120     */
121    public function triggerUserMod($type, $params) {
122        $validTypes = array(
123            'create' => 'createUser',
124            'modify' => 'modifyUser',
125            'delete' => 'deleteUsers'
126        );
127        if(empty($validTypes[$type])) {
128            return false;
129        }
130
131        $result = false;
132        $eventdata = array('type' => $type, 'params' => $params, 'modification_result' => null);
133        $evt       = new Doku_Event('AUTH_USER_CHANGE', $eventdata);
134        if($evt->advise_before(true)) {
135            $result                           = call_user_func_array(array($this, $validTypes[$type]), $evt->data['params']);
136            $evt->data['modification_result'] = $result;
137        }
138        $evt->advise_after();
139        unset($evt);
140        return $result;
141    }
142
143    /**
144     * Log off the current user [ OPTIONAL ]
145     *
146     * Is run in addition to the ususal logoff method. Should
147     * only be needed when trustExternal is implemented.
148     *
149     * @see     auth_logoff()
150     * @author  Andreas Gohr <andi@splitbrain.org>
151     */
152    public function logOff() {
153    }
154
155    /**
156     * Do all authentication [ OPTIONAL ]
157     *
158     * Set $this->cando['external'] = true when implemented
159     *
160     * If this function is implemented it will be used to
161     * authenticate a user - all other DokuWiki internals
162     * will not be used for authenticating, thus
163     * implementing the checkPass() function is not needed
164     * anymore.
165     *
166     * The function can be used to authenticate against third
167     * party cookies or Apache auth mechanisms and replaces
168     * the auth_login() function
169     *
170     * The function will be called with or without a set
171     * username. If the Username is given it was called
172     * from the login form and the given credentials might
173     * need to be checked. If no username was given it
174     * the function needs to check if the user is logged in
175     * by other means (cookie, environment).
176     *
177     * The function needs to set some globals needed by
178     * DokuWiki like auth_login() does.
179     *
180     * @see     auth_login()
181     * @author  Andreas Gohr <andi@splitbrain.org>
182     *
183     * @param   string  $user    Username
184     * @param   string  $pass    Cleartext Password
185     * @param   bool    $sticky  Cookie should not expire
186     * @return  bool             true on successful auth
187     */
188    public function trustExternal($user, $pass, $sticky = false) {
189        /* some example:
190
191        global $USERINFO;
192        global $conf;
193        $sticky ? $sticky = true : $sticky = false; //sanity check
194
195        // do the checking here
196
197        // set the globals if authed
198        $USERINFO['name'] = 'FIXME';
199        $USERINFO['mail'] = 'FIXME';
200        $USERINFO['grps'] = array('FIXME');
201        $_SERVER['REMOTE_USER'] = $user;
202        $_SESSION[DOKU_COOKIE]['auth']['user'] = $user;
203        $_SESSION[DOKU_COOKIE]['auth']['pass'] = $pass;
204        $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO;
205        return true;
206
207        */
208    }
209
210    /**
211     * Check user+password [ MUST BE OVERRIDDEN ]
212     *
213     * Checks if the given user exists and the given
214     * plaintext password is correct
215     *
216     * May be ommited if trustExternal is used.
217     *
218     * @author  Andreas Gohr <andi@splitbrain.org>
219     * @param   string $user the user name
220     * @param   string $pass the clear text password
221     * @return  bool
222     */
223    public function checkPass($user, $pass) {
224        msg("no valid authorisation system in use", -1);
225        return false;
226    }
227
228    /**
229     * Return user info [ MUST BE OVERRIDDEN ]
230     *
231     * Returns info about the given user needs to contain
232     * at least these fields:
233     *
234     * name string  full name of the user
235     * mail string  email address of the user
236     * grps array   list of groups the user is in
237     *
238     * @author  Andreas Gohr <andi@splitbrain.org>
239     * @param   string $user the user name
240     * @param   bool $requireGroups whether or not the returned data must include groups
241     * @return  false|array containing user data or false
242     */
243    public function getUserData($user, $requireGroups=true) {
244        if(!$this->cando['external']) msg("no valid authorisation system in use", -1);
245        return false;
246    }
247
248    /**
249     * Create a new User [implement only where required/possible]
250     *
251     * Returns false if the user already exists, null when an error
252     * occurred and true if everything went well.
253     *
254     * The new user HAS TO be added to the default group by this
255     * function!
256     *
257     * Set addUser capability when implemented
258     *
259     * @author  Andreas Gohr <andi@splitbrain.org>
260     * @param  string     $user
261     * @param  string     $pass
262     * @param  string     $name
263     * @param  string     $mail
264     * @param  null|array $grps
265     * @return bool|null
266     */
267    public function createUser($user, $pass, $name, $mail, $grps = null) {
268        msg("authorisation method does not allow creation of new users", -1);
269        return null;
270    }
271
272    /**
273     * Modify user data [implement only where required/possible]
274     *
275     * Set the mod* capabilities according to the implemented features
276     *
277     * @author  Chris Smith <chris@jalakai.co.uk>
278     * @param   string $user    nick of the user to be changed
279     * @param   array  $changes array of field/value pairs to be changed (password will be clear text)
280     * @return  bool
281     */
282    public function modifyUser($user, $changes) {
283        msg("authorisation method does not allow modifying of user data", -1);
284        return false;
285    }
286
287    /**
288     * Delete one or more users [implement only where required/possible]
289     *
290     * Set delUser capability when implemented
291     *
292     * @author  Chris Smith <chris@jalakai.co.uk>
293     * @param   array  $users
294     * @return  int    number of users deleted
295     */
296    public function deleteUsers($users) {
297        msg("authorisation method does not allow deleting of users", -1);
298        return 0;
299    }
300
301    /**
302     * Return a count of the number of user which meet $filter criteria
303     * [should be implemented whenever retrieveUsers is implemented]
304     *
305     * Set getUserCount capability when implemented
306     *
307     * @author Chris Smith <chris@jalakai.co.uk>
308     * @param  array $filter array of field/pattern pairs, empty array for no filter
309     * @return int
310     */
311    public function getUserCount($filter = array()) {
312        msg("authorisation method does not provide user counts", -1);
313        return 0;
314    }
315
316    /**
317     * Bulk retrieval of user data [implement only where required/possible]
318     *
319     * Set getUsers capability when implemented
320     *
321     * @author  Chris Smith <chris@jalakai.co.uk>
322     * @param   int   $start     index of first user to be returned
323     * @param   int   $limit     max number of users to be returned, 0 for unlimited
324     * @param   array $filter    array of field/pattern pairs, null for no filter
325     * @return  array list of userinfo (refer getUserData for internal userinfo details)
326     */
327    public function retrieveUsers($start = 0, $limit = 0, $filter = null) {
328        msg("authorisation method does not support mass retrieval of user data", -1);
329        return array();
330    }
331
332    /**
333     * Define a group [implement only where required/possible]
334     *
335     * Set addGroup capability when implemented
336     *
337     * @author  Chris Smith <chris@jalakai.co.uk>
338     * @param   string $group
339     * @return  bool
340     */
341    public function addGroup($group) {
342        msg("authorisation method does not support independent group creation", -1);
343        return false;
344    }
345
346    /**
347     * Retrieve groups [implement only where required/possible]
348     *
349     * Set getGroups capability when implemented
350     *
351     * @author  Chris Smith <chris@jalakai.co.uk>
352     * @param   int $start
353     * @param   int $limit
354     * @return  array
355     */
356    public function retrieveGroups($start = 0, $limit = 0) {
357        msg("authorisation method does not support group list retrieval", -1);
358        return array();
359    }
360
361    /**
362     * Return case sensitivity of the backend [OPTIONAL]
363     *
364     * When your backend is caseinsensitive (eg. you can login with USER and
365     * user) then you need to overwrite this method and return false
366     *
367     * @return bool
368     */
369    public function isCaseSensitive() {
370        return true;
371    }
372
373    /**
374     * Sanitize a given username [OPTIONAL]
375     *
376     * This function is applied to any user name that is given to
377     * the backend and should also be applied to any user name within
378     * the backend before returning it somewhere.
379     *
380     * This should be used to enforce username restrictions.
381     *
382     * @author Andreas Gohr <andi@splitbrain.org>
383     * @param string $user username
384     * @return string the cleaned username
385     */
386    public function cleanUser($user) {
387        return $user;
388    }
389
390    /**
391     * Sanitize a given groupname [OPTIONAL]
392     *
393     * This function is applied to any groupname that is given to
394     * the backend and should also be applied to any groupname within
395     * the backend before returning it somewhere.
396     *
397     * This should be used to enforce groupname restrictions.
398     *
399     * Groupnames are to be passed without a leading '@' here.
400     *
401     * @author Andreas Gohr <andi@splitbrain.org>
402     * @param  string $group groupname
403     * @return string the cleaned groupname
404     */
405    public function cleanGroup($group) {
406        return $group;
407    }
408
409    /**
410     * Check Session Cache validity [implement only where required/possible]
411     *
412     * DokuWiki caches user info in the user's session for the timespan defined
413     * in $conf['auth_security_timeout'].
414     *
415     * This makes sure slow authentication backends do not slow down DokuWiki.
416     * This also means that changes to the user database will not be reflected
417     * on currently logged in users.
418     *
419     * To accommodate for this, the user manager plugin will touch a reference
420     * file whenever a change is submitted. This function compares the filetime
421     * of this reference file with the time stored in the session.
422     *
423     * This reference file mechanism does not reflect changes done directly in
424     * the backend's database through other means than the user manager plugin.
425     *
426     * Fast backends might want to return always false, to force rechecks on
427     * each page load. Others might want to use their own checking here. If
428     * unsure, do not override.
429     *
430     * @param  string $user - The username
431     * @author Andreas Gohr <andi@splitbrain.org>
432     * @return bool
433     */
434    public function useSessionCache($user) {
435        global $conf;
436        return ($_SESSION[DOKU_COOKIE]['auth']['time'] >= @filemtime($conf['cachedir'].'/sessionpurge'));
437    }
438
439    /**
440     * Overrides the standard config loading to integrate old auth module style configs
441     *
442     * @deprecated 2012-11-09
443     */
444    public function loadConfig() {
445        global $conf;
446        $plugin  = $this->getPluginName();
447        $oldname = preg_replace('/^auth/', '', $plugin);
448
449        $default = $this->readDefaultSettings();
450        $oldconf = array();
451        if(isset($conf['auth'][$oldname])) $oldconf = (array) $conf['auth'][$oldname];
452        $conf['plugin'][$plugin] = array_merge($default, $oldconf, (array) $conf['plugin'][$plugin]);
453
454        $this->conf         =& $conf['plugin'][$plugin];
455        $this->configloaded = true;
456    }
457}
458