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