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