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 $USERINFO, $INPUT; 24 25 if ($INPUT->has('state') && plugin_load('helper', 'farmer', false, true)) { 26 $this->handleState($INPUT->str('state')); 27 } 28 29 // check session for existing oAuth login data 30 $session = $_SESSION[DOKU_COOKIE]['auth']; 31 if (isset($session['oauth'])) { 32 $servicename = $session['oauth']; 33 // check if session data is still considered valid 34 if ($this->isSessionValid($session)) { 35 $_SERVER['REMOTE_USER'] = $session['user']; 36 $USERINFO = $session['info']; 37 return true; 38 } 39 } 40 41 $existingLoginProcess = false; 42 // are we in login progress? 43 if (isset($_SESSION[DOKU_COOKIE]['oauth-inprogress'])) { 44 $servicename = $_SESSION[DOKU_COOKIE]['oauth-inprogress']['service']; 45 $page = $_SESSION[DOKU_COOKIE]['oauth-inprogress']['id']; 46 $params = $_SESSION[DOKU_COOKIE]['oauth-inprogress']['params']; 47 48 unset($_SESSION[DOKU_COOKIE]['oauth-inprogress']); 49 $existingLoginProcess = true; 50 } 51 52 // either we're in oauth login or a previous log needs to be rechecked 53 if (isset($servicename)) { 54 /** @var helper_plugin_oauth $hlp */ 55 $hlp = plugin_load('helper', 'oauth'); 56 57 /** @var OAuth\Plugin\AbstractAdapter $service */ 58 $service = $hlp->loadService($servicename); 59 if (is_null($service)) { 60 $this->cleanLogout(); 61 return false; 62 } 63 64 if ($service->checkToken()) { 65 $ok = $this->processLogin($sticky, $service, $servicename, $page, $params); 66 if (!$ok) { 67 $this->cleanLogout(); 68 return false; 69 } 70 return true; 71 } else { 72 if ($existingLoginProcess) { 73 msg($this->getLang('oauth login failed'), 0); 74 $this->cleanLogout(); 75 return false; 76 } else { 77 // first time here 78 $this->relogin($servicename); 79 } 80 } 81 82 $this->cleanLogout(); 83 return false; // something went wrong during oAuth login 84 } elseif (isset($_COOKIE[DOKU_COOKIE])) { 85 global $INPUT; 86 //try cookie 87 list($cookieuser, $cookiesticky, $auth, $servicename) = explode('|', $_COOKIE[DOKU_COOKIE]); 88 $cookieuser = base64_decode($cookieuser, true); 89 $auth = base64_decode($auth, true); 90 $servicename = base64_decode($servicename, true); 91 if ($auth === 'oauth') { 92 $this->relogin($servicename); 93 } 94 } 95 96 // do the "normal" plain auth login via form 97 return auth_login($user, $pass, $sticky); 98 } 99 100 /** 101 * Enhance function to check against duplicate emails 102 * 103 * @param string $user 104 * @param string $pwd 105 * @param string $name 106 * @param string $mail 107 * @param null $grps 108 * @return bool|null|string 109 */ 110 public function createUser($user, $pwd, $name, $mail, $grps = null) 111 { 112 if ($this->getUserByEmail($mail)) { 113 msg($this->getLang('emailduplicate'), -1); 114 return false; 115 } 116 117 return parent::createUser($user, $pwd, $name, $mail, $grps); 118 } 119 120 /** 121 * Enhance function to check against duplicate emails 122 * 123 * @param string $user 124 * @param array $changes 125 * @return bool 126 */ 127 public function modifyUser($user, $changes) 128 { 129 global $conf; 130 131 if (isset($changes['mail'])) { 132 $found = $this->getUserByEmail($changes['mail']); 133 if ($found && $found != $user) { 134 msg($this->getLang('emailduplicate'), -1); 135 return false; 136 } 137 } 138 139 $ok = parent::modifyUser($user, $changes); 140 141 // refresh session cache 142 touch($conf['cachedir'] . '/sessionpurge'); 143 144 return $ok; 145 } 146 147 /** 148 * Unset additional stuff in session on logout 149 */ 150 public function logOff() 151 { 152 parent::logOff(); 153 154 $this->cleanLogout(); 155 } 156 157 /** 158 * @param array $session cookie auth session 159 * 160 * @return bool 161 */ 162 protected function isSessionValid($session) 163 { 164 /** @var helper_plugin_oauth $hlp */ 165 $hlp = plugin_load('helper', 'oauth'); 166 if ($hlp->validBrowserID($session)) { 167 if (!$hlp->isSessionTimedOut($session)) { 168 return true; 169 } elseif (!($hlp->isGETRequest() && $hlp->isDokuPHP())) { 170 // only force a recheck on a timed-out session during a GET request on the main script doku.php 171 return true; 172 } 173 } 174 return false; 175 } 176 177 protected function relogin($servicename) 178 { 179 global $INPUT; 180 181 /** @var helper_plugin_oauth $hlp */ 182 $hlp = plugin_load('helper', 'oauth'); 183 $service = $hlp->loadService($servicename); 184 if (is_null($service)) return false; 185 186 // remember service in session 187 session_start(); 188 $_SESSION[DOKU_COOKIE]['oauth-inprogress']['service'] = $servicename; 189 $_SESSION[DOKU_COOKIE]['oauth-inprogress']['id'] = $INPUT->str('id'); 190 $_SESSION[DOKU_COOKIE]['oauth-inprogress']['params'] = $_GET; 191 192 $_SESSION[DOKU_COOKIE]['oauth-done']['$_REQUEST'] = $_REQUEST; 193 194 if (is_array($INPUT->post->param('do'))) { 195 $doPost = key($INPUT->post->arr('do')); 196 } else { 197 $doPost = $INPUT->post->str('do'); 198 } 199 $doGet = $INPUT->get->str('do'); 200 if (!empty($doPost)) { 201 $_SESSION[DOKU_COOKIE]['oauth-done']['do'] = $doPost; 202 } elseif (!empty($doGet)) { 203 $_SESSION[DOKU_COOKIE]['oauth-done']['do'] = $doGet; 204 } 205 206 session_write_close(); 207 208 $service->login(); 209 } 210 211 /** 212 * @param $sticky 213 * @param OAuth\Plugin\AbstractAdapter $service 214 * @param string $servicename 215 * @param string $page 216 * @param array $params 217 * 218 * @return bool 219 */ 220 protected function processLogin($sticky, $service, $servicename, $page, $params = array()) 221 { 222 $uinfo = $service->getUser(); 223 $ok = $this->processUser($uinfo, $servicename); 224 if (!$ok) { 225 return false; 226 } 227 $this->setUserSession($uinfo, $servicename); 228 $this->setUserCookie($uinfo['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 $uinfo array 238 * 239 * @param $uinfo 240 * @param $servicename 241 * 242 * @return bool 243 */ 244 protected function processUser(&$uinfo, $servicename) 245 { 246 $uinfo['user'] = $this->cleanUser((string)$uinfo['user']); 247 if (!$uinfo['name']) $uinfo['name'] = $uinfo['user']; 248 249 if (!$uinfo['user'] || !$uinfo['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 $user = $this->getUserByEmail($uinfo['mail']); 256 if ($user) { 257 $sinfo = $this->getUserData($user); 258 // check if the user allowed access via this service 259 if (!in_array($this->cleanGroup($servicename), $sinfo['grps'])) { 260 msg(sprintf($this->getLang('authnotenabled'), $servicename), -1); 261 return false; 262 } 263 $uinfo['user'] = $user; 264 $uinfo['name'] = $sinfo['name']; 265 $uinfo['grps'] = array_merge((array)$uinfo['grps'], $sinfo['grps']); 266 } elseif (actionOK('register') || $this->getConf('register-on-auth')) { 267 $ok = $this->addUser($uinfo, $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 $uinfo user info received from the oAuth service 283 * @param string $servicename 284 * 285 * @return bool 286 */ 287 protected function addUser(&$uinfo, $servicename) 288 { 289 global $conf; 290 $user = $uinfo['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 $uinfo['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 $uinfo['grps'] = array_merge((array)$uinfo['grps'], $groups_on_creation); 305 306 $ok = $this->triggerUserMod( 307 'create', 308 array($user, auth_pwgen($user), $uinfo['name'], $uinfo['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, $uinfo['name'], $uinfo['mail']); 317 return true; 318 } 319 320 /** 321 * Find a user by his 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 => $uinfo) { 338 if (strtolower($uinfo['mail']) == $mail) return $user; 339 } 340 341 return false; 342 } 343 344 /** 345 * @param array $data 346 * @param string $service 347 */ 348 protected function setUserSession($data, $service) 349 { 350 global $USERINFO; 351 352 // set up groups 353 if (!is_array($data['grps'])) { 354 $data['grps'] = array(); 355 } 356 $data['grps'][] = $this->cleanGroup($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 $_SESSION[DOKU_COOKIE]['auth']['pass'] = $data['pass']; 363 $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO; 364 $_SESSION[DOKU_COOKIE]['auth']['buid'] = auth_browseruid(); 365 $_SESSION[DOKU_COOKIE]['auth']['time'] = time(); 366 $_SESSION[DOKU_COOKIE]['auth']['oauth'] = $service; 367 } 368 369 /** 370 * @param string $user 371 * @param bool $sticky 372 * @param string $servicename 373 * @param int $validityPeriodInSeconds optional, per default 1 Year 374 */ 375 private function setUserCookie($user, $sticky, $servicename, $validityPeriodInSeconds = 31536000) 376 { 377 $cookie = base64_encode($user) . '|' . ((int)$sticky) . '|' . base64_encode('oauth') . '|' . base64_encode($servicename); 378 $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir']; 379 $time = $sticky ? (time() + $validityPeriodInSeconds) : 0; 380 setcookie(DOKU_COOKIE, $cookie, $time, $cookieDir, '', ($conf['securecookie'] && is_ssl()), true); 381 } 382 383 /** 384 * unset auth cookies and session information 385 */ 386 private function cleanLogout() 387 { 388 if (isset($_SESSION[DOKU_COOKIE]['oauth-done'])) { 389 unset($_SESSION[DOKU_COOKIE]['oauth-done']); 390 } 391 if (isset($_SESSION[DOKU_COOKIE]['auth'])) { 392 unset($_SESSION[DOKU_COOKIE]['auth']); 393 } 394 $this->setUserCookie('', true, '', -60); 395 } 396 397 /** 398 * Farmer plugin 399 * 400 * @param $state 401 */ 402 private function handleState($state) 403 { 404 /** @var \helper_plugin_farmer $farmer */ 405 $farmer = plugin_load('helper', 'farmer', false, true); 406 $data = json_decode(base64_decode(urldecode($state))); 407 if (empty($data->animal) || $farmer->getAnimal() == $data->animal) { 408 return; 409 } 410 $animal = $data->animal; 411 $allAnimals = $farmer->getAllAnimals(); 412 if (!in_array($animal, $allAnimals)) { 413 msg('Animal ' . $animal . ' does not exist!'); 414 return; 415 } 416 global $INPUT; 417 $url = $farmer->getAnimalURL($animal) . '/doku.php?' . $INPUT->server->str('QUERY_STRING'); 418 send_redirect($url); 419 } 420} 421 422// vim:ts=4:sw=4:et: 423