1<?php 2/** 3 * DokuWiki Plugin evesso (Auth Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8 9// must be run within Dokuwiki 10if(!defined('DOKU_INC')) die(); 11 12class auth_plugin_evesso extends auth_plugin_authplain { 13 14 /** 15 * Constructor 16 * 17 * Sets capabilities. 18 */ 19 public function __construct() { 20 parent::__construct(); 21 22 $this->cando['external'] = true; 23 } 24 25 /** 26 * Handle the login 27 * 28 * This either trusts the session data (if any), processes the second oAuth step or simply 29 * executes a normal plugin against local users. 30 * 31 * @param string $user 32 * @param string $pass 33 * @param bool $sticky 34 * @return bool 35 */ 36 function trustExternal($user, $pass, $sticky = false) { 37 global $USERINFO, $INPUT; 38 39 // check session for existing oAuth login data 40 if(isset($_SESSION[DOKU_COOKIE]['auth']['oauth'])) { 41 $session = $_SESSION[DOKU_COOKIE]['auth']; 42 $servicename = $session['oauth']; 43 // check if session data is still considered valid 44 if ($this->isSessionValid($session)) { 45 $_SERVER['REMOTE_USER'] = $session['user']; 46 $USERINFO = $session['info']; 47 return true; 48 } 49 } 50 51 $existingLoginProcess = false; 52 // are we in login progress? 53 if(isset($_SESSION[DOKU_COOKIE]['oauth-inprogress'])) { 54 $servicename = $_SESSION[DOKU_COOKIE]['oauth-inprogress']['service']; 55 $page = $_SESSION[DOKU_COOKIE]['oauth-inprogress']['id']; 56 if (isset($_SESSION[DOKU_COOKIE]['oauth-inprogress']['params'])) { 57 $params = $_SESSION[DOKU_COOKIE]['oauth-inprogress']['params']; 58 } else { 59 $params = array(); 60 } 61 unset($_SESSION[DOKU_COOKIE]['oauth-inprogress']); 62 $existingLoginProcess = true; 63 } 64 65 // either we're in oauth login or a previous log needs to be rechecked 66 if(isset($servicename)) { 67 /** @var helper_plugin_evesso $hlp */ 68 $hlp = plugin_load('helper', 'evesso'); 69 70 /** @var OAuth\Plugin\AbstractAdapter $service */ 71 $service = $hlp->loadService($servicename); 72 if(is_null($service)) { 73 $this->cleanLogout(); 74 return false; 75 } 76 77 if($service->checkToken()) { 78 $ok = $this->processLogin($sticky, $hlp, $service, $servicename, $page, $params); 79 if (!$ok) { 80 $this->cleanLogout(); 81 return false; 82 } 83 return true; 84 } else { 85 if ($existingLoginProcess) { 86 msg($this->getLang('loginFailed'), -1); 87 $this->cleanLogout(); 88 return false; 89 } else { 90 // first time here 91 $this->relogin($servicename); 92 } 93 } 94 95 $this->cleanLogout(); 96 return false; // something went wrong during oAuth login 97 } elseif (isset($_COOKIE[DOKU_COOKIE])) { 98 global $INPUT; 99 //try cookie 100 $cookie = explode('|', $_COOKIE[DOKU_COOKIE]); 101 if (isset($cookie[2]) && isset($cookie[3])) { 102 $auth = base64_decode($cookie[2], true); 103 $servicename = base64_decode($cookie[3], true); 104 if ($auth === 'oauth') { 105 $this->relogin($servicename); 106 } 107 } 108 } 109 110 // do the "normal" plain auth login via form 111 return auth_login($user, $pass, $sticky); 112 } 113 114 /** 115 * @param array $session cookie auth session 116 * 117 * @return bool 118 */ 119 protected function isSessionValid ($session) { 120 /** @var helper_plugin_evesso $hlp */ 121 $hlp = plugin_load('helper', 'evesso'); 122 if ($hlp->validBrowserID($session)) { 123 if (!$hlp->isSessionTimedOut($session)) { 124 return true; 125 } elseif (!($hlp->isGETRequest() && $hlp->isDokuPHP())) { 126 // only force a recheck on a timed-out session during a GET request on the main script doku.php 127 return true; 128 } 129 } 130 return false; 131 } 132 133 protected function relogin($servicename) { 134 global $INPUT; 135 136 /** @var helper_plugin_evesso $hlp */ 137 $hlp = plugin_load('helper', 'evesso'); 138 $service = $hlp->loadService($servicename); 139 if(is_null($service)) return false; 140 141 // remember service in session 142 session_start(); 143 $_SESSION[DOKU_COOKIE]['oauth-inprogress']['service'] = $servicename; 144 $_SESSION[DOKU_COOKIE]['oauth-inprogress']['id'] = $INPUT->str('id'); 145 $_SESSION[DOKU_COOKIE]['oauth-inprogress']['params'] = $_GET; 146 147 $_SESSION[DOKU_COOKIE]['oauth-done']['$_REQUEST'] = $_REQUEST; 148 149 if (is_array($INPUT->post->param('do'))) { 150 $doPost = key($INPUT->post->arr('do')); 151 } else { 152 $doPost = $INPUT->post->str('do'); 153 } 154 $doGet = $INPUT->get->str('do'); 155 if (!empty($doPost)) { 156 $_SESSION[DOKU_COOKIE]['oauth-done']['do'] = $doPost; 157 } elseif (!empty($doGet)) { 158 $_SESSION[DOKU_COOKIE]['oauth-done']['do'] = $doGet; 159 } 160 161 session_write_close(); 162 163 $service->login(); 164 } 165 166 /** 167 * @param $sticky 168 * @param OAuth\Plugin\AbstractAdapter $service 169 * @param string $servicename 170 * @param string $page 171 * @param array $params 172 * 173 * @return bool 174 */ 175 protected function processLogin($sticky, $hlp, $service, $servicename, $page, $params = array()) { 176 $uinfo = $service->getUser(); 177 $ok = $this->processUser($uinfo, $hlp, $servicename); 178 if(!$ok) { 179 return false; 180 } 181 $this->setUserSession($uinfo, $servicename); 182 $this->setUserCookie($uinfo['user'], $sticky, $servicename); 183 if(isset($page)) { 184 if(!empty($params['id'])) unset($params['id']); 185 send_redirect(wl($page, $params, false, '&')); 186 } 187 return true; 188 } 189 190 /** 191 * process the user and update the $uinfo array 192 * 193 * @param $uinfo 194 * @param $servicename 195 * 196 * @return bool 197 */ 198 protected function processUser(&$uinfo, $hlp, $servicename) { 199 $uinfo['user'] = $this->cleanUser((string) $uinfo['user']); 200 if(!$uinfo['name']) $uinfo['name'] = $uinfo['user']; 201 202 if(!$uinfo['user'] || !$uinfo['mail']) { 203 msg("$servicename did not provide the needed user info. Can't log you in", -1); 204 return false; 205 } 206 207 // see if the user is known already 208 $user = $this->getUserByEmail($uinfo['mail']); 209 if($user) { 210 $sinfo = $this->getUserData($user); 211 // check if the user allowed access via this service 212 if(!in_array($this->cleanServiceGroup($servicename), $sinfo['grps'])) { 213 msg(sprintf($this->getLang('authnotenabled'), $servicename), -1); 214 return false; 215 } 216 $uinfo['user'] = $user; 217 $uinfo['name'] = $sinfo['name']; 218 if ($this->haveEveGroups($uinfo['grps'])) { //Update groups 219 //Remove all eve groups 220 foreach($sinfo['grps'] as $group) { 221 if($this->startsWith($group, helper_plugin_evesso::CORPORATION_PREFIX) 222 || $this->startsWith($group, helper_plugin_evesso::ALLIANCE_PREFIX) 223 || $this->startsWith($group, helper_plugin_evesso::FACTION_PREFIX) 224 || $this->startsWith($group, "eve-") //Old Prefix 225 ) { 226 $idx = array_search($group, $sinfo['grps']); 227 if($idx !== false) unset($sinfo['grps'][$idx]); 228 } 229 } 230 //Merge groups 231 $uinfo['grps'] = array_unique(array_merge((array) $uinfo['grps'], $sinfo['grps'])); 232 } else { //Load groups 233 $uinfo['grps'] = $sinfo['grps']; 234 } 235 //Update groups 236 $this->modifyUser($user, array('grps' => $uinfo['grps'])); 237 //Check group 238 if (!$hlp->checkGroups($uinfo['grps'])) { 239 msg($this->getLang("rejectedGroup"), -1); 240 return false; 241 } 242 return true; //Existing valid user 243 } elseif(actionOK('register') || $this->getConf('register-on-auth')) { 244 //Check group before creation 245 if (!$hlp->checkGroups($uinfo['grps'])) { 246 msg($this->getLang("rejectedGroup"), -1); 247 return false; 248 } 249 //Add user 250 $ok = $this->addUser($uinfo, $servicename); 251 if(!$ok) { 252 msg('something went wrong creating your user account. please try again later.', -1); 253 return false; 254 } 255 return true; //New valid user 256 } 257 //Not existing valid user and can not create a new user 258 msg($this->getLang('addUserNotPossible'), -1); 259 return false; 260 } 261 262 private function haveEveGroups($groups) { 263 if (!isset($groups)) { 264 return false; 265 } 266 foreach($groups as $group) { 267 if($this->startsWith($group, helper_plugin_evesso::CORPORATION_PREFIX) 268 || $this->startsWith($group, helper_plugin_evesso::ALLIANCE_PREFIX) 269 || $this->startsWith($group, helper_plugin_evesso::FACTION_PREFIX) 270 || $this->startsWith($group, "eve-") //Old Prefix 271 ) { 272 return true; 273 } 274 } 275 return false; 276 } 277 278 private function startsWith($haystack, $needle) { 279 $length = strlen($needle); 280 return (substr($haystack, 0, $length) === $needle); 281 } 282 283 /** 284 * new user, create him - making sure the login is unique by adding a number if needed 285 * 286 * @param array $uinfo user info received from the oAuth service 287 * @param string $servicename 288 * 289 * @return bool 290 */ 291 protected function addUser(&$uinfo, $servicename) { 292 global $conf; 293 $user = $uinfo['user']; 294 $count = ''; 295 while($this->getUserData($user . $count)) { 296 if($count) { 297 $count++; 298 } else { 299 $count = 1; 300 } 301 } 302 $user = $user . $count; 303 $uinfo['user'] = $user; 304 $groups_on_creation = array(); 305 $groups_on_creation[] = $conf['defaultgroup']; 306 $groups_on_creation[] = $this->cleanServiceGroup($servicename); // add service as group 307 $uinfo['grps'] = array_merge((array) $uinfo['grps'], $groups_on_creation); 308 309 $ok = $this->triggerUserMod( 310 'create', 311 array($user, auth_pwgen($user), $uinfo['name'], $uinfo['mail'], $uinfo['grps'],) 312 ); 313 if(!$ok) { 314 return false; 315 } 316 317 // send notification about the new user 318 $subscription = new Subscription(); 319 $subscription->send_register($user, $uinfo['name'], $uinfo['mail']); 320 return true; 321 } 322 323 /** 324 * Find a user by his email address 325 * 326 * @param $mail 327 * @return bool|string 328 */ 329 protected function getUserByEmail($mail) { 330 if (method_exists($this, "loadUserData")) { 331 if($this->users === null) $this->loadUserData(); 332 } else { 333 if($this->users === null) $this->_loadUserData(); 334 } 335 336 $mail = strtolower($mail); 337 338 foreach($this->users as $user => $uinfo) { 339 if(strtolower($uinfo['mail']) == $mail) return $user; 340 } 341 342 return false; 343 } 344 345 /** 346 * @param array $data 347 * @param string $service 348 */ 349 protected function setUserSession($data, $service) { 350 global $USERINFO; 351 352 // set up groups 353 if(!is_array($data['grps'])) { 354 $data['grps'] = array(); 355 } 356 $data['grps'][] = $this->cleanServiceGroup($service); 357 $data['grps'] = array_unique($data['grps']); 358 359 $USERINFO = $data; 360 $_SERVER['REMOTE_USER'] = $data['user']; 361 $_SESSION[DOKU_COOKIE]['auth']['user'] = $data['user']; 362 if (isset($data['pass'])) { 363 $_SESSION[DOKU_COOKIE]['auth']['pass'] = $data['pass']; 364 } 365 $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO; 366 $_SESSION[DOKU_COOKIE]['auth']['buid'] = auth_browseruid(); 367 $_SESSION[DOKU_COOKIE]['auth']['time'] = time(); 368 $_SESSION[DOKU_COOKIE]['auth']['oauth'] = $service; 369 } 370 371 /** 372 * @param string $user 373 * @param bool $sticky 374 * @param string $servicename 375 * @param int $validityPeriodInSeconds optional, per default 1 Year 376 */ 377 private function setUserCookie($user, $sticky, $servicename, $validityPeriodInSeconds = 31536000) { 378 global $conf; 379 380 $cookie = base64_encode($user).'|'.((int) $sticky).'|'.base64_encode('oauth').'|'.base64_encode($servicename); 381 $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir']; 382 $time = $sticky ? (time() + $validityPeriodInSeconds) : 0; 383 setcookie(DOKU_COOKIE,$cookie, $time, $cookieDir, '',($conf['securecookie'] && is_ssl()), true); 384 } 385 386 /** 387 * Unset additional stuff in session on logout 388 */ 389 public function logOff() { 390 parent::logOff(); 391 392 $this->cleanLogout(); 393 } 394 395 /** 396 * unset auth cookies and session information 397 */ 398 private function cleanLogout() { 399 $hlp = plugin_load('helper', 'evesso'); 400 $service = $hlp->loadService(); 401 if (!is_null($service)) { 402 $service->logout(); 403 } 404 if(isset($_SESSION[DOKU_COOKIE]['auth'])) { 405 unset($_SESSION[DOKU_COOKIE]['auth']); 406 } 407 $this->setUserCookie('',true,'',-60); 408 } 409 410 /** 411 * Enhance function to check against duplicate emails 412 * 413 * @param string $user 414 * @param string $pwd 415 * @param string $name 416 * @param string $mail 417 * @param null $grps 418 * @return bool|null|string 419 */ 420 public function createUser($user, $pwd, $name, $mail, $grps = null) { 421 if($this->getUserByEmail($mail)) { 422 msg($this->getLang('emailduplicate'), -1); 423 return false; 424 } 425 426 return parent::createUser($user, $pwd, $name, $mail, $grps); 427 } 428 429 /** 430 * Enhance function to check against duplicate emails 431 * 432 * @param string $user 433 * @param array $changes 434 * @return bool 435 */ 436 public function modifyUser($user, $changes) { 437 global $conf; 438 439 if(isset($changes['mail'])) { 440 $found = $this->getUserByEmail($changes['mail']); 441 if($found != $user) { 442 msg($this->getLang('emailduplicate'), -1); 443 return false; 444 } 445 } 446 447 $ok = parent::modifyUser($user, $changes); 448 449 // refresh session cache 450 touch($conf['cachedir'] . '/sessionpurge'); 451 452 return $ok; 453 } 454 455 public function cleanServiceGroup($group) { 456 return strtolower($this->cleanGroup($group)); 457 } 458 459 public function cleanGroup($group) { 460 global $conf; 461 return str_replace(':', $conf['sepchar'], $group); 462 } 463} 464// vim:ts=4:sw=4:et: 465