174b4d4a4SAndreas Gohr<?php 274b4d4a4SAndreas Gohr 374b4d4a4SAndreas Gohrnamespace dokuwiki\plugin\oauth; 474b4d4a4SAndreas Gohr 56d9a8a49SAndreas Gohr/** 66d9a8a49SAndreas Gohr * Implements the flow control for oAuth 76d9a8a49SAndreas Gohr */ 874b4d4a4SAndreas Gohrclass OAuthManager 974b4d4a4SAndreas Gohr{ 106d9a8a49SAndreas Gohr // region main flow 1174b4d4a4SAndreas Gohr 1274b4d4a4SAndreas Gohr /** 1331039e80SAndreas Gohr * Explicitly starts the oauth flow by redirecting to IDP 1431039e80SAndreas Gohr * 1504a78b87SAndreas Gohr * @throws \OAuth\Common\Exception\Exception 1674b4d4a4SAndreas Gohr */ 1774b4d4a4SAndreas Gohr public function startFlow($servicename) 1874b4d4a4SAndreas Gohr { 1931039e80SAndreas Gohr global $ID; 2031039e80SAndreas Gohr 2174b4d4a4SAndreas Gohr $session = Session::getInstance(); 2228002081SAndreas Gohr $session->setLoginData($servicename, $ID); 2374b4d4a4SAndreas Gohr $service = $this->loadService($servicename); 2428002081SAndreas Gohr $service->initOAuthService(); 2574b4d4a4SAndreas Gohr $service->login(); // redirects 2674b4d4a4SAndreas Gohr } 2774b4d4a4SAndreas Gohr 2874b4d4a4SAndreas Gohr /** 2931039e80SAndreas Gohr * Continues the flow from various states 3031039e80SAndreas Gohr * 3174b4d4a4SAndreas Gohr * @return bool true if the login has been handled 3274b4d4a4SAndreas Gohr * @throws Exception 3374b4d4a4SAndreas Gohr * @throws \OAuth\Common\Exception\Exception 3474b4d4a4SAndreas Gohr */ 3574b4d4a4SAndreas Gohr public function continueFlow() 3674b4d4a4SAndreas Gohr { 3774b4d4a4SAndreas Gohr return $this->loginByService() or 3874b4d4a4SAndreas Gohr $this->loginBySession() or 3974b4d4a4SAndreas Gohr $this->loginByCookie(); 4074b4d4a4SAndreas Gohr } 4174b4d4a4SAndreas Gohr 4274b4d4a4SAndreas Gohr /** 436d9a8a49SAndreas Gohr * Second step in a explicit login, validates the oauth code 446d9a8a49SAndreas Gohr * 456d9a8a49SAndreas Gohr * @return bool true if successful, false if not applies 4674b4d4a4SAndreas Gohr * @throws \OAuth\Common\Exception\Exception 4774b4d4a4SAndreas Gohr */ 4874b4d4a4SAndreas Gohr protected function loginByService() 4974b4d4a4SAndreas Gohr { 5074b4d4a4SAndreas Gohr global $INPUT; 5174b4d4a4SAndreas Gohr 5274b4d4a4SAndreas Gohr if (!$INPUT->get->has('code') && !$INPUT->get->has('oauth_token')) { 5374b4d4a4SAndreas Gohr return false; 5474b4d4a4SAndreas Gohr } 5574b4d4a4SAndreas Gohr 5674b4d4a4SAndreas Gohr $session = Session::getInstance(); 5774b4d4a4SAndreas Gohr 5874b4d4a4SAndreas Gohr // init service from session 5974b4d4a4SAndreas Gohr $logindata = $session->getLoginData(); 6074b4d4a4SAndreas Gohr if (!$logindata) return false; 6174b4d4a4SAndreas Gohr $service = $this->loadService($logindata['servicename']); 6228002081SAndreas Gohr $service->initOAuthService(); 6374b4d4a4SAndreas Gohr $session->clearLoginData(); 6474b4d4a4SAndreas Gohr 6574b4d4a4SAndreas Gohr // oAuth login 6604a78b87SAndreas Gohr if (!$service->checkToken()) throw new \OAuth\Common\Exception\Exception("Invalid Token - Login failed"); 6774b4d4a4SAndreas Gohr $userdata = $service->getUser(); 6874b4d4a4SAndreas Gohr 6974b4d4a4SAndreas Gohr // processing 7074b4d4a4SAndreas Gohr $userdata = $this->validateUserData($userdata, $logindata['servicename']); 7174b4d4a4SAndreas Gohr $userdata = $this->processUserData($userdata, $logindata['servicename']); 7274b4d4a4SAndreas Gohr 7328002081SAndreas Gohr // store data 7428002081SAndreas Gohr $storageId = $this->getStorageId($userdata['mail']); 7528002081SAndreas Gohr $service->upgradeStorage($storageId); 7628002081SAndreas Gohr 7774b4d4a4SAndreas Gohr // login 7874b4d4a4SAndreas Gohr $session->setUser($userdata); // log in 7928002081SAndreas Gohr $session->setCookie($logindata['servicename'], $storageId); // set cookie 8074b4d4a4SAndreas Gohr 8131039e80SAndreas Gohr // redirect to the appropriate ID 8231039e80SAndreas Gohr if (!empty($logindata['id'])) { 8331039e80SAndreas Gohr send_redirect(wl($logindata['id'], [], true, '&')); 8431039e80SAndreas Gohr } 8574b4d4a4SAndreas Gohr return true; 8674b4d4a4SAndreas Gohr } 8774b4d4a4SAndreas Gohr 8874b4d4a4SAndreas Gohr /** 896d9a8a49SAndreas Gohr * Login based on user's current session data 9074b4d4a4SAndreas Gohr * 9174b4d4a4SAndreas Gohr * This will also log in plainauth users 9274b4d4a4SAndreas Gohr * 936d9a8a49SAndreas Gohr * @return bool true if successful, false if not applies 9474b4d4a4SAndreas Gohr * @throws Exception 9574b4d4a4SAndreas Gohr */ 9674b4d4a4SAndreas Gohr protected function loginBySession() 9774b4d4a4SAndreas Gohr { 9874b4d4a4SAndreas Gohr $session = Session::getInstance(); 9974b4d4a4SAndreas Gohr if (!$session->isValid()) { 10074b4d4a4SAndreas Gohr $session->clear(); 10174b4d4a4SAndreas Gohr return false; 10274b4d4a4SAndreas Gohr } 10374b4d4a4SAndreas Gohr 10474b4d4a4SAndreas Gohr $userdata = $session->getUser(); 10531039e80SAndreas Gohr if (!$userdata) return false; 10604a78b87SAndreas Gohr if (!isset($userdata['user'])) return false; // default dokuwiki does not put username here, let DW handle it 10774b4d4a4SAndreas Gohr $session->setUser($userdata, false); // does a login without resetting the time 10874b4d4a4SAndreas Gohr return true; 10974b4d4a4SAndreas Gohr } 11074b4d4a4SAndreas Gohr 11174b4d4a4SAndreas Gohr /** 1126d9a8a49SAndreas Gohr * Login based on user cookie and a previously saved access token 11374b4d4a4SAndreas Gohr * 1146d9a8a49SAndreas Gohr * @return bool true if successful, false if not applies 115c82ad624SAndreas Gohr * @throws \OAuth\Common\Exception\Exception 11674b4d4a4SAndreas Gohr */ 11774b4d4a4SAndreas Gohr protected function loginByCookie() 11874b4d4a4SAndreas Gohr { 11974b4d4a4SAndreas Gohr $session = Session::getInstance(); 12074b4d4a4SAndreas Gohr $cookie = $session->getCookie(); 12174b4d4a4SAndreas Gohr if (!$cookie) return false; 12274b4d4a4SAndreas Gohr 12374b4d4a4SAndreas Gohr $service = $this->loadService($cookie['servicename']); 12428002081SAndreas Gohr $service->initOAuthService($cookie['storageId']); 125c82ad624SAndreas Gohr 126c82ad624SAndreas Gohr // ensure that we have a current access token 1279cbef4d7SAndreas Gohr $service->refreshOutdatedToken(); 12874b4d4a4SAndreas Gohr 1296d9a8a49SAndreas Gohr // this should use a previously saved token 1306d9a8a49SAndreas Gohr $userdata = $service->getUser(); 1316d9a8a49SAndreas Gohr 1326d9a8a49SAndreas Gohr // processing 1336d9a8a49SAndreas Gohr $userdata = $this->validateUserData($userdata, $cookie['servicename']); 1346d9a8a49SAndreas Gohr $userdata = $this->processUserData($userdata, $cookie['servicename']); 1356d9a8a49SAndreas Gohr 13674b4d4a4SAndreas Gohr $session->setUser($userdata); // log in 13774b4d4a4SAndreas Gohr return true; 13874b4d4a4SAndreas Gohr } 13974b4d4a4SAndreas Gohr 1406d9a8a49SAndreas Gohr // endregion 1416d9a8a49SAndreas Gohr 14274b4d4a4SAndreas Gohr /** 14328002081SAndreas Gohr * The ID we store authentication data as 14428002081SAndreas Gohr * 14528002081SAndreas Gohr * @param string $mail 14628002081SAndreas Gohr * @return string 14728002081SAndreas Gohr */ 14828002081SAndreas Gohr protected function getStorageId($mail) 14928002081SAndreas Gohr { 15028002081SAndreas Gohr return md5($mail); 15128002081SAndreas Gohr } 15228002081SAndreas Gohr 15328002081SAndreas Gohr /** 15474b4d4a4SAndreas Gohr * Clean and validate the user data provided from the service 15574b4d4a4SAndreas Gohr * 15674b4d4a4SAndreas Gohr * @param array $userdata 15774b4d4a4SAndreas Gohr * @param string $servicename 15874b4d4a4SAndreas Gohr * @return array 15974b4d4a4SAndreas Gohr * @throws Exception 16074b4d4a4SAndreas Gohr */ 16174b4d4a4SAndreas Gohr protected function validateUserData($userdata, $servicename) 16274b4d4a4SAndreas Gohr { 16374b4d4a4SAndreas Gohr /** @var \auth_plugin_oauth */ 16474b4d4a4SAndreas Gohr global $auth; 16574b4d4a4SAndreas Gohr 16674b4d4a4SAndreas Gohr // mail is required 16774b4d4a4SAndreas Gohr if (empty($userdata['mail'])) { 16874b4d4a4SAndreas Gohr throw new Exception("$servicename did not provide the an email address. Can't log you in"); 16974b4d4a4SAndreas Gohr } 17074b4d4a4SAndreas Gohr 171e261c7e8SAndreas Gohr $userdata['mail'] = strtolower($userdata['mail']); 172e261c7e8SAndreas Gohr 17374b4d4a4SAndreas Gohr // mail needs to be allowed 17474b4d4a4SAndreas Gohr /** @var \helper_plugin_oauth $hlp */ 17574b4d4a4SAndreas Gohr $hlp = plugin_load('helper', 'oauth'); 17674b4d4a4SAndreas Gohr $hlp->checkMail($userdata['mail']); 17774b4d4a4SAndreas Gohr 17874b4d4a4SAndreas Gohr // make username from mail if empty 179*1a5ede3eSAndreas Gohr if (!isset($userdata['user'])) $userdata['user'] = ''; 18074b4d4a4SAndreas Gohr $userdata['user'] = $auth->cleanUser((string)$userdata['user']); 181*1a5ede3eSAndreas Gohr if ($userdata['user'] === '') { 18274b4d4a4SAndreas Gohr list($userdata['user']) = explode('@', $userdata['mail']); 18374b4d4a4SAndreas Gohr } 18474b4d4a4SAndreas Gohr 18574b4d4a4SAndreas Gohr // make full name from username if empty 18674b4d4a4SAndreas Gohr if (empty($userdata['name'])) { 18774b4d4a4SAndreas Gohr $userdata['name'] = $userdata['user']; 18874b4d4a4SAndreas Gohr } 18974b4d4a4SAndreas Gohr 190*1a5ede3eSAndreas Gohr // make sure groups are array and valid 191*1a5ede3eSAndreas Gohr if (!isset($userdata['grps'])) $userdata['grps'] = []; 192*1a5ede3eSAndreas Gohr $userdata['grps'] = array_map([$auth, 'cleanGroup'], (array)$userdata['grps']); 193*1a5ede3eSAndreas Gohr 19474b4d4a4SAndreas Gohr return $userdata; 19574b4d4a4SAndreas Gohr } 19674b4d4a4SAndreas Gohr 19774b4d4a4SAndreas Gohr /** 19874b4d4a4SAndreas Gohr * Process the userdata, update the user info array and create the user if necessary 19974b4d4a4SAndreas Gohr * 20074b4d4a4SAndreas Gohr * Uses the global $auth object for user management 20174b4d4a4SAndreas Gohr * 20274b4d4a4SAndreas Gohr * @param array $userdata User info received from authentication 20374b4d4a4SAndreas Gohr * @param string $servicename Auth service 20474b4d4a4SAndreas Gohr * @return array the modified user info 20574b4d4a4SAndreas Gohr * @throws Exception 20674b4d4a4SAndreas Gohr */ 20774b4d4a4SAndreas Gohr protected function processUserData($userdata, $servicename) 20874b4d4a4SAndreas Gohr { 209e170f465SAndreas Gohr /** @var \auth_plugin_oauth $auth */ 21074b4d4a4SAndreas Gohr global $auth; 21174b4d4a4SAndreas Gohr 21274b4d4a4SAndreas Gohr // see if the user is known already 21374b4d4a4SAndreas Gohr $localUser = $auth->getUserByEmail($userdata['mail']); 21474b4d4a4SAndreas Gohr if ($localUser) { 21574b4d4a4SAndreas Gohr $localUserInfo = $auth->getUserData($localUser); 21674b4d4a4SAndreas Gohr // check if the user allowed access via this service 21774b4d4a4SAndreas Gohr if (!in_array($auth->cleanGroup($servicename), $localUserInfo['grps'])) { 21874b4d4a4SAndreas Gohr throw new Exception(sprintf($auth->getLang('authnotenabled'), $servicename)); 21974b4d4a4SAndreas Gohr } 22074b4d4a4SAndreas Gohr $userdata['user'] = $localUser; 22174b4d4a4SAndreas Gohr $userdata['name'] = $localUserInfo['name']; 22274b4d4a4SAndreas Gohr $userdata['grps'] = array_merge((array)$userdata['grps'], $localUserInfo['grps']); 22374b4d4a4SAndreas Gohr } elseif (actionOK('register') || $auth->getConf('register-on-auth')) { 224e170f465SAndreas Gohr if (!$auth->registerOAuthUser($userdata, $servicename)) { 22574b4d4a4SAndreas Gohr throw new Exception('something went wrong creating your user account. please try again later.'); 22674b4d4a4SAndreas Gohr } 22774b4d4a4SAndreas Gohr } else { 22874b4d4a4SAndreas Gohr throw new Exception($auth->getLang('addUser not possible')); 22974b4d4a4SAndreas Gohr } 23074b4d4a4SAndreas Gohr 23174b4d4a4SAndreas Gohr return $userdata; 23274b4d4a4SAndreas Gohr } 23374b4d4a4SAndreas Gohr 23474b4d4a4SAndreas Gohr /** 23574b4d4a4SAndreas Gohr * Instantiates a Service by name 23674b4d4a4SAndreas Gohr * 23774b4d4a4SAndreas Gohr * @param string $servicename 23804a78b87SAndreas Gohr * @return Adapter 23974b4d4a4SAndreas Gohr * @throws Exception 24074b4d4a4SAndreas Gohr */ 24174b4d4a4SAndreas Gohr protected function loadService($servicename) 24274b4d4a4SAndreas Gohr { 24374b4d4a4SAndreas Gohr /** @var \helper_plugin_oauth $hlp */ 24474b4d4a4SAndreas Gohr $hlp = plugin_load('helper', 'oauth'); 24574b4d4a4SAndreas Gohr $srv = $hlp->loadService($servicename); 24674b4d4a4SAndreas Gohr 24774b4d4a4SAndreas Gohr if ($srv === null) throw new Exception("No such service $servicename"); 24874b4d4a4SAndreas Gohr return $srv; 24974b4d4a4SAndreas Gohr } 25074b4d4a4SAndreas Gohr 25174b4d4a4SAndreas Gohr} 252