1<?php 2 3/** 4 * DokuWiki Plugin authsaml (SAML class) 5 * 6 * @author Sixto Martin <sixto.martin.garcia@gmail.com> 7 * @author Andreas Aakre Solberg, UNINETT, http://www.uninett.no 8 * @author François Kooman 9 * @author Thijs Kinkhorst, Universiteit van Tilburg / SURFnet bv 10 * @author Jorge Hervás <jordihv@gmail.com>, Lukas Slansky <lukas.slansky@upce.cz> 11 12 * @license GPL2 http://www.gnu.org/licenses/gpl.html 13 * @link https://github.com/pitbulk/dokuwiki-saml 14 */ 15 16class saml_handler { 17 18 protected $simplesaml_path; 19 protected $simplesaml_authsource; 20 protected $simplesaml_uid; 21 protected $simplesaml_mail; 22 protected $simplesaml_name; 23 protected $simplesaml_grps; 24 protected $defaultgroup; 25 26 protected $force_saml_login; 27 28 protected $use_internal_user_store; 29 protected $saml_user_file; 30 protected $users; 31 32 public $ssp = null; 33 protected $attributes = array(); 34 35 36 public function __construct($plugin_conf) 37 { 38 global $auth, $conf; 39 40 $this->defaultgroup = $conf['defaultgroup']; 41 42 $this->simplesaml_path = $plugin_conf['simplesaml_path']; 43 $this->simplesaml_authsource = $plugin_conf['simplesaml_authsource']; 44 $this->simplesaml_uid = $plugin_conf['simplesaml_uid']; 45 $this->simplesaml_mail = $plugin_conf['simplesaml_mail']; 46 $this->simplesaml_name = $plugin_conf['simplesaml_name']; 47 $this->simplesaml_grps = $plugin_conf['simplesaml_grps']; 48 49 $auth_saml_active = isset($conf['authtype']) && $conf['authtype'] == 'authsaml'; 50 51 // Store users in files if we chose authsaml as authtype (due we cant use internal store) or if 52 // other auth is active and the 'use_internal_user_store' param in the plugin configuration file is false 53 $this->use_internal_user_store = !$auth_saml_active && $plugin_conf['use_internal_user_store']; 54 55 // Force redirection to the IdP if we chose authsaml as authtype or if we configured as true the 'force_saml_login' 56 $force_saml_login = $auth_saml_active || $plugin_conf['force_saml_login']; 57 58 $this->saml_user_file = $conf['savedir'] . '/users.saml.php'; 59 } 60 61 /** 62 * Get a simplesamlphp auth instance (initiate it when it does not exist) 63 */ 64 public function get_ssp_instance() 65 { 66 if ($this->ssp == null) { 67 include_once($this->simplesaml_path.'/lib/_autoload.php'); 68 $this->ssp = new SimpleSAML_Auth_Simple($this->simplesaml_authsource); 69 } 70 return $this->ssp; 71 } 72 73 /** 74 * Get user data 75 * 76 * @return string|null 77 */ 78 public function getUserData($user) { 79 if ($this->use_internal_user_store) { 80 global $auth; 81 return $auth->getUserData($user); 82 } 83 else { 84 return $this->getFILEUserData($user); 85 } 86 } 87 88 public function checkPass($user) { 89 $ssp = $this->get_ssp_instance(); 90 if ($ssp->isAuthenticated()) { 91 if ($user == $this->getUsername()) { 92 return true; 93 } 94 } 95 return false; 96 } 97 98 public function slo() 99 { 100 if ($this->ssp->isAuthenticated()) { 101 $this->ssp->logout(); 102 } 103 if ($this->use_internal_user_store) { 104 auth_logoff(); 105 } 106 } 107 108 public function getUsername() 109 { 110 $attributes = $this->ssp->getAttributes(); 111 return $attributes[$this->simplesaml_uid][0]; 112 } 113 114 115 /** 116 * Get user data from the SAML assertion 117 * 118 * @return array|false 119 */ 120 public function getSAMLUserData() 121 { 122 $this->attributes = $this->ssp->getAttributes(); 123 124 if (!empty($this->attributes)) { 125 if (!array_key_exists($this->simplesaml_name , $this->attributes)) { 126 $name = ""; 127 } else { 128 $name = $this->attributes[$this->simplesaml_name][0]; 129 } 130 131 if (!array_key_exists($this->simplesaml_mail , $this->attributes)) { 132 $mail = ""; 133 } else { 134 $mail = $this->attributes[$this->simplesaml_mail][0]; 135 } 136 137 if (!array_key_exists($this->simplesaml_grps, $this->attributes) || 138 empty($this->attributes[$this->simplesaml_grps])) { 139 $grps = array(); 140 } else { 141 $grps = $this->attributes[$this->simplesaml_grps]; 142 } 143 144 return array( 145 'name' => $name, 146 'mail' => $mail, 147 'grps' => $grps 148 ); 149 } 150 return false; 151 } 152 153 /** 154 * Get user data from the saml store user file 155 * 156 * @return array|false 157 */ 158 public function getFILEUserData($user) 159 { 160 if($this->users === null) $this->_loadUserData(); 161 return isset($this->users[$user]) ? $this->users[$user] : false; 162 } 163 164 function login($username) 165 { 166 global $conf, $USERINFO; 167 168 $ssp = $this->get_ssp_instance(); 169 $this->attributes = $ssp->getAttributes(); 170 171 172 if ($ssp->isAuthenticated() && !empty($this->attributes)) { 173 174 $_SERVER['REMOTE_USER'] = $username; 175 176 $userData = $this->getUserData($username); 177 178 $USERINFO['name'] = $userData['name']; 179 $USERINFO['mail'] = $userData['mail']; 180 $USERINFO['grps'] = $userData['grps']; 181 182 // set cookie 183 $cookie = base64_encode($username).'|'.((int) false).'|'.base64_encode($pass); 184 $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir']; 185 $time = $sticky ? (time() + 60 * 60 * 24 * 365) : 0; //one year 186 if(version_compare(PHP_VERSION, '5.2.0', '>')) { 187 setcookie(DOKU_COOKIE, $cookie, $time, $cookieDir, '', ($conf['securecookie'] && is_ssl()), true); 188 } else { 189 setcookie(DOKU_COOKIE, $cookie, $time, $cookieDir, '', ($conf['securecookie'] && is_ssl())); 190 } 191 // set session 192 $_SESSION[DOKU_COOKIE]['auth']['user'] = $username; 193 $_SESSION[DOKU_COOKIE]['auth']['pass'] = sha1(auth_pwgen()); 194 $_SESSION[DOKU_COOKIE]['auth']['buid'] = auth_browseruid(); 195 $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO; 196 $_SESSION[DOKU_COOKIE]['auth']['time'] = time(); 197 } 198 } 199 200 function register_user($username) { 201 global $auth; 202 $user = $username; 203 $pass = auth_pwgen(); 204 205 $userData = $this->getSAMLUserData(); 206 207 if ($this->use_internal_user_store) { 208 global $auth; 209 if ($auth->canDo('addUser')) { 210 if (empty($userData['grps'])) { 211 $userData['grps'] = array($this->defaultgroup); 212 } 213 return $auth->createUser($user, $pass, $userData['name'], $userData['mail'], $userData['grps']); 214 } 215 else { 216 return false; 217 } 218 } 219 else { 220 return $this->_saveUserData($username, $userData); 221 } 222 } 223 224 function update_user($username) { 225 global $auth, $conf; 226 227 $changes = array(); 228 $userData = $this->getSAMLUserData(); 229 230 if ($auth->canDo('modName')) { 231 if(!empty($userData['name'])) { 232 $changes['name'] = $userData['name']; 233 } 234 } 235 if ($auth->canDo('modMail')) { 236 if(!empty($userData['mail'])) { 237 $changes['mail'] = $userData['mail']; 238 } 239 } 240 if ($auth->canDo('modGroups')) { 241 if(!empty($userData['grps'])) { 242 $changes['grps'] = $userData['grps']; 243 } 244 } 245 246 if (!empty($changes)) { 247 if ($this->use_internal_user_store) { 248 $auth->modifyUser($username, $changes); 249 } 250 else { 251 $this->modifyUser($username, $changes); 252 } 253 } 254 } 255 256 function delete_user($users) { 257 if ($this->use_internal_user_store) { 258 global $auth; 259 return $auth->deleteUser($users); 260 } 261 else { 262 return $this->deleteUsers($users); 263 } 264 } 265 266 /** 267 * Load all user data (modified copy from plain.class.php) 268 * 269 * loads the user file into a datastructure 270 * 271 * @author Lukas Slansky <lukas.slansky@upce.cz> 272 */ 273 function _loadUserData() { 274 global $conf; 275 276 $this->users = array(); 277 278 if(!@file_exists($this->saml_user_file)) 279 return; 280 281 $lines = file($this->saml_user_file); 282 foreach($lines as $line){ 283 $line = preg_replace('/#.*$/','',$line); //ignore comments 284 $line = trim($line); 285 if(empty($line)) continue; 286 287 $row = explode(":",$line,5); 288 $groups = array_map('urldecode', array_values(array_filter(explode(",",$row[3])))); 289 290 $this->users[$row[0]]['name'] = urldecode($row[1]); 291 $this->users[$row[0]]['mail'] = $row[2]; 292 $this->users[$row[0]]['grps'] = $groups; 293 } 294 } 295 296 /** 297 * Save user data 298 * 299 * saves the user file into a datastructure 300 * 301 * @author Lukas Slansky <lukas.slansky@upce.cz> 302 */ 303 function _saveUserData($username, $userData) { 304 global $conf; 305 306 $pattern = '/^' . $username . ':/'; 307 308 // Delete old line from users file 309 if (!io_deleteFromFile($this->saml_user_file, $pattern, true)) { 310 msg('Error saving user data (1)', -1); 311 return false; 312 } 313 if ($userData['grps'] == null) { 314 $userData['grps'] = array(); 315 } 316 317 $groups = join(',',array_map('urlencode',$userData['grps'])); 318 $userline = join(':',array($username, $userData['name'], $userData['mail'], $groups))."\n"; 319 // Save new line into users file 320 if (!io_saveFile($this->saml_user_file, $userline, true)) { 321 msg('Error saving user data (2)', -1); 322 return false; 323 } 324 $this->users[$username] = $userinfo; 325 return true; 326 } 327 328 public function deleteUsers($users) { 329 330 if(!is_array($users) || empty($users)) return 0; 331 332 if($this->users === null) $this->_loadUserData(); 333 334 $deleted = array(); 335 foreach($users as $user) { 336 if(isset($this->users[$user])) $deleted[] = preg_quote($user, '/'); 337 } 338 339 if(empty($deleted)) return 0; 340 341 $pattern = '/^('.join('|', $deleted).'):/'; 342 343 if(io_deleteFromFile($this->saml_user_file, $pattern, true)) { 344 foreach($deleted as $user) unset($this->users[$user]); 345 return count($deleted); 346 } 347 348 // problem deleting, reload the user list and count the difference 349 $count = count($this->users); 350 $this->_loadUserData(); 351 $count -= count($this->users); 352 return $count; 353 } 354 355 public function modifyUser($username, $changes) { 356 global $ACT; 357 358 // sanity checks, user must already exist and there must be something to change 359 if(($userinfo = $this->getFILEUserData($username)) === false) return false; 360 if(!is_array($changes) || !count($changes)) return true; 361 362 // update userinfo with new data 363 $newuser = $username; 364 foreach($changes as $field => $value) { 365 if($field == 'user') { 366 $newuser = $value; 367 continue; 368 } 369 $userinfo[$field] = $value; 370 } 371 372 $groups = join(',', $userinfo['grps']); 373 $groups = array_map('urlencode', $groups); 374 375 $userline = join(':', array($newuser, $userinfo['name'], $userinfo['mail'], $groups))."\n"; 376 377 if(!$this->deleteUsers(array($username))) { 378 msg('Unable to modify user data. Please inform the Wiki-Admin', -1); 379 return false; 380 } 381 382 if(!io_saveFile($this->saml_user_file, $userline, true)) { 383 msg('There was an error modifying your user data. You should register again.', -1); 384 // FIXME, user has been deleted but not recreated, should force a logout and redirect to login page 385 $ACT = 'register'; 386 return false; 387 } 388 389 $this->users[$newuser] = $userinfo; 390 return true; 391 } 392 393 394 395} 396 397 398 399