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