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