174b4d4a4SAndreas Gohr<?php 274b4d4a4SAndreas Gohr 374b4d4a4SAndreas Gohrnamespace dokuwiki\plugin\oauth; 474b4d4a4SAndreas Gohr 574b4d4a4SAndreas Gohruse OAuth\Common\Http\Exception\TokenResponseException; 674b4d4a4SAndreas Gohr 76d9a8a49SAndreas Gohr/** 86d9a8a49SAndreas Gohr * Implements the flow control for oAuth 96d9a8a49SAndreas Gohr */ 1074b4d4a4SAndreas Gohrclass OAuthManager 1174b4d4a4SAndreas Gohr{ 126d9a8a49SAndreas Gohr // region main flow 1374b4d4a4SAndreas Gohr 1474b4d4a4SAndreas Gohr /** 1531039e80SAndreas Gohr * Explicitly starts the oauth flow by redirecting to IDP 1631039e80SAndreas Gohr * 17*04a78b87SAndreas Gohr * @throws \OAuth\Common\Exception\Exception 1874b4d4a4SAndreas Gohr */ 1974b4d4a4SAndreas Gohr public function startFlow($servicename) 2074b4d4a4SAndreas Gohr { 2131039e80SAndreas Gohr global $ID; 2231039e80SAndreas Gohr 2374b4d4a4SAndreas Gohr // generate a new GUID to identify this user 24*04a78b87SAndreas Gohr try { 2574b4d4a4SAndreas Gohr $guid = bin2hex(random_bytes(16)); 26*04a78b87SAndreas Gohr } catch (\Exception $e) { 27*04a78b87SAndreas Gohr throw new \OAuth\Common\Exception\Exception($e->getMessage()); 28*04a78b87SAndreas Gohr } 2974b4d4a4SAndreas Gohr 3074b4d4a4SAndreas Gohr $session = Session::getInstance(); 3131039e80SAndreas Gohr $session->setLoginData($servicename, $guid, $ID); 3274b4d4a4SAndreas Gohr $service = $this->loadService($servicename); 3374b4d4a4SAndreas Gohr $service->initOAuthService($guid); 3474b4d4a4SAndreas Gohr $service->login(); // redirects 3574b4d4a4SAndreas Gohr } 3674b4d4a4SAndreas Gohr 3774b4d4a4SAndreas Gohr /** 3831039e80SAndreas Gohr * Continues the flow from various states 3931039e80SAndreas Gohr * 4074b4d4a4SAndreas Gohr * @return bool true if the login has been handled 4174b4d4a4SAndreas Gohr * @throws Exception 4274b4d4a4SAndreas Gohr * @throws \OAuth\Common\Exception\Exception 4374b4d4a4SAndreas Gohr */ 4474b4d4a4SAndreas Gohr public function continueFlow() 4574b4d4a4SAndreas Gohr { 4674b4d4a4SAndreas Gohr return $this->loginByService() or 4774b4d4a4SAndreas Gohr $this->loginBySession() or 4874b4d4a4SAndreas Gohr $this->loginByCookie(); 4974b4d4a4SAndreas Gohr } 5074b4d4a4SAndreas Gohr 5174b4d4a4SAndreas Gohr /** 526d9a8a49SAndreas Gohr * Second step in a explicit login, validates the oauth code 536d9a8a49SAndreas Gohr * 546d9a8a49SAndreas Gohr * @return bool true if successful, false if not applies 5574b4d4a4SAndreas Gohr * @throws \OAuth\Common\Exception\Exception 5674b4d4a4SAndreas Gohr */ 5774b4d4a4SAndreas Gohr protected function loginByService() 5874b4d4a4SAndreas Gohr { 5974b4d4a4SAndreas Gohr global $INPUT; 6074b4d4a4SAndreas Gohr 6174b4d4a4SAndreas Gohr if (!$INPUT->get->has('code') && !$INPUT->get->has('oauth_token')) { 6274b4d4a4SAndreas Gohr return false; 6374b4d4a4SAndreas Gohr } 6474b4d4a4SAndreas Gohr 6574b4d4a4SAndreas Gohr $session = Session::getInstance(); 6674b4d4a4SAndreas Gohr 6774b4d4a4SAndreas Gohr // init service from session 6874b4d4a4SAndreas Gohr $logindata = $session->getLoginData(); 6974b4d4a4SAndreas Gohr if (!$logindata) return false; 7074b4d4a4SAndreas Gohr $service = $this->loadService($logindata['servicename']); 7174b4d4a4SAndreas Gohr $service->initOAuthService($logindata['guid']); 7274b4d4a4SAndreas Gohr $session->clearLoginData(); 7374b4d4a4SAndreas Gohr 7474b4d4a4SAndreas Gohr // oAuth login 75*04a78b87SAndreas Gohr if (!$service->checkToken()) throw new \OAuth\Common\Exception\Exception("Invalid Token - Login failed"); 7674b4d4a4SAndreas Gohr $userdata = $service->getUser(); 7774b4d4a4SAndreas Gohr 7874b4d4a4SAndreas Gohr // processing 7974b4d4a4SAndreas Gohr $userdata = $this->validateUserData($userdata, $logindata['servicename']); 8074b4d4a4SAndreas Gohr $userdata = $this->processUserData($userdata, $logindata['servicename']); 8174b4d4a4SAndreas Gohr 8274b4d4a4SAndreas Gohr // login 8374b4d4a4SAndreas Gohr $session->setUser($userdata); // log in 8474b4d4a4SAndreas Gohr $session->setCookie($logindata['servicename'], $logindata['guid']); // set cookie 8574b4d4a4SAndreas Gohr 8631039e80SAndreas Gohr // redirect to the appropriate ID 8731039e80SAndreas Gohr if (!empty($logindata['id'])) { 8831039e80SAndreas Gohr send_redirect(wl($logindata['id'], [], true, '&')); 8931039e80SAndreas Gohr } 9074b4d4a4SAndreas Gohr return true; 9174b4d4a4SAndreas Gohr } 9274b4d4a4SAndreas Gohr 9374b4d4a4SAndreas Gohr /** 946d9a8a49SAndreas Gohr * Login based on user's current session data 9574b4d4a4SAndreas Gohr * 9674b4d4a4SAndreas Gohr * This will also log in plainauth users 9774b4d4a4SAndreas Gohr * 986d9a8a49SAndreas Gohr * @return bool true if successful, false if not applies 9974b4d4a4SAndreas Gohr * @throws Exception 10074b4d4a4SAndreas Gohr */ 10174b4d4a4SAndreas Gohr protected function loginBySession() 10274b4d4a4SAndreas Gohr { 10374b4d4a4SAndreas Gohr $session = Session::getInstance(); 10474b4d4a4SAndreas Gohr if (!$session->isValid()) { 10574b4d4a4SAndreas Gohr $session->clear(); 10674b4d4a4SAndreas Gohr return false; 10774b4d4a4SAndreas Gohr } 10874b4d4a4SAndreas Gohr 10974b4d4a4SAndreas Gohr $userdata = $session->getUser(); 11031039e80SAndreas Gohr if (!$userdata) return false; 111*04a78b87SAndreas Gohr if (!isset($userdata['user'])) return false; // default dokuwiki does not put username here, let DW handle it 11274b4d4a4SAndreas Gohr $session->setUser($userdata, false); // does a login without resetting the time 11374b4d4a4SAndreas Gohr return true; 11474b4d4a4SAndreas Gohr } 11574b4d4a4SAndreas Gohr 11674b4d4a4SAndreas Gohr /** 1176d9a8a49SAndreas Gohr * Login based on user cookie and a previously saved access token 11874b4d4a4SAndreas Gohr * 1196d9a8a49SAndreas Gohr * @return bool true if successful, false if not applies 12074b4d4a4SAndreas Gohr * @throws Exception 12174b4d4a4SAndreas Gohr */ 12274b4d4a4SAndreas Gohr protected function loginByCookie() 12374b4d4a4SAndreas Gohr { 12474b4d4a4SAndreas Gohr $session = Session::getInstance(); 12574b4d4a4SAndreas Gohr $cookie = $session->getCookie(); 12674b4d4a4SAndreas Gohr if (!$cookie) return false; 12774b4d4a4SAndreas Gohr 12874b4d4a4SAndreas Gohr try { 12974b4d4a4SAndreas Gohr $service = $this->loadService($cookie['servicename']); 13074b4d4a4SAndreas Gohr $service->initOAuthService($cookie['guid']); 131*04a78b87SAndreas Gohr } catch (\OAuth\Common\Exception\Exception $e) { 13274b4d4a4SAndreas Gohr return false; // maybe cookie had old service that is no longer available 13374b4d4a4SAndreas Gohr } 13474b4d4a4SAndreas Gohr 1356d9a8a49SAndreas Gohr // this should use a previously saved token 1366d9a8a49SAndreas Gohr $userdata = $service->getUser(); 1376d9a8a49SAndreas Gohr 1386d9a8a49SAndreas Gohr // processing 1396d9a8a49SAndreas Gohr $userdata = $this->validateUserData($userdata, $cookie['servicename']); 1406d9a8a49SAndreas Gohr $userdata = $this->processUserData($userdata, $cookie['servicename']); 1416d9a8a49SAndreas Gohr 14274b4d4a4SAndreas Gohr $session->setUser($userdata); // log in 14374b4d4a4SAndreas Gohr return true; 14474b4d4a4SAndreas Gohr } 14574b4d4a4SAndreas Gohr 1466d9a8a49SAndreas Gohr // endregion 1476d9a8a49SAndreas Gohr 14874b4d4a4SAndreas Gohr /** 14974b4d4a4SAndreas Gohr * Clean and validate the user data provided from the service 15074b4d4a4SAndreas Gohr * 15174b4d4a4SAndreas Gohr * @param array $userdata 15274b4d4a4SAndreas Gohr * @param string $servicename 15374b4d4a4SAndreas Gohr * @return array 15474b4d4a4SAndreas Gohr * @throws Exception 15574b4d4a4SAndreas Gohr * @todo test 15674b4d4a4SAndreas Gohr */ 15774b4d4a4SAndreas Gohr protected function validateUserData($userdata, $servicename) 15874b4d4a4SAndreas Gohr { 15974b4d4a4SAndreas Gohr /** @var \auth_plugin_oauth */ 16074b4d4a4SAndreas Gohr global $auth; 16174b4d4a4SAndreas Gohr 16274b4d4a4SAndreas Gohr // mail is required 16374b4d4a4SAndreas Gohr if (empty($userdata['mail'])) { 16474b4d4a4SAndreas Gohr throw new Exception("$servicename did not provide the an email address. Can't log you in"); 16574b4d4a4SAndreas Gohr } 16674b4d4a4SAndreas Gohr 16774b4d4a4SAndreas Gohr // mail needs to be allowed 16874b4d4a4SAndreas Gohr /** @var \helper_plugin_oauth $hlp */ 16974b4d4a4SAndreas Gohr $hlp = plugin_load('helper', 'oauth'); 17074b4d4a4SAndreas Gohr $hlp->checkMail($userdata['mail']); 17174b4d4a4SAndreas Gohr 17274b4d4a4SAndreas Gohr // make username from mail if empty 17374b4d4a4SAndreas Gohr $userdata['user'] = $auth->cleanUser((string)$userdata['user']); 17474b4d4a4SAndreas Gohr if ($userdata === '') { 17574b4d4a4SAndreas Gohr list($userdata['user']) = explode('@', $userdata['mail']); 17674b4d4a4SAndreas Gohr } 17774b4d4a4SAndreas Gohr 17874b4d4a4SAndreas Gohr // make full name from username if empty 17974b4d4a4SAndreas Gohr if (empty($userdata['name'])) { 18074b4d4a4SAndreas Gohr $userdata['name'] = $userdata['user']; 18174b4d4a4SAndreas Gohr } 18274b4d4a4SAndreas Gohr 18374b4d4a4SAndreas Gohr return $userdata; 18474b4d4a4SAndreas Gohr } 18574b4d4a4SAndreas Gohr 18674b4d4a4SAndreas Gohr /** 18774b4d4a4SAndreas Gohr * Process the userdata, update the user info array and create the user if necessary 18874b4d4a4SAndreas Gohr * 18974b4d4a4SAndreas Gohr * Uses the global $auth object for user management 19074b4d4a4SAndreas Gohr * 19174b4d4a4SAndreas Gohr * @param array $userdata User info received from authentication 19274b4d4a4SAndreas Gohr * @param string $servicename Auth service 19374b4d4a4SAndreas Gohr * @return array the modified user info 19474b4d4a4SAndreas Gohr * @throws Exception 19574b4d4a4SAndreas Gohr */ 19674b4d4a4SAndreas Gohr protected function processUserData($userdata, $servicename) 19774b4d4a4SAndreas Gohr { 198e170f465SAndreas Gohr /** @var \auth_plugin_oauth $auth */ 19974b4d4a4SAndreas Gohr global $auth; 20074b4d4a4SAndreas Gohr 20174b4d4a4SAndreas Gohr // see if the user is known already 20274b4d4a4SAndreas Gohr $localUser = $auth->getUserByEmail($userdata['mail']); 20374b4d4a4SAndreas Gohr if ($localUser) { 20474b4d4a4SAndreas Gohr $localUserInfo = $auth->getUserData($localUser); 20574b4d4a4SAndreas Gohr // check if the user allowed access via this service 20674b4d4a4SAndreas Gohr if (!in_array($auth->cleanGroup($servicename), $localUserInfo['grps'])) { 20774b4d4a4SAndreas Gohr throw new Exception(sprintf($auth->getLang('authnotenabled'), $servicename)); 20874b4d4a4SAndreas Gohr } 20974b4d4a4SAndreas Gohr $userdata['user'] = $localUser; 21074b4d4a4SAndreas Gohr $userdata['name'] = $localUserInfo['name']; 21174b4d4a4SAndreas Gohr $userdata['grps'] = array_merge((array)$userdata['grps'], $localUserInfo['grps']); 21274b4d4a4SAndreas Gohr } elseif (actionOK('register') || $auth->getConf('register-on-auth')) { 213e170f465SAndreas Gohr if (!$auth->registerOAuthUser($userdata, $servicename)) { 21474b4d4a4SAndreas Gohr throw new Exception('something went wrong creating your user account. please try again later.'); 21574b4d4a4SAndreas Gohr } 21674b4d4a4SAndreas Gohr } else { 21774b4d4a4SAndreas Gohr throw new Exception($auth->getLang('addUser not possible')); 21874b4d4a4SAndreas Gohr } 21974b4d4a4SAndreas Gohr 22074b4d4a4SAndreas Gohr return $userdata; 22174b4d4a4SAndreas Gohr } 22274b4d4a4SAndreas Gohr 22374b4d4a4SAndreas Gohr /** 22474b4d4a4SAndreas Gohr * Instantiates a Service by name 22574b4d4a4SAndreas Gohr * 22674b4d4a4SAndreas Gohr * @param string $servicename 227*04a78b87SAndreas Gohr * @return Adapter 22874b4d4a4SAndreas Gohr * @throws Exception 22974b4d4a4SAndreas Gohr */ 23074b4d4a4SAndreas Gohr protected function loadService($servicename) 23174b4d4a4SAndreas Gohr { 23274b4d4a4SAndreas Gohr /** @var \helper_plugin_oauth $hlp */ 23374b4d4a4SAndreas Gohr $hlp = plugin_load('helper', 'oauth'); 23474b4d4a4SAndreas Gohr $srv = $hlp->loadService($servicename); 23574b4d4a4SAndreas Gohr 23674b4d4a4SAndreas Gohr if ($srv === null) throw new Exception("No such service $servicename"); 23774b4d4a4SAndreas Gohr return $srv; 23874b4d4a4SAndreas Gohr } 23974b4d4a4SAndreas Gohr 24074b4d4a4SAndreas Gohr} 241