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')) { 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) $this->_loadUserData(); 298 $mail = strtolower($mail); 299 300 foreach($this->users as $user => $uinfo) { 301 if(strtolower($uinfo['mail']) == $mail) return $user; 302 } 303 304 return false; 305 } 306 307 /** 308 * @param array $data 309 * @param string $service 310 */ 311 protected function setUserSession($data, $service) { 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 $cookie = base64_encode($user).'|'.((int) $sticky).'|'.base64_encode('oauth').'|'.base64_encode($servicename); 340 $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir']; 341 $time = $sticky ? (time() + $validityPeriodInSeconds) : 0; 342 setcookie(DOKU_COOKIE,$cookie, $time, $cookieDir, '',($conf['securecookie'] && is_ssl()), true); 343 } 344 345 /** 346 * Unset additional stuff in session on logout 347 */ 348 public function logOff() { 349 parent::logOff(); 350 351 $this->cleanLogout(); 352 } 353 354 /** 355 * unset auth cookies and session information 356 */ 357 private function cleanLogout() { 358 if(isset($_SESSION[DOKU_COOKIE]['oauth-done'])) { 359 unset($_SESSION[DOKU_COOKIE]['oauth-done']); 360 } 361 if(isset($_SESSION[DOKU_COOKIE]['auth'])) { 362 unset($_SESSION[DOKU_COOKIE]['auth']); 363 } 364 $this->setUserCookie('',true,'',-60); 365 } 366 367 /** 368 * Enhance function to check against duplicate emails 369 * 370 * @param string $user 371 * @param string $pwd 372 * @param string $name 373 * @param string $mail 374 * @param null $grps 375 * @return bool|null|string 376 */ 377 public function createUser($user, $pwd, $name, $mail, $grps = null) { 378 if($this->getUserByEmail($mail)) { 379 msg($this->getLang('emailduplicate'), -1); 380 return false; 381 } 382 383 return parent::createUser($user, $pwd, $name, $mail, $grps); 384 } 385 386 /** 387 * Enhance function to check aainst duplicate emails 388 * 389 * @param string $user 390 * @param array $changes 391 * @return bool 392 */ 393 public function modifyUser($user, $changes) { 394 global $conf; 395 396 if(isset($changes['mail'])) { 397 $found = $this->getUserByEmail($changes['mail']); 398 if($found != $user) { 399 msg($this->getLang('emailduplicate'), -1); 400 return false; 401 } 402 } 403 404 $ok = parent::modifyUser($user, $changes); 405 406 // refresh session cache 407 touch($conf['cachedir'] . '/sessionpurge'); 408 409 return $ok; 410 } 411 412} 413 414// vim:ts=4:sw=4:et: 415