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 $session = Session::getInstance(); 22 $session->setLoginData($servicename, $ID); 23 $service = $this->loadService($servicename); 24 $service->initOAuthService(); 25 $service->login(); // redirects 26 } 27 28 /** 29 * Continues the flow from various states 30 * 31 * @return bool true if the login has been handled 32 * @throws Exception 33 * @throws \OAuth\Common\Exception\Exception 34 */ 35 public function continueFlow() 36 { 37 return $this->loginByService() or 38 $this->loginBySession() or 39 $this->loginByCookie(); 40 } 41 42 /** 43 * Second step in a explicit login, validates the oauth code 44 * 45 * @return bool true if successful, false if not applies 46 * @throws \OAuth\Common\Exception\Exception 47 */ 48 protected function loginByService() 49 { 50 global $INPUT; 51 52 if (!$INPUT->get->has('code') && !$INPUT->get->has('oauth_token')) { 53 return false; 54 } 55 56 $session = Session::getInstance(); 57 58 // init service from session 59 $logindata = $session->getLoginData(); 60 if (!$logindata) return false; 61 $service = $this->loadService($logindata['servicename']); 62 $service->initOAuthService(); 63 $session->clearLoginData(); 64 65 // oAuth login 66 if (!$service->checkToken()) throw new \OAuth\Common\Exception\Exception("Invalid Token - Login failed"); 67 $userdata = $service->getUser(); 68 69 // processing 70 $userdata = $this->validateUserData($userdata, $logindata['servicename']); 71 $userdata = $this->processUserData($userdata, $logindata['servicename']); 72 73 // store data 74 $storageId = $this->getStorageId($userdata['mail']); 75 $service->upgradeStorage($storageId); 76 77 // login 78 $session->setUser($userdata); // log in 79 $session->setCookie($logindata['servicename'], $storageId); // set cookie 80 81 // redirect to the appropriate ID 82 if (!empty($logindata['id'])) { 83 send_redirect(wl($logindata['id'], [], true, '&')); 84 } 85 return true; 86 } 87 88 /** 89 * Login based on user's current session data 90 * 91 * This will also log in plainauth users 92 * 93 * @return bool true if successful, false if not applies 94 * @throws Exception 95 */ 96 protected function loginBySession() 97 { 98 $session = Session::getInstance(); 99 if (!$session->isValid()) { 100 $session->clear(); 101 return false; 102 } 103 104 $userdata = $session->getUser(); 105 if (!$userdata) return false; 106 if (!isset($userdata['user'])) return false; // default dokuwiki does not put username here, let DW handle it 107 $session->setUser($userdata, false); // does a login without resetting the time 108 return true; 109 } 110 111 /** 112 * Login based on user cookie and a previously saved access token 113 * 114 * @return bool true if successful, false if not applies 115 * @throws \OAuth\Common\Exception\Exception 116 */ 117 protected function loginByCookie() 118 { 119 $session = Session::getInstance(); 120 $cookie = $session->getCookie(); 121 if (!$cookie) return false; 122 123 $service = $this->loadService($cookie['servicename']); 124 $service->initOAuthService($cookie['storageId']); 125 126 // ensure that we have a current access token 127 $service->refreshOutdatedToken(); 128 129 // this should use a previously saved token 130 $userdata = $service->getUser(); 131 132 // processing 133 $userdata = $this->validateUserData($userdata, $cookie['servicename']); 134 $userdata = $this->processUserData($userdata, $cookie['servicename']); 135 136 $session->setUser($userdata); // log in 137 return true; 138 } 139 140 /** 141 * Callback service's logout 142 * 143 * @return void 144 */ 145 public function logout() 146 { 147 $session = Session::getInstance(); 148 $cookie = $session->getCookie(); 149 if (!$cookie) return; 150 try { 151 $service = $this->loadService($cookie['servicename']); 152 $service->initOAuthService($cookie['storageId']); 153 $service->logout(); 154 } catch (\OAuth\Common\Exception\Exception $e) { 155 return; 156 } 157 } 158 159 // endregion 160 161 /** 162 * The ID we store authentication data as 163 * 164 * @param string $mail 165 * @return string 166 */ 167 protected function getStorageId($mail) 168 { 169 return md5($mail); 170 } 171 172 /** 173 * Clean and validate the user data provided from the service 174 * 175 * @param array $userdata 176 * @param string $servicename 177 * @return array 178 * @throws Exception 179 */ 180 protected function validateUserData($userdata, $servicename) 181 { 182 /** @var \auth_plugin_oauth */ 183 global $auth; 184 185 // mail is required 186 if (empty($userdata['mail'])) { 187 throw new Exception('noEmail', [$servicename]); 188 } 189 190 $userdata['mail'] = strtolower($userdata['mail']); 191 192 // mail needs to be allowed 193 /** @var \helper_plugin_oauth $hlp */ 194 $hlp = plugin_load('helper', 'oauth'); 195 196 if (!$hlp->checkMail($userdata['mail'])) { 197 throw new Exception('rejectedEMail', [join(', ', $hlp->getValidDomains())]); 198 } 199 200 // make username from mail if empty 201 if (!isset($userdata['user'])) $userdata['user'] = ''; 202 $userdata['user'] = $auth->cleanUser((string)$userdata['user']); 203 if ($userdata['user'] === '') { 204 list($userdata['user']) = explode('@', $userdata['mail']); 205 } 206 207 // make full name from username if empty 208 if (empty($userdata['name'])) { 209 $userdata['name'] = $userdata['user']; 210 } 211 212 // make sure groups are array and valid 213 if (!isset($userdata['grps'])) $userdata['grps'] = []; 214 $userdata['grps'] = array_map([$auth, 'cleanGroup'], (array)$userdata['grps']); 215 216 return $userdata; 217 } 218 219 /** 220 * Process the userdata, update the user info array and create the user if necessary 221 * 222 * Uses the global $auth object for user management 223 * 224 * @param array $userdata User info received from authentication 225 * @param string $servicename Auth service 226 * @return array the modified user info 227 * @throws Exception 228 */ 229 protected function processUserData($userdata, $servicename) 230 { 231 /** @var \auth_plugin_oauth $auth */ 232 global $auth; 233 234 // see if the user is known already 235 $localUser = $auth->getUserByEmail($userdata['mail']); 236 if ($localUser) { 237 $localUserInfo = $auth->getUserData($localUser); 238 // check if the user allowed access via this service 239 if (!in_array($auth->cleanGroup($servicename), $localUserInfo['grps'])) { 240 throw new Exception('authnotenabled', [$servicename]); 241 } 242 $userdata['user'] = $localUser; 243 $userdata['name'] = $localUserInfo['name']; 244 $userdata['grps'] = array_merge((array)$userdata['grps'], $localUserInfo['grps']); 245 } elseif (actionOK('register') || $auth->getConf('register-on-auth')) { 246 if (!$auth->registerOAuthUser($userdata, $servicename)) { 247 throw new Exception('generic create error'); 248 } 249 } else { 250 throw new Exception('addUser not possible'); 251 } 252 253 return $userdata; 254 } 255 256 /** 257 * Instantiates a Service by name 258 * 259 * @param string $servicename 260 * @return Adapter 261 * @throws Exception 262 */ 263 protected function loadService($servicename) 264 { 265 /** @var \helper_plugin_oauth $hlp */ 266 $hlp = plugin_load('helper', 'oauth'); 267 $srv = $hlp->loadService($servicename); 268 269 if ($srv === null) throw new Exception("No such service $servicename"); 270 return $srv; 271 } 272 273} 274