1<?php 2/** 3 * DokuWiki Plugin authpdo (Auth Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8 9// must be run within Dokuwiki 10if(!defined('DOKU_INC')) die(); 11 12class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { 13 14 /** @var PDO */ 15 protected $pdo; 16 17 /** 18 * Constructor. 19 */ 20 public function __construct() { 21 parent::__construct(); // for compatibility 22 23 if(!class_exists('PDO')) { 24 $this->_debug('PDO extension for PHP not found.', -1, __LINE__); 25 $this->success = false; 26 return; 27 } 28 29 if(!$this->getConf('dsn')) { 30 $this->_debug('No DSN specified', -1, __LINE__); 31 $this->success = false; 32 return; 33 } 34 35 try { 36 $this->pdo = new PDO( 37 $this->getConf('dsn'), 38 $this->getConf('user'), 39 $this->getConf('pass'), 40 array( 41 PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // always fetch as array 42 PDO::ATTR_EMULATE_PREPARES => true, // emulating prepares allows us to reuse param names 43 ) 44 ); 45 } catch(PDOException $e) { 46 $this->_debug($e); 47 $this->success = false; 48 return; 49 } 50 51 // FIXME set capabilities accordingly 52 //$this->cando['addUser'] = false; // can Users be created? 53 //$this->cando['delUser'] = false; // can Users be deleted? 54 //$this->cando['modLogin'] = false; // can login names be changed? 55 //$this->cando['modPass'] = false; // can passwords be changed? 56 //$this->cando['modName'] = false; // can real names be changed? 57 //$this->cando['modMail'] = false; // can emails be changed? 58 //$this->cando['modGroups'] = false; // can groups be changed? 59 //$this->cando['getUsers'] = false; // can a (filtered) list of users be retrieved? 60 //$this->cando['getUserCount']= false; // can the number of users be retrieved? 61 //$this->cando['getGroups'] = false; // can a list of available groups be retrieved? 62 //$this->cando['external'] = false; // does the module do external auth checking? 63 //$this->cando['logout'] = true; // can the user logout again? (eg. not possible with HTTP auth) 64 65 // FIXME intialize your auth system and set success to true, if successful 66 $this->success = true; 67 } 68 69 /** 70 * Check user+password 71 * 72 * May be ommited if trustExternal is used. 73 * 74 * @param string $user the user name 75 * @param string $pass the clear text password 76 * @return bool 77 */ 78 public function checkPass($user, $pass) { 79 80 $data = $this->_selectUser($user); 81 if($data == false) return false; 82 83 if(isset($data['hash'])) { 84 // hashed password 85 $passhash = new PassHash(); 86 return $passhash->verify_hash($pass, $data['hash']); 87 } else { 88 // clear text password in the database O_o 89 return ($pass == $data['clear']); 90 } 91 } 92 93 /** 94 * Return user info 95 * 96 * Returns info about the given user needs to contain 97 * at least these fields: 98 * 99 * name string full name of the user 100 * mail string email addres of the user 101 * grps array list of groups the user is in 102 * 103 * @param string $user the user name 104 * @param bool $requireGroups whether or not the returned data must include groups 105 * @return array containing user data or false 106 */ 107 public function getUserData($user, $requireGroups = true) { 108 $data = $this->_selectUser($user); 109 if($data == false) return false; 110 111 if(isset($data['hash'])) unset($data['hash']); 112 if(isset($data['clean'])) unset($data['clean']); 113 114 if($requireGroups) { 115 $data['grps'] = $this->_selectUserGroups($data); 116 } 117 118 return $data; 119 } 120 121 122 /** 123 * Create a new User [implement only where required/possible] 124 * 125 * Returns false if the user already exists, null when an error 126 * occurred and true if everything went well. 127 * 128 * The new user HAS TO be added to the default group by this 129 * function! 130 * 131 * Set addUser capability when implemented 132 * 133 * @param string $user 134 * @param string $pass 135 * @param string $name 136 * @param string $mail 137 * @param null|array $grps 138 * @return bool|null 139 */ 140 //public function createUser($user, $pass, $name, $mail, $grps = null) { 141 // FIXME implement 142 // return null; 143 //} 144 145 /** 146 * Modify user data [implement only where required/possible] 147 * 148 * Set the mod* capabilities according to the implemented features 149 * 150 * @param string $user nick of the user to be changed 151 * @param array $changes array of field/value pairs to be changed (password will be clear text) 152 * @return bool 153 */ 154 //public function modifyUser($user, $changes) { 155 // FIXME implement 156 // return false; 157 //} 158 159 /** 160 * Delete one or more users [implement only where required/possible] 161 * 162 * Set delUser capability when implemented 163 * 164 * @param array $users 165 * @return int number of users deleted 166 */ 167 //public function deleteUsers($users) { 168 // FIXME implement 169 // return false; 170 //} 171 172 /** 173 * Bulk retrieval of user data [implement only where required/possible] 174 * 175 * Set getUsers capability when implemented 176 * 177 * @param int $start index of first user to be returned 178 * @param int $limit max number of users to be returned 179 * @param array $filter array of field/pattern pairs, null for no filter 180 * @return array list of userinfo (refer getUserData for internal userinfo details) 181 */ 182 //public function retrieveUsers($start = 0, $limit = -1, $filter = null) { 183 // FIXME implement 184 // return array(); 185 //} 186 187 /** 188 * Return a count of the number of user which meet $filter criteria 189 * [should be implemented whenever retrieveUsers is implemented] 190 * 191 * Set getUserCount capability when implemented 192 * 193 * @param array $filter array of field/pattern pairs, empty array for no filter 194 * @return int 195 */ 196 //public function getUserCount($filter = array()) { 197 // FIXME implement 198 // return 0; 199 //} 200 201 /** 202 * Define a group [implement only where required/possible] 203 * 204 * Set addGroup capability when implemented 205 * 206 * @param string $group 207 * @return bool 208 */ 209 //public function addGroup($group) { 210 // FIXME implement 211 // return false; 212 //} 213 214 /** 215 * Retrieve groups [implement only where required/possible] 216 * 217 * Set getGroups capability when implemented 218 * 219 * @param int $start 220 * @param int $limit 221 * @return array 222 */ 223 //public function retrieveGroups($start = 0, $limit = 0) { 224 // FIXME implement 225 // return array(); 226 //} 227 228 /** 229 * Return case sensitivity of the backend 230 * 231 * When your backend is caseinsensitive (eg. you can login with USER and 232 * user) then you need to overwrite this method and return false 233 * 234 * @return bool 235 */ 236 public function isCaseSensitive() { 237 return true; 238 } 239 240 /** 241 * Sanitize a given username 242 * 243 * This function is applied to any user name that is given to 244 * the backend and should also be applied to any user name within 245 * the backend before returning it somewhere. 246 * 247 * This should be used to enforce username restrictions. 248 * 249 * @param string $user username 250 * @return string the cleaned username 251 */ 252 public function cleanUser($user) { 253 return $user; 254 } 255 256 /** 257 * Sanitize a given groupname 258 * 259 * This function is applied to any groupname that is given to 260 * the backend and should also be applied to any groupname within 261 * the backend before returning it somewhere. 262 * 263 * This should be used to enforce groupname restrictions. 264 * 265 * Groupnames are to be passed without a leading '@' here. 266 * 267 * @param string $group groupname 268 * @return string the cleaned groupname 269 */ 270 public function cleanGroup($group) { 271 return $group; 272 } 273 274 /** 275 * Check Session Cache validity [implement only where required/possible] 276 * 277 * DokuWiki caches user info in the user's session for the timespan defined 278 * in $conf['auth_security_timeout']. 279 * 280 * This makes sure slow authentication backends do not slow down DokuWiki. 281 * This also means that changes to the user database will not be reflected 282 * on currently logged in users. 283 * 284 * To accommodate for this, the user manager plugin will touch a reference 285 * file whenever a change is submitted. This function compares the filetime 286 * of this reference file with the time stored in the session. 287 * 288 * This reference file mechanism does not reflect changes done directly in 289 * the backend's database through other means than the user manager plugin. 290 * 291 * Fast backends might want to return always false, to force rechecks on 292 * each page load. Others might want to use their own checking here. If 293 * unsure, do not override. 294 * 295 * @param string $user - The username 296 * @return bool 297 */ 298 //public function useSessionCache($user) { 299 // FIXME implement 300 //} 301 302 /** 303 * Select data of a specified user 304 * 305 * @param $user 306 * @return bool|array 307 */ 308 protected function _selectUser($user) { 309 $sql = $this->getConf('select-user'); 310 311 $result = $this->query($sql, array(':user' => $user)); 312 if(!$result) return false; 313 314 if(count($result) > 1) { 315 $this->_debug('Found more than one matching user', -1, __LINE__); 316 return false; 317 } 318 319 $data = array_shift($result); 320 $dataok = true; 321 322 if(!isset($data['user'])) { 323 $this->_debug("Statement did not return 'user' attribute", -1, __LINE__); 324 $dataok = false; 325 } 326 if(!isset($data['hash']) && !isset($data['clear'])) { 327 $this->_debug("Statement did not return 'clear' or 'hash' attribute", -1, __LINE__); 328 $dataok = false; 329 } 330 if(!isset($data['name'])) { 331 $this->_debug("Statement did not return 'name' attribute", -1, __LINE__); 332 $dataok = false; 333 } 334 if(!isset($data['mail'])) { 335 $this->_debug("Statement did not return 'mail' attribute", -1, __LINE__); 336 $dataok = false; 337 } 338 339 if(!$dataok) return false; 340 return $data; 341 } 342 343 /** 344 * Select all groups of a user 345 * 346 * @param array $userdata The userdata as returned by _selectUser() 347 * @return array 348 */ 349 protected function _selectUserGroups($userdata) { 350 global $conf; 351 $sql = $this->getConf('select-user-groups'); 352 353 $result = $this->query($sql, $userdata); 354 355 $groups = array($conf['defaultgroup']); // always add default config 356 if($result) foreach($result as $row) { 357 if(!isset($row['group'])) continue; 358 $groups[] = $row['group']; 359 } 360 361 $groups = array_unique($groups); 362 sort($groups); 363 return $groups; 364 } 365 366 /** 367 * Executes a query 368 * 369 * @param string $sql The SQL statement to execute 370 * @param array $arguments Named parameters to be used in the statement 371 * @return array|bool The result as associative array 372 */ 373 protected function query($sql, $arguments) { 374 // prepare parameters - we only use those that exist in the SQL 375 $params = array(); 376 foreach($arguments as $key => $value) { 377 if(is_array($value)) continue; 378 if(is_object($value)) continue; 379 if($key[0] != ':') $key = ":$key"; // prefix with colon if needed 380 if(strpos($sql, $key) !== false) $params[$key] = $value; 381 } 382 383 // execute 384 try { 385 $sth = $this->pdo->prepare($sql); 386 $sth->execute($params); 387 $result = $sth->fetchAll(); 388 if((int) $sth->errorCode()) { 389 $this->_debug(join(' ',$sth->errorInfo()), -1, __LINE__); 390 $result = false; 391 } 392 $sth->closeCursor(); 393 $sth = null; 394 } catch(PDOException $e) { 395 $this->_debug($e); 396 $result = false; 397 } 398 return $result; 399 } 400 401 402 /** 403 * Wrapper around msg() but outputs only when debug is enabled 404 * 405 * @param string|Exception $message 406 * @param int $err 407 * @param int $line 408 */ 409 protected function _debug($message, $err = 0, $line = 0) { 410 if(!$this->getConf('debug')) return; 411 if(is_a($message, 'Exception')) { 412 $err = -1; 413 $line = $message->getLine(); 414 $msg = $message->getMessage(); 415 } else { 416 $msg = $message; 417 } 418 419 if(defined('DOKU_UNITTEST')) { 420 printf("\n%s, %s:%d\n", $msg, __FILE__, $line); 421 } else { 422 msg('authpdo: ' . $msg, $err, $line, __FILE__); 423 } 424 } 425} 426 427// vim:ts=4:sw=4:et: 428