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