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