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