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