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