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