180852c15SAndreas Gohr<?php 280852c15SAndreas Gohr/** 380852c15SAndreas Gohr * DokuWiki Plugin oauth (Auth Component) 480852c15SAndreas Gohr * 580852c15SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 680852c15SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 780852c15SAndreas Gohr */ 880852c15SAndreas Gohr 980852c15SAndreas Gohr// must be run within Dokuwiki 1080852c15SAndreas Gohrif(!defined('DOKU_INC')) die(); 1180852c15SAndreas Gohr 12f10e09e2SAndreas Gohrclass auth_plugin_oauth extends auth_plugin_authplain { 1380852c15SAndreas Gohr 14f866280eSAndreas Gohr /** 15f866280eSAndreas Gohr * Constructor 16f866280eSAndreas Gohr * 17f866280eSAndreas Gohr * Sets capabilities. 18f866280eSAndreas Gohr */ 1980852c15SAndreas Gohr public function __construct() { 20f10e09e2SAndreas Gohr parent::__construct(); 2180852c15SAndreas Gohr 22f10e09e2SAndreas Gohr $this->cando['external'] = true; 2380852c15SAndreas Gohr } 2480852c15SAndreas Gohr 25f866280eSAndreas Gohr /** 26f866280eSAndreas Gohr * Handle the login 27f866280eSAndreas Gohr * 28f866280eSAndreas Gohr * This either trusts the session data (if any), processes the second oAuth step or simply 29f866280eSAndreas Gohr * executes a normal plugin against local users. 30f866280eSAndreas Gohr * 31f866280eSAndreas Gohr * @param string $user 32f866280eSAndreas Gohr * @param string $pass 33f866280eSAndreas Gohr * @param bool $sticky 34f866280eSAndreas Gohr * @return bool 35f866280eSAndreas Gohr */ 36f10e09e2SAndreas Gohr function trustExternal($user, $pass, $sticky = false) { 37a7a8f46aSAndreas Gohr global $USERINFO; 3880852c15SAndreas Gohr 39a7a8f46aSAndreas Gohr // check session for existing oAuth login data 40a7a8f46aSAndreas Gohr $session = $_SESSION[DOKU_COOKIE]['auth']; 41523e6571SMichael Große if(isset($session['oauth'])) { 42a7a8f46aSAndreas Gohr $servicename = $session['oauth']; 43a7a8f46aSAndreas Gohr // check if session data is still considered valid 44f2e164b0SMichael Große if ($this->isSessionValid($session)) { 45a7a8f46aSAndreas Gohr $_SERVER['REMOTE_USER'] = $session['user']; 46a7a8f46aSAndreas Gohr $USERINFO = $session['info']; 4780852c15SAndreas Gohr return true; 48f10e09e2SAndreas Gohr } 4980852c15SAndreas Gohr } 5080852c15SAndreas Gohr 51523e6571SMichael Große $existingLoginProcess = false; 52523e6571SMichael Große // are we in login progress? 53523e6571SMichael Große if(isset($_SESSION[DOKU_COOKIE]['oauth-inprogress'])) { 54523e6571SMichael Große $servicename = $_SESSION[DOKU_COOKIE]['oauth-inprogress']['service']; 55523e6571SMichael Große $page = $_SESSION[DOKU_COOKIE]['oauth-inprogress']['id']; 56*188ba446SMichael Große $params = $_SESSION[DOKU_COOKIE]['oauth-inprogress']['params']; 57523e6571SMichael Große 58523e6571SMichael Große unset($_SESSION[DOKU_COOKIE]['oauth-inprogress']); 59523e6571SMichael Große $existingLoginProcess = true; 60523e6571SMichael Große } 61523e6571SMichael Große 62a7a8f46aSAndreas Gohr // either we're in oauth login or a previous log needs to be rechecked 632e94f0b8SAndreas Gohr if(isset($servicename)) { 64a7a8f46aSAndreas Gohr /** @var helper_plugin_oauth $hlp */ 65a7a8f46aSAndreas Gohr $hlp = plugin_load('helper', 'oauth'); 66827232fcSMichael Große 67827232fcSMichael Große /** @var OAuth\Plugin\AbstractAdapter $service */ 68a7a8f46aSAndreas Gohr $service = $hlp->loadService($servicename); 69523e6571SMichael Große if(is_null($service)) { 70523e6571SMichael Große $this->cleanLogout(); 71523e6571SMichael Große return false; 72a7a8f46aSAndreas Gohr } 73a7a8f46aSAndreas Gohr 74523e6571SMichael Große if($service->checkToken()) { 75*188ba446SMichael Große $ok = $this->processLogin($sticky, $service, $servicename, $page, $params); 76523e6571SMichael Große if (!$ok) { 77523e6571SMichael Große $this->cleanLogout(); 78523e6571SMichael Große return false; 79523e6571SMichael Große } 80523e6571SMichael Große return true; 81523e6571SMichael Große } else { 82523e6571SMichael Große if ($existingLoginProcess) { 83523e6571SMichael Große msg($this->getLang('oauth login failed'),0); 84523e6571SMichael Große $this->cleanLogout(); 85523e6571SMichael Große return false; 86523e6571SMichael Große } else { 87523e6571SMichael Große // first time here 88523e6571SMichael Große $this->relogin($servicename); 89523e6571SMichael Große } 90523e6571SMichael Große } 91523e6571SMichael Große 92523e6571SMichael Große $this->cleanLogout(); 93a7a8f46aSAndreas Gohr return false; // something went wrong during oAuth login 94213f4618SMichael Große } elseif (isset($_COOKIE[DOKU_COOKIE])) { 95213f4618SMichael Große global $INPUT; 96213f4618SMichael Große //try cookie 97213f4618SMichael Große list($cookieuser, $cookiesticky, $auth, $servicename) = explode('|', $_COOKIE[DOKU_COOKIE]); 98213f4618SMichael Große $cookieuser = base64_decode($cookieuser, true); 99213f4618SMichael Große $auth = base64_decode($auth, true); 100213f4618SMichael Große $servicename = base64_decode($servicename, true); 101213f4618SMichael Große if ($auth === 'oauth') { 102213f4618SMichael Große $this->relogin($servicename); 103213f4618SMichael Große } 10480852c15SAndreas Gohr } 10580852c15SAndreas Gohr 106a7a8f46aSAndreas Gohr // do the "normal" plain auth login via form 107a7a8f46aSAndreas Gohr return auth_login($user, $pass, $sticky); 108a7a8f46aSAndreas Gohr } 10980852c15SAndreas Gohr 110f2e164b0SMichael Große /** 111f2e164b0SMichael Große * @param array $session cookie auth session 112f2e164b0SMichael Große * 113f2e164b0SMichael Große * @return bool 114f2e164b0SMichael Große */ 115f2e164b0SMichael Große protected function isSessionValid ($session) { 116f2e164b0SMichael Große /** @var helper_plugin_oauth $hlp */ 117f2e164b0SMichael Große $hlp = plugin_load('helper', 'oauth'); 118f2e164b0SMichael Große if ($hlp->validBrowserID($session)) { 119f2e164b0SMichael Große if (!$hlp->isSessionTimedOut($session)) { 120f2e164b0SMichael Große return true; 121f2e164b0SMichael Große } elseif (!($hlp->isGETRequest() && $hlp->isDokuPHP())) { 122f2e164b0SMichael Große // only force a recheck on a timed-out session during a GET request on the main script doku.php 123f2e164b0SMichael Große return true; 124f2e164b0SMichael Große } 125f2e164b0SMichael Große } 126f2e164b0SMichael Große return false; 127f2e164b0SMichael Große } 128f2e164b0SMichael Große 129213f4618SMichael Große protected function relogin($servicename) { 130213f4618SMichael Große global $INPUT; 131213f4618SMichael Große 132213f4618SMichael Große /** @var helper_plugin_oauth $hlp */ 133213f4618SMichael Große $hlp = plugin_load('helper', 'oauth'); 134213f4618SMichael Große $service = $hlp->loadService($servicename); 135213f4618SMichael Große if(is_null($service)) return false; 136213f4618SMichael Große 137213f4618SMichael Große // remember service in session 138213f4618SMichael Große session_start(); 139213f4618SMichael Große $_SESSION[DOKU_COOKIE]['oauth-inprogress']['service'] = $servicename; 140213f4618SMichael Große $_SESSION[DOKU_COOKIE]['oauth-inprogress']['id'] = $INPUT->str('id'); 141*188ba446SMichael Große $_SESSION[DOKU_COOKIE]['oauth-inprogress']['params'] = $_GET; 142213f4618SMichael Große 14309623faaSMichael Große $_SESSION[DOKU_COOKIE]['oauth-done']['$_REQUEST'] = $_REQUEST; 144213f4618SMichael Große 145213f4618SMichael Große if (is_array($INPUT->post->param('do'))) { 146213f4618SMichael Große $doPost = key($INPUT->post->arr('do')); 147213f4618SMichael Große } else { 148213f4618SMichael Große $doPost = $INPUT->post->str('do'); 149213f4618SMichael Große } 150213f4618SMichael Große $doGet = $INPUT->get->str('do'); 151213f4618SMichael Große if (!empty($doPost)) { 152213f4618SMichael Große $_SESSION[DOKU_COOKIE]['oauth-done']['do'] = $doPost; 153213f4618SMichael Große } elseif (!empty($doGet)) { 154213f4618SMichael Große $_SESSION[DOKU_COOKIE]['oauth-done']['do'] = $doGet; 155213f4618SMichael Große } 156213f4618SMichael Große 157213f4618SMichael Große session_write_close(); 158213f4618SMichael Große 159213f4618SMichael Große $service->login(); 160213f4618SMichael Große } 161213f4618SMichael Große 162a7a8f46aSAndreas Gohr /** 163b2b9fbc7SMichael Große * @param $sticky 164b2b9fbc7SMichael Große * @param OAuth\Plugin\AbstractAdapter $service 1659928f5efSMichael Große * @param string $servicename 166b2b9fbc7SMichael Große * @param string $page 167*188ba446SMichael Große * @param array $params 168f07c7607SMichael Große * 169f07c7607SMichael Große * @return bool 170f07c7607SMichael Große */ 171*188ba446SMichael Große protected function processLogin($sticky, $service, $servicename, $page, $params = array()) { 172b2b9fbc7SMichael Große $uinfo = $service->getUser(); 173b2b9fbc7SMichael Große $ok = $this->processUser($uinfo, $servicename); 174f07c7607SMichael Große if(!$ok) { 175f07c7607SMichael Große return false; 176f07c7607SMichael Große } 177b2b9fbc7SMichael Große $this->setUserSession($uinfo, $servicename); 178b2b9fbc7SMichael Große $this->setUserCookie($uinfo['user'], $sticky, $servicename); 179b2b9fbc7SMichael Große if(isset($page)) { 180*188ba446SMichael Große if(!empty($params['id'])) unset($params['id']); 181*188ba446SMichael Große send_redirect(wl($page, $params, false, '&')); 182b2b9fbc7SMichael Große } 183f07c7607SMichael Große return true; 184f07c7607SMichael Große } 185f07c7607SMichael Große 1869928f5efSMichael Große /** 1879928f5efSMichael Große * process the user and update the $uinfo array 1889928f5efSMichael Große * 1899928f5efSMichael Große * @param $uinfo 1909928f5efSMichael Große * @param $servicename 1919928f5efSMichael Große * 1929928f5efSMichael Große * @return bool 1939928f5efSMichael Große */ 1949928f5efSMichael Große protected function processUser(&$uinfo, $servicename) { 1959928f5efSMichael Große $uinfo['user'] = $this->cleanUser((string) $uinfo['user']); 1969928f5efSMichael Große if(!$uinfo['name']) $uinfo['name'] = $uinfo['user']; 1979928f5efSMichael Große 1989928f5efSMichael Große if(!$uinfo['user'] || !$uinfo['mail']) { 1999928f5efSMichael Große msg("$servicename did not provide the needed user info. Can't log you in", -1); 2009928f5efSMichael Große return false; 2019928f5efSMichael Große } 2029928f5efSMichael Große 2039928f5efSMichael Große // see if the user is known already 2049928f5efSMichael Große $user = $this->getUserByEmail($uinfo['mail']); 2059928f5efSMichael Große if($user) { 2069928f5efSMichael Große $sinfo = $this->getUserData($user); 2079928f5efSMichael Große // check if the user allowed access via this service 2089928f5efSMichael Große if(!in_array($this->cleanGroup($servicename), $sinfo['grps'])) { 2099928f5efSMichael Große msg(sprintf($this->getLang('authnotenabled'), $servicename), -1); 2109928f5efSMichael Große return false; 2119928f5efSMichael Große } 2129928f5efSMichael Große $uinfo['user'] = $user; 2139928f5efSMichael Große $uinfo['name'] = $sinfo['name']; 2149928f5efSMichael Große $uinfo['grps'] = array_merge((array) $uinfo['grps'], $sinfo['grps']); 2159928f5efSMichael Große } elseif(actionOK('register')) { 2169928f5efSMichael Große $ok = $this->addUser($uinfo, $servicename); 2179928f5efSMichael Große if(!$ok) { 2189928f5efSMichael Große msg('something went wrong creating your user account. please try again later.', -1); 2199928f5efSMichael Große return false; 2209928f5efSMichael Große } 2219928f5efSMichael Große } else { 2229928f5efSMichael Große msg($this->getLang('addUser not possible'), -1); 2239928f5efSMichael Große return false; 2249928f5efSMichael Große } 2259928f5efSMichael Große return true; 2269928f5efSMichael Große } 2279928f5efSMichael Große 2289928f5efSMichael Große /** 229b2b9fbc7SMichael Große * new user, create him - making sure the login is unique by adding a number if needed 230b2b9fbc7SMichael Große * 231b2b9fbc7SMichael Große * @param array $uinfo user info received from the oAuth service 232b2b9fbc7SMichael Große * @param string $servicename 233b2b9fbc7SMichael Große * 234b2b9fbc7SMichael Große * @return bool 235b2b9fbc7SMichael Große */ 236b2b9fbc7SMichael Große protected function addUser(&$uinfo, $servicename) { 237b2b9fbc7SMichael Große global $conf; 238b2b9fbc7SMichael Große $user = $uinfo['user']; 239b2b9fbc7SMichael Große $count = ''; 240b2b9fbc7SMichael Große while($this->getUserData($user . $count)) { 241b2b9fbc7SMichael Große if($count) { 242b2b9fbc7SMichael Große $count++; 243b2b9fbc7SMichael Große } else { 244b2b9fbc7SMichael Große $count = 1; 245b2b9fbc7SMichael Große } 246b2b9fbc7SMichael Große } 247b2b9fbc7SMichael Große $user = $user . $count; 248b2b9fbc7SMichael Große $uinfo['user'] = $user; 249b2b9fbc7SMichael Große $groups_on_creation = array(); 250b2b9fbc7SMichael Große $groups_on_creation[] = $conf['defaultgroup']; 251b2b9fbc7SMichael Große $groups_on_creation[] = $this->cleanGroup($servicename); // add service as group 252b2b9fbc7SMichael Große $uinfo['grps'] = array_merge((array) $uinfo['grps'], $groups_on_creation); 253b2b9fbc7SMichael Große 254b2b9fbc7SMichael Große $ok = $this->triggerUserMod( 255b2b9fbc7SMichael Große 'create', 256b2b9fbc7SMichael Große array($user, auth_pwgen($user), $uinfo['name'], $uinfo['mail'], $groups_on_creation,) 257b2b9fbc7SMichael Große ); 258b2b9fbc7SMichael Große if(!$ok) { 259b2b9fbc7SMichael Große return false; 260b2b9fbc7SMichael Große } 261b2b9fbc7SMichael Große 262b2b9fbc7SMichael Große // send notification about the new user 263b2b9fbc7SMichael Große $subscription = new Subscription(); 264b2b9fbc7SMichael Große $subscription->send_register($user, $uinfo['name'], $uinfo['mail']); 265b2b9fbc7SMichael Große return true; 266b2b9fbc7SMichael Große } 267b2b9fbc7SMichael Große 268b2b9fbc7SMichael Große /** 269b2b9fbc7SMichael Große * Find a user by his email address 270b2b9fbc7SMichael Große * 271b2b9fbc7SMichael Große * @param $mail 272b2b9fbc7SMichael Große * @return bool|string 273b2b9fbc7SMichael Große */ 274b2b9fbc7SMichael Große protected function getUserByEmail($mail) { 275b2b9fbc7SMichael Große if($this->users === null) $this->_loadUserData(); 276b2b9fbc7SMichael Große $mail = strtolower($mail); 277b2b9fbc7SMichael Große 278b2b9fbc7SMichael Große foreach($this->users as $user => $uinfo) { 279b2b9fbc7SMichael Große if(strtolower($uinfo['mail']) == $mail) return $user; 280b2b9fbc7SMichael Große } 281b2b9fbc7SMichael Große 282b2b9fbc7SMichael Große return false; 283b2b9fbc7SMichael Große } 284b2b9fbc7SMichael Große 285b2b9fbc7SMichael Große /** 286b2b9fbc7SMichael Große * @param array $data 287b2b9fbc7SMichael Große * @param string $service 288b2b9fbc7SMichael Große */ 289b2b9fbc7SMichael Große protected function setUserSession($data, $service) { 290b2b9fbc7SMichael Große global $USERINFO; 291b2b9fbc7SMichael Große global $conf; 292b2b9fbc7SMichael Große 293b2b9fbc7SMichael Große // set up groups 294b2b9fbc7SMichael Große if(!is_array($data['grps'])) { 295b2b9fbc7SMichael Große $data['grps'] = array(); 296b2b9fbc7SMichael Große } 297b2b9fbc7SMichael Große $data['grps'][] = $this->cleanGroup($service); 298b2b9fbc7SMichael Große $data['grps'] = array_unique($data['grps']); 299b2b9fbc7SMichael Große 300b2b9fbc7SMichael Große $USERINFO = $data; 301b2b9fbc7SMichael Große $_SERVER['REMOTE_USER'] = $data['user']; 302b2b9fbc7SMichael Große $_SESSION[DOKU_COOKIE]['auth']['user'] = $data['user']; 303b2b9fbc7SMichael Große $_SESSION[DOKU_COOKIE]['auth']['pass'] = $data['pass']; 304b2b9fbc7SMichael Große $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO; 305b2b9fbc7SMichael Große $_SESSION[DOKU_COOKIE]['auth']['buid'] = auth_browseruid(); 306b2b9fbc7SMichael Große $_SESSION[DOKU_COOKIE]['auth']['time'] = time(); 307b2b9fbc7SMichael Große $_SESSION[DOKU_COOKIE]['auth']['oauth'] = $service; 308b2b9fbc7SMichael Große } 309b2b9fbc7SMichael Große 310b2b9fbc7SMichael Große /** 3119928f5efSMichael Große * @param string $user 312523e6571SMichael Große * @param bool $sticky 3139928f5efSMichael Große * @param string $servicename 314523e6571SMichael Große * @param int $validityPeriodInSeconds optional, per default 1 Year 3159928f5efSMichael Große */ 316523e6571SMichael Große private function setUserCookie($user, $sticky, $servicename, $validityPeriodInSeconds = 31536000) { 3179928f5efSMichael Große $cookie = base64_encode($user).'|'.((int) $sticky).'|'.base64_encode('oauth').'|'.base64_encode($servicename); 3189928f5efSMichael Große $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir']; 319523e6571SMichael Große $time = $sticky ? (time() + $validityPeriodInSeconds) : 0; 3209928f5efSMichael Große setcookie(DOKU_COOKIE,$cookie, $time, $cookieDir, '',($conf['securecookie'] && is_ssl()), true); 3219928f5efSMichael Große } 3229928f5efSMichael Große 323827232fcSMichael Große /** 324b2b9fbc7SMichael Große * Unset additional stuff in session on logout 325827232fcSMichael Große */ 326b2b9fbc7SMichael Große public function logOff() { 327b2b9fbc7SMichael Große parent::logOff(); 328b2b9fbc7SMichael Große 329af2a4e8fSMichael Große $this->cleanLogout(); 330b2b9fbc7SMichael Große } 331b2b9fbc7SMichael Große 332b2b9fbc7SMichael Große /** 333b2b9fbc7SMichael Große * unset auth cookies and session information 334b2b9fbc7SMichael Große */ 335b2b9fbc7SMichael Große private function cleanLogout() { 336af2a4e8fSMichael Große if(isset($_SESSION[DOKU_COOKIE]['oauth-done'])) { 337b2b9fbc7SMichael Große unset($_SESSION[DOKU_COOKIE]['oauth-done']); 338af2a4e8fSMichael Große } 339af2a4e8fSMichael Große if(isset($_SESSION[DOKU_COOKIE]['auth'])) { 340b2b9fbc7SMichael Große unset($_SESSION[DOKU_COOKIE]['auth']); 341af2a4e8fSMichael Große } 342b2b9fbc7SMichael Große $this->setUserCookie('',true,'',-60); 343b2b9fbc7SMichael Große } 344b2b9fbc7SMichael Große 345b2b9fbc7SMichael Große /** 346b2b9fbc7SMichael Große * Enhance function to check against duplicate emails 347b2b9fbc7SMichael Große * 348b2b9fbc7SMichael Große * @param string $user 349b2b9fbc7SMichael Große * @param string $pwd 350b2b9fbc7SMichael Große * @param string $name 351b2b9fbc7SMichael Große * @param string $mail 352b2b9fbc7SMichael Große * @param null $grps 353b2b9fbc7SMichael Große * @return bool|null|string 354b2b9fbc7SMichael Große */ 355b2b9fbc7SMichael Große public function createUser($user, $pwd, $name, $mail, $grps = null) { 356b2b9fbc7SMichael Große if($this->getUserByEmail($mail)) { 357b2b9fbc7SMichael Große msg($this->getLang('emailduplicate'), -1); 358827232fcSMichael Große return false; 359827232fcSMichael Große } 360b2b9fbc7SMichael Große 361b2b9fbc7SMichael Große return parent::createUser($user, $pwd, $name, $mail, $grps); 362827232fcSMichael Große } 363b2b9fbc7SMichael Große 364b2b9fbc7SMichael Große /** 365b2b9fbc7SMichael Große * Enhance function to check aainst duplicate emails 366b2b9fbc7SMichael Große * 367b2b9fbc7SMichael Große * @param string $user 368b2b9fbc7SMichael Große * @param array $changes 369b2b9fbc7SMichael Große * @return bool 370b2b9fbc7SMichael Große */ 371b2b9fbc7SMichael Große public function modifyUser($user, $changes) { 372b2b9fbc7SMichael Große global $conf; 373b2b9fbc7SMichael Große 374b2b9fbc7SMichael Große if(isset($changes['mail'])) { 375b2b9fbc7SMichael Große $found = $this->getUserByEmail($changes['mail']); 376b2b9fbc7SMichael Große if($found != $user) { 377b2b9fbc7SMichael Große msg($this->getLang('emailduplicate'), -1); 378b2b9fbc7SMichael Große return false; 379b2b9fbc7SMichael Große } 380b2b9fbc7SMichael Große } 381b2b9fbc7SMichael Große 382b2b9fbc7SMichael Große $ok = parent::modifyUser($user, $changes); 383b2b9fbc7SMichael Große 384b2b9fbc7SMichael Große // refresh session cache 385b2b9fbc7SMichael Große touch($conf['cachedir'] . '/sessionpurge'); 386b2b9fbc7SMichael Große 387b2b9fbc7SMichael Große return $ok; 388827232fcSMichael Große } 389827232fcSMichael Große 39080852c15SAndreas Gohr} 39180852c15SAndreas Gohr 39280852c15SAndreas Gohr// vim:ts=4:sw=4:et: 393