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