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