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