1f4476bd9SJan Schumann<?php 2f4476bd9SJan Schumann// must be run within Dokuwiki 3f4476bd9SJan Schumannif(!defined('DOKU_INC')) die(); 4f4476bd9SJan Schumann 5f4476bd9SJan Schumann/** 6f4476bd9SJan Schumann * Plaintext authentication backend 7f4476bd9SJan Schumann * 8f4476bd9SJan Schumann * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 9f4476bd9SJan Schumann * @author Andreas Gohr <andi@splitbrain.org> 10f4476bd9SJan Schumann * @author Chris Smith <chris@jalakai.co.uk> 11f4476bd9SJan Schumann * @author Jan Schumann <js@schumann-it.com> 12f4476bd9SJan Schumann */ 1393a7873eSAndreas Gohrclass auth_plugin_authplain extends DokuWiki_Auth_Plugin { 14*311f4603SAndreas Gohr /** @var array user cache */ 15*311f4603SAndreas Gohr protected $users = null; 16*311f4603SAndreas Gohr 17*311f4603SAndreas Gohr /** @var array filter pattern */ 18*311f4603SAndreas Gohr protected $_pattern = array(); 19f4476bd9SJan Schumann 20f4476bd9SJan Schumann /** 21f4476bd9SJan Schumann * Constructor 22f4476bd9SJan Schumann * 23f4476bd9SJan Schumann * Carry out sanity checks to ensure the object is 24f4476bd9SJan Schumann * able to operate. Set capabilities. 25f4476bd9SJan Schumann * 26f4476bd9SJan Schumann * @author Christopher Smith <chris@jalakai.co.uk> 27f4476bd9SJan Schumann */ 28*311f4603SAndreas Gohr public function __construct() { 29454d868bSAndreas Gohr parent::__construct(); 30f4476bd9SJan Schumann global $config_cascade; 31f4476bd9SJan Schumann 32f4476bd9SJan Schumann if(!@is_readable($config_cascade['plainauth.users']['default'])) { 33f4476bd9SJan Schumann $this->success = false; 34f4476bd9SJan Schumann } else { 35f4476bd9SJan Schumann if(@is_writable($config_cascade['plainauth.users']['default'])) { 36f4476bd9SJan Schumann $this->cando['addUser'] = true; 37f4476bd9SJan Schumann $this->cando['delUser'] = true; 38f4476bd9SJan Schumann $this->cando['modLogin'] = true; 39f4476bd9SJan Schumann $this->cando['modPass'] = true; 40f4476bd9SJan Schumann $this->cando['modName'] = true; 41f4476bd9SJan Schumann $this->cando['modMail'] = true; 42f4476bd9SJan Schumann $this->cando['modGroups'] = true; 43f4476bd9SJan Schumann } 44f4476bd9SJan Schumann $this->cando['getUsers'] = true; 45f4476bd9SJan Schumann $this->cando['getUserCount'] = true; 46f4476bd9SJan Schumann } 47f4476bd9SJan Schumann } 48f4476bd9SJan Schumann 49f4476bd9SJan Schumann /** 50*311f4603SAndreas Gohr * Check user+password 51f4476bd9SJan Schumann * 52f4476bd9SJan Schumann * Checks if the given user exists and the given 53f4476bd9SJan Schumann * plaintext password is correct 54f4476bd9SJan Schumann * 55f4476bd9SJan Schumann * @author Andreas Gohr <andi@splitbrain.org> 56*311f4603SAndreas Gohr * @param string $user 57*311f4603SAndreas Gohr * @param string $pass 58f4476bd9SJan Schumann * @return bool 59f4476bd9SJan Schumann */ 60*311f4603SAndreas Gohr public function checkPass($user, $pass) { 61f4476bd9SJan Schumann $userinfo = $this->getUserData($user); 62f4476bd9SJan Schumann if($userinfo === false) return false; 63f4476bd9SJan Schumann 64f4476bd9SJan Schumann return auth_verifyPassword($pass, $this->users[$user]['pass']); 65f4476bd9SJan Schumann } 66f4476bd9SJan Schumann 67f4476bd9SJan Schumann /** 68f4476bd9SJan Schumann * Return user info 69f4476bd9SJan Schumann * 70f4476bd9SJan Schumann * Returns info about the given user needs to contain 71f4476bd9SJan Schumann * at least these fields: 72f4476bd9SJan Schumann * 73f4476bd9SJan Schumann * name string full name of the user 74f4476bd9SJan Schumann * mail string email addres of the user 75f4476bd9SJan Schumann * grps array list of groups the user is in 76f4476bd9SJan Schumann * 77f4476bd9SJan Schumann * @author Andreas Gohr <andi@splitbrain.org> 78*311f4603SAndreas Gohr * @param string $user 79*311f4603SAndreas Gohr * @return array|bool 80f4476bd9SJan Schumann */ 81*311f4603SAndreas Gohr public function getUserData($user) { 82f4476bd9SJan Schumann if($this->users === null) $this->_loadUserData(); 83f4476bd9SJan Schumann return isset($this->users[$user]) ? $this->users[$user] : false; 84f4476bd9SJan Schumann } 85f4476bd9SJan Schumann 86f4476bd9SJan Schumann /** 87f4476bd9SJan Schumann * Create a new User 88f4476bd9SJan Schumann * 89f4476bd9SJan Schumann * Returns false if the user already exists, null when an error 90f4476bd9SJan Schumann * occurred and true if everything went well. 91f4476bd9SJan Schumann * 92f4476bd9SJan Schumann * The new user will be added to the default group by this 93f4476bd9SJan Schumann * function if grps are not specified (default behaviour). 94f4476bd9SJan Schumann * 95f4476bd9SJan Schumann * @author Andreas Gohr <andi@splitbrain.org> 96f4476bd9SJan Schumann * @author Chris Smith <chris@jalakai.co.uk> 97*311f4603SAndreas Gohr * 98*311f4603SAndreas Gohr * @param string $user 99*311f4603SAndreas Gohr * @param string $pwd 100*311f4603SAndreas Gohr * @param string $name 101*311f4603SAndreas Gohr * @param string $mail 102*311f4603SAndreas Gohr * @param array $grps 103*311f4603SAndreas Gohr * @return bool|null|string 104f4476bd9SJan Schumann */ 105*311f4603SAndreas Gohr public function createUser($user, $pwd, $name, $mail, $grps = null) { 106f4476bd9SJan Schumann global $conf; 107f4476bd9SJan Schumann global $config_cascade; 108f4476bd9SJan Schumann 109f4476bd9SJan Schumann // user mustn't already exist 110f4476bd9SJan Schumann if($this->getUserData($user) !== false) return false; 111f4476bd9SJan Schumann 112f4476bd9SJan Schumann $pass = auth_cryptPassword($pwd); 113f4476bd9SJan Schumann 114f4476bd9SJan Schumann // set default group if no groups specified 115f4476bd9SJan Schumann if(!is_array($grps)) $grps = array($conf['defaultgroup']); 116f4476bd9SJan Schumann 117f4476bd9SJan Schumann // prepare user line 118f4476bd9SJan Schumann $groups = join(',', $grps); 119f4476bd9SJan Schumann $userline = join(':', array($user, $pass, $name, $mail, $groups))."\n"; 120f4476bd9SJan Schumann 121f4476bd9SJan Schumann if(io_saveFile($config_cascade['plainauth.users']['default'], $userline, true)) { 122f4476bd9SJan Schumann $this->users[$user] = compact('pass', 'name', 'mail', 'grps'); 123f4476bd9SJan Schumann return $pwd; 124f4476bd9SJan Schumann } 125f4476bd9SJan Schumann 126*311f4603SAndreas Gohr msg( 127*311f4603SAndreas Gohr 'The '.$config_cascade['plainauth.users']['default']. 128*311f4603SAndreas Gohr ' file is not writable. Please inform the Wiki-Admin', -1 129*311f4603SAndreas Gohr ); 130f4476bd9SJan Schumann return null; 131f4476bd9SJan Schumann } 132f4476bd9SJan Schumann 133f4476bd9SJan Schumann /** 134f4476bd9SJan Schumann * Modify user data 135f4476bd9SJan Schumann * 136f4476bd9SJan Schumann * @author Chris Smith <chris@jalakai.co.uk> 137*311f4603SAndreas Gohr * @param string $user nick of the user to be changed 138*311f4603SAndreas Gohr * @param array $changes array of field/value pairs to be changed (password will be clear text) 139f4476bd9SJan Schumann * @return bool 140f4476bd9SJan Schumann */ 141*311f4603SAndreas Gohr public function modifyUser($user, $changes) { 142f4476bd9SJan Schumann global $ACT; 143f4476bd9SJan Schumann global $config_cascade; 144f4476bd9SJan Schumann 145f4476bd9SJan Schumann // sanity checks, user must already exist and there must be something to change 146f4476bd9SJan Schumann if(($userinfo = $this->getUserData($user)) === false) return false; 147f4476bd9SJan Schumann if(!is_array($changes) || !count($changes)) return true; 148f4476bd9SJan Schumann 149f4476bd9SJan Schumann // update userinfo with new data, remembering to encrypt any password 150f4476bd9SJan Schumann $newuser = $user; 151f4476bd9SJan Schumann foreach($changes as $field => $value) { 152f4476bd9SJan Schumann if($field == 'user') { 153f4476bd9SJan Schumann $newuser = $value; 154f4476bd9SJan Schumann continue; 155f4476bd9SJan Schumann } 156f4476bd9SJan Schumann if($field == 'pass') $value = auth_cryptPassword($value); 157f4476bd9SJan Schumann $userinfo[$field] = $value; 158f4476bd9SJan Schumann } 159f4476bd9SJan Schumann 160f4476bd9SJan Schumann $groups = join(',', $userinfo['grps']); 161f4476bd9SJan Schumann $userline = join(':', array($newuser, $userinfo['pass'], $userinfo['name'], $userinfo['mail'], $groups))."\n"; 162f4476bd9SJan Schumann 163f4476bd9SJan Schumann if(!$this->deleteUsers(array($user))) { 164f4476bd9SJan Schumann msg('Unable to modify user data. Please inform the Wiki-Admin', -1); 165f4476bd9SJan Schumann return false; 166f4476bd9SJan Schumann } 167f4476bd9SJan Schumann 168f4476bd9SJan Schumann if(!io_saveFile($config_cascade['plainauth.users']['default'], $userline, true)) { 169f4476bd9SJan Schumann msg('There was an error modifying your user data. You should register again.', -1); 170f4476bd9SJan Schumann // FIXME, user has been deleted but not recreated, should force a logout and redirect to login page 171*311f4603SAndreas Gohr $ACT = 'register'; 172f4476bd9SJan Schumann return false; 173f4476bd9SJan Schumann } 174f4476bd9SJan Schumann 175f4476bd9SJan Schumann $this->users[$newuser] = $userinfo; 176f4476bd9SJan Schumann return true; 177f4476bd9SJan Schumann } 178f4476bd9SJan Schumann 179f4476bd9SJan Schumann /** 180f4476bd9SJan Schumann * Remove one or more users from the list of registered users 181f4476bd9SJan Schumann * 182f4476bd9SJan Schumann * @author Christopher Smith <chris@jalakai.co.uk> 183f4476bd9SJan Schumann * @param array $users array of users to be deleted 184f4476bd9SJan Schumann * @return int the number of users deleted 185f4476bd9SJan Schumann */ 186*311f4603SAndreas Gohr public function deleteUsers($users) { 187f4476bd9SJan Schumann global $config_cascade; 188f4476bd9SJan Schumann 189f4476bd9SJan Schumann if(!is_array($users) || empty($users)) return 0; 190f4476bd9SJan Schumann 191f4476bd9SJan Schumann if($this->users === null) $this->_loadUserData(); 192f4476bd9SJan Schumann 193f4476bd9SJan Schumann $deleted = array(); 194f4476bd9SJan Schumann foreach($users as $user) { 195f4476bd9SJan Schumann if(isset($this->users[$user])) $deleted[] = preg_quote($user, '/'); 196f4476bd9SJan Schumann } 197f4476bd9SJan Schumann 198f4476bd9SJan Schumann if(empty($deleted)) return 0; 199f4476bd9SJan Schumann 200f4476bd9SJan Schumann $pattern = '/^('.join('|', $deleted).'):/'; 201f4476bd9SJan Schumann 202f4476bd9SJan Schumann if(io_deleteFromFile($config_cascade['plainauth.users']['default'], $pattern, true)) { 203f4476bd9SJan Schumann foreach($deleted as $user) unset($this->users[$user]); 204f4476bd9SJan Schumann return count($deleted); 205f4476bd9SJan Schumann } 206f4476bd9SJan Schumann 207f4476bd9SJan Schumann // problem deleting, reload the user list and count the difference 208f4476bd9SJan Schumann $count = count($this->users); 209f4476bd9SJan Schumann $this->_loadUserData(); 210f4476bd9SJan Schumann $count -= count($this->users); 211f4476bd9SJan Schumann return $count; 212f4476bd9SJan Schumann } 213f4476bd9SJan Schumann 214f4476bd9SJan Schumann /** 215f4476bd9SJan Schumann * Return a count of the number of user which meet $filter criteria 216f4476bd9SJan Schumann * 217f4476bd9SJan Schumann * @author Chris Smith <chris@jalakai.co.uk> 218*311f4603SAndreas Gohr * 219*311f4603SAndreas Gohr * @param array $filter 220*311f4603SAndreas Gohr * @return int 221f4476bd9SJan Schumann */ 222*311f4603SAndreas Gohr public function getUserCount($filter = array()) { 223f4476bd9SJan Schumann 224f4476bd9SJan Schumann if($this->users === null) $this->_loadUserData(); 225f4476bd9SJan Schumann 226f4476bd9SJan Schumann if(!count($filter)) return count($this->users); 227f4476bd9SJan Schumann 228f4476bd9SJan Schumann $count = 0; 229f4476bd9SJan Schumann $this->_constructPattern($filter); 230f4476bd9SJan Schumann 231f4476bd9SJan Schumann foreach($this->users as $user => $info) { 232f4476bd9SJan Schumann $count += $this->_filter($user, $info); 233f4476bd9SJan Schumann } 234f4476bd9SJan Schumann 235f4476bd9SJan Schumann return $count; 236f4476bd9SJan Schumann } 237f4476bd9SJan Schumann 238f4476bd9SJan Schumann /** 239f4476bd9SJan Schumann * Bulk retrieval of user data 240f4476bd9SJan Schumann * 241f4476bd9SJan Schumann * @author Chris Smith <chris@jalakai.co.uk> 242*311f4603SAndreas Gohr * 243*311f4603SAndreas Gohr * @param int $start index of first user to be returned 244*311f4603SAndreas Gohr * @param int $limit max number of users to be returned 245*311f4603SAndreas Gohr * @param array $filter array of field/pattern pairs 246*311f4603SAndreas Gohr * @return array userinfo (refer getUserData for internal userinfo details) 247f4476bd9SJan Schumann */ 248*311f4603SAndreas Gohr public function retrieveUsers($start = 0, $limit = 0, $filter = array()) { 249f4476bd9SJan Schumann 250f4476bd9SJan Schumann if($this->users === null) $this->_loadUserData(); 251f4476bd9SJan Schumann 252f4476bd9SJan Schumann ksort($this->users); 253f4476bd9SJan Schumann 254f4476bd9SJan Schumann $i = 0; 255f4476bd9SJan Schumann $count = 0; 256f4476bd9SJan Schumann $out = array(); 257f4476bd9SJan Schumann $this->_constructPattern($filter); 258f4476bd9SJan Schumann 259f4476bd9SJan Schumann foreach($this->users as $user => $info) { 260f4476bd9SJan Schumann if($this->_filter($user, $info)) { 261f4476bd9SJan Schumann if($i >= $start) { 262f4476bd9SJan Schumann $out[$user] = $info; 263f4476bd9SJan Schumann $count++; 264f4476bd9SJan Schumann if(($limit > 0) && ($count >= $limit)) break; 265f4476bd9SJan Schumann } 266f4476bd9SJan Schumann $i++; 267f4476bd9SJan Schumann } 268f4476bd9SJan Schumann } 269f4476bd9SJan Schumann 270f4476bd9SJan Schumann return $out; 271f4476bd9SJan Schumann } 272f4476bd9SJan Schumann 273f4476bd9SJan Schumann /** 274f4476bd9SJan Schumann * Only valid pageid's (no namespaces) for usernames 275*311f4603SAndreas Gohr * 276*311f4603SAndreas Gohr * @param string $user 277*311f4603SAndreas Gohr * @return string 278f4476bd9SJan Schumann */ 279*311f4603SAndreas Gohr public function cleanUser($user) { 280f4476bd9SJan Schumann global $conf; 281f4476bd9SJan Schumann return cleanID(str_replace(':', $conf['sepchar'], $user)); 282f4476bd9SJan Schumann } 283f4476bd9SJan Schumann 284f4476bd9SJan Schumann /** 285f4476bd9SJan Schumann * Only valid pageid's (no namespaces) for groupnames 286*311f4603SAndreas Gohr * 287*311f4603SAndreas Gohr * @param string $group 288*311f4603SAndreas Gohr * @return string 289f4476bd9SJan Schumann */ 290*311f4603SAndreas Gohr public function cleanGroup($group) { 291f4476bd9SJan Schumann global $conf; 292f4476bd9SJan Schumann return cleanID(str_replace(':', $conf['sepchar'], $group)); 293f4476bd9SJan Schumann } 294f4476bd9SJan Schumann 295f4476bd9SJan Schumann /** 296f4476bd9SJan Schumann * Load all user data 297f4476bd9SJan Schumann * 298f4476bd9SJan Schumann * loads the user file into a datastructure 299f4476bd9SJan Schumann * 300f4476bd9SJan Schumann * @author Andreas Gohr <andi@splitbrain.org> 301f4476bd9SJan Schumann */ 302*311f4603SAndreas Gohr protected function _loadUserData() { 303f4476bd9SJan Schumann global $config_cascade; 304f4476bd9SJan Schumann 305f4476bd9SJan Schumann $this->users = array(); 306f4476bd9SJan Schumann 307f4476bd9SJan Schumann if(!@file_exists($config_cascade['plainauth.users']['default'])) return; 308f4476bd9SJan Schumann 309f4476bd9SJan Schumann $lines = file($config_cascade['plainauth.users']['default']); 310f4476bd9SJan Schumann foreach($lines as $line) { 311f4476bd9SJan Schumann $line = preg_replace('/#.*$/', '', $line); //ignore comments 312f4476bd9SJan Schumann $line = trim($line); 313f4476bd9SJan Schumann if(empty($line)) continue; 314f4476bd9SJan Schumann 315f4476bd9SJan Schumann $row = explode(":", $line, 5); 316f4476bd9SJan Schumann $groups = array_values(array_filter(explode(",", $row[4]))); 317f4476bd9SJan Schumann 318f4476bd9SJan Schumann $this->users[$row[0]]['pass'] = $row[1]; 319f4476bd9SJan Schumann $this->users[$row[0]]['name'] = urldecode($row[2]); 320f4476bd9SJan Schumann $this->users[$row[0]]['mail'] = $row[3]; 321f4476bd9SJan Schumann $this->users[$row[0]]['grps'] = $groups; 322f4476bd9SJan Schumann } 323f4476bd9SJan Schumann } 324f4476bd9SJan Schumann 325f4476bd9SJan Schumann /** 326*311f4603SAndreas Gohr * return true if $user + $info match $filter criteria, false otherwise 327f4476bd9SJan Schumann * 328f4476bd9SJan Schumann * @author Chris Smith <chris@jalakai.co.uk> 329*311f4603SAndreas Gohr * 330*311f4603SAndreas Gohr * @param string $user User login 331*311f4603SAndreas Gohr * @param array $info User's userinfo array 332*311f4603SAndreas Gohr * @return bool 333f4476bd9SJan Schumann */ 334*311f4603SAndreas Gohr protected function _filter($user, $info) { 335f4476bd9SJan Schumann foreach($this->_pattern as $item => $pattern) { 336f4476bd9SJan Schumann if($item == 'user') { 337*311f4603SAndreas Gohr if(!preg_match($pattern, $user)) return false; 338f4476bd9SJan Schumann } else if($item == 'grps') { 339*311f4603SAndreas Gohr if(!count(preg_grep($pattern, $info['grps']))) return false; 340f4476bd9SJan Schumann } else { 341*311f4603SAndreas Gohr if(!preg_match($pattern, $info[$item])) return false; 342f4476bd9SJan Schumann } 343f4476bd9SJan Schumann } 344*311f4603SAndreas Gohr return true; 345f4476bd9SJan Schumann } 346f4476bd9SJan Schumann 347*311f4603SAndreas Gohr /** 348*311f4603SAndreas Gohr * construct a filter pattern 349*311f4603SAndreas Gohr * 350*311f4603SAndreas Gohr * @param array $filter 351*311f4603SAndreas Gohr */ 352*311f4603SAndreas Gohr protected function _constructPattern($filter) { 353f4476bd9SJan Schumann $this->_pattern = array(); 354f4476bd9SJan Schumann foreach($filter as $item => $pattern) { 355f4476bd9SJan Schumann $this->_pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters 356f4476bd9SJan Schumann } 357f4476bd9SJan Schumann } 358f4476bd9SJan Schumann}