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