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