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