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']; 56523e6571SMichael Große 57523e6571SMichael Große unset($_SESSION[DOKU_COOKIE]['oauth-inprogress']); 58523e6571SMichael Große $existingLoginProcess = true; 59523e6571SMichael Große } 60523e6571SMichael Große 61a7a8f46aSAndreas Gohr // either we're in oauth login or a previous log needs to be rechecked 622e94f0b8SAndreas Gohr if(isset($servicename)) { 63a7a8f46aSAndreas Gohr /** @var helper_plugin_oauth $hlp */ 64a7a8f46aSAndreas Gohr $hlp = plugin_load('helper', 'oauth'); 65827232fcSMichael Große 66827232fcSMichael Große /** @var OAuth\Plugin\AbstractAdapter $service */ 67a7a8f46aSAndreas Gohr $service = $hlp->loadService($servicename); 68523e6571SMichael Große if(is_null($service)) { 69523e6571SMichael Große $this->cleanLogout(); 70523e6571SMichael Große return false; 71a7a8f46aSAndreas Gohr } 72a7a8f46aSAndreas Gohr 73523e6571SMichael Große if($service->checkToken()) { 74523e6571SMichael Große $ok = $this->processLogin($sticky, $service, $servicename, $page); 75523e6571SMichael Große if (!$ok) { 76523e6571SMichael Große $this->cleanLogout(); 77523e6571SMichael Große return false; 78523e6571SMichael Große } 79523e6571SMichael Große return true; 80523e6571SMichael Große } else { 81523e6571SMichael Große if ($existingLoginProcess) { 82523e6571SMichael Große msg($this->getLang('oauth login failed'),0); 83523e6571SMichael Große $this->cleanLogout(); 84523e6571SMichael Große return false; 85523e6571SMichael Große } else { 86523e6571SMichael Große // first time here 87523e6571SMichael Große $this->relogin($servicename); 88523e6571SMichael Große } 89523e6571SMichael Große } 90523e6571SMichael Große 91523e6571SMichael Große $this->cleanLogout(); 92a7a8f46aSAndreas Gohr return false; // something went wrong during oAuth login 93213f4618SMichael Große } elseif (isset($_COOKIE[DOKU_COOKIE])) { 94213f4618SMichael Große global $INPUT; 95213f4618SMichael Große //try cookie 96213f4618SMichael Große list($cookieuser, $cookiesticky, $auth, $servicename) = explode('|', $_COOKIE[DOKU_COOKIE]); 97213f4618SMichael Große $cookieuser = base64_decode($cookieuser, true); 98213f4618SMichael Große $auth = base64_decode($auth, true); 99213f4618SMichael Große $servicename = base64_decode($servicename, true); 100213f4618SMichael Große if ($auth === 'oauth') { 101213f4618SMichael Große $this->relogin($servicename); 102213f4618SMichael Große } 10380852c15SAndreas Gohr } 10480852c15SAndreas Gohr 105a7a8f46aSAndreas Gohr // do the "normal" plain auth login via form 106a7a8f46aSAndreas Gohr return auth_login($user, $pass, $sticky); 107a7a8f46aSAndreas Gohr } 10880852c15SAndreas Gohr 109f2e164b0SMichael Große /** 110f2e164b0SMichael Große * @param array $session cookie auth session 111f2e164b0SMichael Große * 112f2e164b0SMichael Große * @return bool 113f2e164b0SMichael Große */ 114f2e164b0SMichael Große protected function isSessionValid ($session) { 115f2e164b0SMichael Große /** @var helper_plugin_oauth $hlp */ 116f2e164b0SMichael Große $hlp = plugin_load('helper', 'oauth'); 117f2e164b0SMichael Große if ($hlp->validBrowserID($session)) { 118f2e164b0SMichael Große if (!$hlp->isSessionTimedOut($session)) { 119f2e164b0SMichael Große return true; 120f2e164b0SMichael Große } elseif (!($hlp->isGETRequest() && $hlp->isDokuPHP())) { 121f2e164b0SMichael Große // only force a recheck on a timed-out session during a GET request on the main script doku.php 122f2e164b0SMichael Große return true; 123f2e164b0SMichael Große } 124f2e164b0SMichael Große } 125f2e164b0SMichael Große return false; 126f2e164b0SMichael Große } 127f2e164b0SMichael Große 128213f4618SMichael Große protected function relogin($servicename) { 129213f4618SMichael Große global $INPUT; 130213f4618SMichael Große 131213f4618SMichael Große /** @var helper_plugin_oauth $hlp */ 132213f4618SMichael Große $hlp = plugin_load('helper', 'oauth'); 133213f4618SMichael Große $service = $hlp->loadService($servicename); 134213f4618SMichael Große if(is_null($service)) return false; 135213f4618SMichael Große 136213f4618SMichael Große // remember service in session 137213f4618SMichael Große session_start(); 138213f4618SMichael Große $_SESSION[DOKU_COOKIE]['oauth-inprogress']['service'] = $servicename; 139213f4618SMichael Große $_SESSION[DOKU_COOKIE]['oauth-inprogress']['id'] = $INPUT->str('id'); 140213f4618SMichael Große 14109623faaSMichael Große $_SESSION[DOKU_COOKIE]['oauth-done']['$_REQUEST'] = $_REQUEST; 142213f4618SMichael Große 143213f4618SMichael Große if (is_array($INPUT->post->param('do'))) { 144213f4618SMichael Große $doPost = key($INPUT->post->arr('do')); 145213f4618SMichael Große } else { 146213f4618SMichael Große $doPost = $INPUT->post->str('do'); 147213f4618SMichael Große } 148213f4618SMichael Große $doGet = $INPUT->get->str('do'); 149213f4618SMichael Große if (!empty($doPost)) { 150213f4618SMichael Große $_SESSION[DOKU_COOKIE]['oauth-done']['do'] = $doPost; 151213f4618SMichael Große } elseif (!empty($doGet)) { 152213f4618SMichael Große $_SESSION[DOKU_COOKIE]['oauth-done']['do'] = $doGet; 153213f4618SMichael Große } 154213f4618SMichael Große 155213f4618SMichael Große session_write_close(); 156213f4618SMichael Große 157213f4618SMichael Große $service->login(); 158213f4618SMichael Große } 159213f4618SMichael Große 160a7a8f46aSAndreas Gohr /** 161b2b9fbc7SMichael Große * @param $sticky 162b2b9fbc7SMichael Große * @param OAuth\Plugin\AbstractAdapter $service 1639928f5efSMichael Große * @param string $servicename 164b2b9fbc7SMichael Große * @param string $page 165f07c7607SMichael Große * 166f07c7607SMichael Große * @return bool 167f07c7607SMichael Große */ 168b2b9fbc7SMichael Große protected function processLogin($sticky, $service, $servicename, $page) { 169b2b9fbc7SMichael Große $uinfo = $service->getUser(); 170b2b9fbc7SMichael Große $ok = $this->processUser($uinfo, $servicename); 171f07c7607SMichael Große if(!$ok) { 172f07c7607SMichael Große return false; 173f07c7607SMichael Große } 174b2b9fbc7SMichael Große $this->setUserSession($uinfo, $servicename); 175b2b9fbc7SMichael Große $this->setUserCookie($uinfo['user'], $sticky, $servicename); 176b2b9fbc7SMichael Große if(isset($page)) { 177b2b9fbc7SMichael Große send_redirect(wl($page)); 178b2b9fbc7SMichael Große } 179f07c7607SMichael Große return true; 180f07c7607SMichael Große } 181f07c7607SMichael Große 1829928f5efSMichael Große /** 1839928f5efSMichael Große * process the user and update the $uinfo array 1849928f5efSMichael Große * 1859928f5efSMichael Große * @param $uinfo 1869928f5efSMichael Große * @param $servicename 1879928f5efSMichael Große * 1889928f5efSMichael Große * @return bool 1899928f5efSMichael Große */ 1909928f5efSMichael Große protected function processUser(&$uinfo, $servicename) { 1919928f5efSMichael Große $uinfo['user'] = $this->cleanUser((string) $uinfo['user']); 1929928f5efSMichael Große if(!$uinfo['name']) $uinfo['name'] = $uinfo['user']; 1939928f5efSMichael Große 1949928f5efSMichael Große if(!$uinfo['user'] || !$uinfo['mail']) { 1959928f5efSMichael Große msg("$servicename did not provide the needed user info. Can't log you in", -1); 1969928f5efSMichael Große return false; 1979928f5efSMichael Große } 1989928f5efSMichael Große 1999928f5efSMichael Große // see if the user is known already 2009928f5efSMichael Große $user = $this->getUserByEmail($uinfo['mail']); 2019928f5efSMichael Große if($user) { 2029928f5efSMichael Große $sinfo = $this->getUserData($user); 2039928f5efSMichael Große // check if the user allowed access via this service 2049928f5efSMichael Große if(!in_array($this->cleanGroup($servicename), $sinfo['grps'])) { 2059928f5efSMichael Große msg(sprintf($this->getLang('authnotenabled'), $servicename), -1); 2069928f5efSMichael Große return false; 2079928f5efSMichael Große } 2089928f5efSMichael Große $uinfo['user'] = $user; 2099928f5efSMichael Große $uinfo['name'] = $sinfo['name']; 2109928f5efSMichael Große $uinfo['grps'] = array_merge((array) $uinfo['grps'], $sinfo['grps']); 2119928f5efSMichael Große } elseif(actionOK('register')) { 2129928f5efSMichael Große $ok = $this->addUser($uinfo, $servicename); 2139928f5efSMichael Große if(!$ok) { 2149928f5efSMichael Große msg('something went wrong creating your user account. please try again later.', -1); 2159928f5efSMichael Große return false; 2169928f5efSMichael Große } 2179928f5efSMichael Große } else { 2189928f5efSMichael Große msg($this->getLang('addUser not possible'), -1); 2199928f5efSMichael Große return false; 2209928f5efSMichael Große } 2219928f5efSMichael Große return true; 2229928f5efSMichael Große } 2239928f5efSMichael Große 2249928f5efSMichael Große /** 225b2b9fbc7SMichael Große * new user, create him - making sure the login is unique by adding a number if needed 226b2b9fbc7SMichael Große * 227b2b9fbc7SMichael Große * @param array $uinfo user info received from the oAuth service 228b2b9fbc7SMichael Große * @param string $servicename 229b2b9fbc7SMichael Große * 230b2b9fbc7SMichael Große * @return bool 231b2b9fbc7SMichael Große */ 232b2b9fbc7SMichael Große protected function addUser(&$uinfo, $servicename) { 233b2b9fbc7SMichael Große global $conf; 234b2b9fbc7SMichael Große $user = $uinfo['user']; 235b2b9fbc7SMichael Große $count = ''; 236b2b9fbc7SMichael Große while($this->getUserData($user . $count)) { 237b2b9fbc7SMichael Große if($count) { 238b2b9fbc7SMichael Große $count++; 239b2b9fbc7SMichael Große } else { 240b2b9fbc7SMichael Große $count = 1; 241b2b9fbc7SMichael Große } 242b2b9fbc7SMichael Große } 243b2b9fbc7SMichael Große $user = $user . $count; 244b2b9fbc7SMichael Große $uinfo['user'] = $user; 245b2b9fbc7SMichael Große $groups_on_creation = array(); 246b2b9fbc7SMichael Große $groups_on_creation[] = $conf['defaultgroup']; 247b2b9fbc7SMichael Große $groups_on_creation[] = $this->cleanGroup($servicename); // add service as group 248b2b9fbc7SMichael Große $uinfo['grps'] = array_merge((array) $uinfo['grps'], $groups_on_creation); 249b2b9fbc7SMichael Große 250b2b9fbc7SMichael Große $ok = $this->triggerUserMod( 251b2b9fbc7SMichael Große 'create', 252b2b9fbc7SMichael Große array($user, auth_pwgen($user), $uinfo['name'], $uinfo['mail'], $groups_on_creation,) 253b2b9fbc7SMichael Große ); 254b2b9fbc7SMichael Große if(!$ok) { 255b2b9fbc7SMichael Große return false; 256b2b9fbc7SMichael Große } 257b2b9fbc7SMichael Große 258b2b9fbc7SMichael Große // send notification about the new user 259b2b9fbc7SMichael Große $subscription = new Subscription(); 260b2b9fbc7SMichael Große $subscription->send_register($user, $uinfo['name'], $uinfo['mail']); 261b2b9fbc7SMichael Große return true; 262b2b9fbc7SMichael Große } 263b2b9fbc7SMichael Große 264b2b9fbc7SMichael Große /** 265b2b9fbc7SMichael Große * Find a user by his email address 266b2b9fbc7SMichael Große * 267b2b9fbc7SMichael Große * @param $mail 268b2b9fbc7SMichael Große * @return bool|string 269b2b9fbc7SMichael Große */ 270b2b9fbc7SMichael Große protected function getUserByEmail($mail) { 271b2b9fbc7SMichael Große if($this->users === null) $this->_loadUserData(); 272b2b9fbc7SMichael Große $mail = strtolower($mail); 273b2b9fbc7SMichael Große 274b2b9fbc7SMichael Große foreach($this->users as $user => $uinfo) { 275b2b9fbc7SMichael Große if(strtolower($uinfo['mail']) == $mail) return $user; 276b2b9fbc7SMichael Große } 277b2b9fbc7SMichael Große 278b2b9fbc7SMichael Große return false; 279b2b9fbc7SMichael Große } 280b2b9fbc7SMichael Große 281b2b9fbc7SMichael Große /** 282b2b9fbc7SMichael Große * @param array $data 283b2b9fbc7SMichael Große * @param string $service 284b2b9fbc7SMichael Große */ 285b2b9fbc7SMichael Große protected function setUserSession($data, $service) { 286b2b9fbc7SMichael Große global $USERINFO; 287b2b9fbc7SMichael Große global $conf; 288b2b9fbc7SMichael Große 289b2b9fbc7SMichael Große // set up groups 290b2b9fbc7SMichael Große if(!is_array($data['grps'])) { 291b2b9fbc7SMichael Große $data['grps'] = array(); 292b2b9fbc7SMichael Große } 293b2b9fbc7SMichael Große $data['grps'][] = $this->cleanGroup($service); 294b2b9fbc7SMichael Große $data['grps'] = array_unique($data['grps']); 295b2b9fbc7SMichael Große 296b2b9fbc7SMichael Große $USERINFO = $data; 297b2b9fbc7SMichael Große $_SERVER['REMOTE_USER'] = $data['user']; 298b2b9fbc7SMichael Große $_SESSION[DOKU_COOKIE]['auth']['user'] = $data['user']; 299b2b9fbc7SMichael Große $_SESSION[DOKU_COOKIE]['auth']['pass'] = $data['pass']; 300b2b9fbc7SMichael Große $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO; 301b2b9fbc7SMichael Große $_SESSION[DOKU_COOKIE]['auth']['buid'] = auth_browseruid(); 302b2b9fbc7SMichael Große $_SESSION[DOKU_COOKIE]['auth']['time'] = time(); 303b2b9fbc7SMichael Große $_SESSION[DOKU_COOKIE]['auth']['oauth'] = $service; 304b2b9fbc7SMichael Große } 305b2b9fbc7SMichael Große 306b2b9fbc7SMichael Große /** 3079928f5efSMichael Große * @param string $user 308523e6571SMichael Große * @param bool $sticky 3099928f5efSMichael Große * @param string $servicename 310523e6571SMichael Große * @param int $validityPeriodInSeconds optional, per default 1 Year 3119928f5efSMichael Große */ 312523e6571SMichael Große private function setUserCookie($user, $sticky, $servicename, $validityPeriodInSeconds = 31536000) { 3139928f5efSMichael Große $cookie = base64_encode($user).'|'.((int) $sticky).'|'.base64_encode('oauth').'|'.base64_encode($servicename); 3149928f5efSMichael Große $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir']; 315523e6571SMichael Große $time = $sticky ? (time() + $validityPeriodInSeconds) : 0; 3169928f5efSMichael Große setcookie(DOKU_COOKIE,$cookie, $time, $cookieDir, '',($conf['securecookie'] && is_ssl()), true); 3179928f5efSMichael Große } 3189928f5efSMichael Große 319827232fcSMichael Große /** 320b2b9fbc7SMichael Große * Unset additional stuff in session on logout 321827232fcSMichael Große */ 322b2b9fbc7SMichael Große public function logOff() { 323b2b9fbc7SMichael Große parent::logOff(); 324b2b9fbc7SMichael Große 325*af2a4e8fSMichael Große $this->cleanLogout(); 326b2b9fbc7SMichael Große } 327b2b9fbc7SMichael Große 328b2b9fbc7SMichael Große /** 329b2b9fbc7SMichael Große * unset auth cookies and session information 330b2b9fbc7SMichael Große */ 331b2b9fbc7SMichael Große private function cleanLogout() { 332*af2a4e8fSMichael Große if(isset($_SESSION[DOKU_COOKIE]['oauth-done'])) { 333b2b9fbc7SMichael Große unset($_SESSION[DOKU_COOKIE]['oauth-done']); 334*af2a4e8fSMichael Große } 335*af2a4e8fSMichael Große if(isset($_SESSION[DOKU_COOKIE]['auth'])) { 336b2b9fbc7SMichael Große unset($_SESSION[DOKU_COOKIE]['auth']); 337*af2a4e8fSMichael Große } 338b2b9fbc7SMichael Große $this->setUserCookie('',true,'',-60); 339b2b9fbc7SMichael Große } 340b2b9fbc7SMichael Große 341b2b9fbc7SMichael Große /** 342b2b9fbc7SMichael Große * Enhance function to check against duplicate emails 343b2b9fbc7SMichael Große * 344b2b9fbc7SMichael Große * @param string $user 345b2b9fbc7SMichael Große * @param string $pwd 346b2b9fbc7SMichael Große * @param string $name 347b2b9fbc7SMichael Große * @param string $mail 348b2b9fbc7SMichael Große * @param null $grps 349b2b9fbc7SMichael Große * @return bool|null|string 350b2b9fbc7SMichael Große */ 351b2b9fbc7SMichael Große public function createUser($user, $pwd, $name, $mail, $grps = null) { 352b2b9fbc7SMichael Große if($this->getUserByEmail($mail)) { 353b2b9fbc7SMichael Große msg($this->getLang('emailduplicate'), -1); 354827232fcSMichael Große return false; 355827232fcSMichael Große } 356b2b9fbc7SMichael Große 357b2b9fbc7SMichael Große return parent::createUser($user, $pwd, $name, $mail, $grps); 358827232fcSMichael Große } 359b2b9fbc7SMichael Große 360b2b9fbc7SMichael Große /** 361b2b9fbc7SMichael Große * Enhance function to check aainst duplicate emails 362b2b9fbc7SMichael Große * 363b2b9fbc7SMichael Große * @param string $user 364b2b9fbc7SMichael Große * @param array $changes 365b2b9fbc7SMichael Große * @return bool 366b2b9fbc7SMichael Große */ 367b2b9fbc7SMichael Große public function modifyUser($user, $changes) { 368b2b9fbc7SMichael Große global $conf; 369b2b9fbc7SMichael Große 370b2b9fbc7SMichael Große if(isset($changes['mail'])) { 371b2b9fbc7SMichael Große $found = $this->getUserByEmail($changes['mail']); 372b2b9fbc7SMichael Große if($found != $user) { 373b2b9fbc7SMichael Große msg($this->getLang('emailduplicate'), -1); 374b2b9fbc7SMichael Große return false; 375b2b9fbc7SMichael Große } 376b2b9fbc7SMichael Große } 377b2b9fbc7SMichael Große 378b2b9fbc7SMichael Große $ok = parent::modifyUser($user, $changes); 379b2b9fbc7SMichael Große 380b2b9fbc7SMichael Große // refresh session cache 381b2b9fbc7SMichael Große touch($conf['cachedir'] . '/sessionpurge'); 382b2b9fbc7SMichael Große 383b2b9fbc7SMichael Große return $ok; 384827232fcSMichael Große } 385827232fcSMichael Große 38680852c15SAndreas Gohr} 38780852c15SAndreas Gohr 38880852c15SAndreas Gohr// vim:ts=4:sw=4:et: 389