1<?php 2 3namespace dokuwiki\plugin\oauth; 4 5/** 6 * Implements the flow control for oAuth 7 */ 8class OAuthManager 9{ 10 // region main flow 11 12 /** 13 * Explicitly starts the oauth flow by redirecting to IDP 14 * 15 * @throws \OAuth\Common\Exception\Exception 16 */ 17 public function startFlow($servicename) 18 { 19 global $ID; 20 21 // generate a new GUID to identify this user 22 try { 23 $guid = bin2hex(random_bytes(16)); 24 } catch (\Exception $e) { 25 throw new \OAuth\Common\Exception\Exception($e->getMessage()); 26 } 27 28 $session = Session::getInstance(); 29 $session->setLoginData($servicename, $guid, $ID); 30 $service = $this->loadService($servicename); 31 $service->initOAuthService($guid); 32 $service->login(); // redirects 33 } 34 35 /** 36 * Continues the flow from various states 37 * 38 * @return bool true if the login has been handled 39 * @throws Exception 40 * @throws \OAuth\Common\Exception\Exception 41 */ 42 public function continueFlow() 43 { 44 return $this->loginByService() or 45 $this->loginBySession() or 46 $this->loginByCookie(); 47 } 48 49 /** 50 * Second step in a explicit login, validates the oauth code 51 * 52 * @return bool true if successful, false if not applies 53 * @throws \OAuth\Common\Exception\Exception 54 */ 55 protected function loginByService() 56 { 57 global $INPUT; 58 59 if (!$INPUT->get->has('code') && !$INPUT->get->has('oauth_token')) { 60 return false; 61 } 62 63 $session = Session::getInstance(); 64 65 // init service from session 66 $logindata = $session->getLoginData(); 67 if (!$logindata) return false; 68 $service = $this->loadService($logindata['servicename']); 69 $service->initOAuthService($logindata['guid']); 70 $session->clearLoginData(); 71 72 // oAuth login 73 if (!$service->checkToken()) throw new \OAuth\Common\Exception\Exception("Invalid Token - Login failed"); 74 $userdata = $service->getUser(); 75 76 // processing 77 $userdata = $this->validateUserData($userdata, $logindata['servicename']); 78 $userdata = $this->processUserData($userdata, $logindata['servicename']); 79 80 // login 81 $session->setUser($userdata); // log in 82 $session->setCookie($logindata['servicename'], $logindata['guid']); // set cookie 83 84 // redirect to the appropriate ID 85 if (!empty($logindata['id'])) { 86 send_redirect(wl($logindata['id'], [], true, '&')); 87 } 88 return true; 89 } 90 91 /** 92 * Login based on user's current session data 93 * 94 * This will also log in plainauth users 95 * 96 * @return bool true if successful, false if not applies 97 * @throws Exception 98 */ 99 protected function loginBySession() 100 { 101 $session = Session::getInstance(); 102 if (!$session->isValid()) { 103 $session->clear(); 104 return false; 105 } 106 107 $userdata = $session->getUser(); 108 if (!$userdata) return false; 109 if (!isset($userdata['user'])) return false; // default dokuwiki does not put username here, let DW handle it 110 $session->setUser($userdata, false); // does a login without resetting the time 111 return true; 112 } 113 114 /** 115 * Login based on user cookie and a previously saved access token 116 * 117 * @return bool true if successful, false if not applies 118 * @throws \OAuth\Common\Exception\Exception 119 */ 120 protected function loginByCookie() 121 { 122 $session = Session::getInstance(); 123 $cookie = $session->getCookie(); 124 if (!$cookie) return false; 125 126 $service = $this->loadService($cookie['servicename']); 127 $service->initOAuthService($cookie['guid']); 128 129 // ensure that we have a current access token 130 $oauth = $service->getOAuthService(); 131 if (!$oauth->getStorage()->hasAccessToken($oauth->service())) return false; 132 $accessToken = $oauth->getStorage()->retrieveAccessToken($oauth->service()); 133 if ($accessToken->getEndOfLife() > 0 && 134 $accessToken->getEndOfLife() - time() < 3600 && 135 $accessToken->getRefreshToken()) { 136 $oauth->refreshAccessToken($accessToken); 137 } 138 139 // this should use a previously saved token 140 $userdata = $service->getUser(); 141 142 // processing 143 $userdata = $this->validateUserData($userdata, $cookie['servicename']); 144 $userdata = $this->processUserData($userdata, $cookie['servicename']); 145 146 $session->setUser($userdata); // log in 147 return true; 148 } 149 150 // endregion 151 152 /** 153 * Clean and validate the user data provided from the service 154 * 155 * @param array $userdata 156 * @param string $servicename 157 * @return array 158 * @throws Exception 159 * @todo test 160 */ 161 protected function validateUserData($userdata, $servicename) 162 { 163 /** @var \auth_plugin_oauth */ 164 global $auth; 165 166 // mail is required 167 if (empty($userdata['mail'])) { 168 throw new Exception("$servicename did not provide the an email address. Can't log you in"); 169 } 170 171 // mail needs to be allowed 172 /** @var \helper_plugin_oauth $hlp */ 173 $hlp = plugin_load('helper', 'oauth'); 174 $hlp->checkMail($userdata['mail']); 175 176 // make username from mail if empty 177 $userdata['user'] = $auth->cleanUser((string)$userdata['user']); 178 if ($userdata === '') { 179 list($userdata['user']) = explode('@', $userdata['mail']); 180 } 181 182 // make full name from username if empty 183 if (empty($userdata['name'])) { 184 $userdata['name'] = $userdata['user']; 185 } 186 187 return $userdata; 188 } 189 190 /** 191 * Process the userdata, update the user info array and create the user if necessary 192 * 193 * Uses the global $auth object for user management 194 * 195 * @param array $userdata User info received from authentication 196 * @param string $servicename Auth service 197 * @return array the modified user info 198 * @throws Exception 199 */ 200 protected function processUserData($userdata, $servicename) 201 { 202 /** @var \auth_plugin_oauth $auth */ 203 global $auth; 204 205 // see if the user is known already 206 $localUser = $auth->getUserByEmail($userdata['mail']); 207 if ($localUser) { 208 $localUserInfo = $auth->getUserData($localUser); 209 // check if the user allowed access via this service 210 if (!in_array($auth->cleanGroup($servicename), $localUserInfo['grps'])) { 211 throw new Exception(sprintf($auth->getLang('authnotenabled'), $servicename)); 212 } 213 $userdata['user'] = $localUser; 214 $userdata['name'] = $localUserInfo['name']; 215 $userdata['grps'] = array_merge((array)$userdata['grps'], $localUserInfo['grps']); 216 } elseif (actionOK('register') || $auth->getConf('register-on-auth')) { 217 if (!$auth->registerOAuthUser($userdata, $servicename)) { 218 throw new Exception('something went wrong creating your user account. please try again later.'); 219 } 220 } else { 221 throw new Exception($auth->getLang('addUser not possible')); 222 } 223 224 return $userdata; 225 } 226 227 /** 228 * Instantiates a Service by name 229 * 230 * @param string $servicename 231 * @return Adapter 232 * @throws Exception 233 */ 234 protected function loadService($servicename) 235 { 236 /** @var \helper_plugin_oauth $hlp */ 237 $hlp = plugin_load('helper', 'oauth'); 238 $srv = $hlp->loadService($servicename); 239 240 if ($srv === null) throw new Exception("No such service $servicename"); 241 return $srv; 242 } 243 244} 245