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