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 $service->refreshOutdatedToken(); 131 132 // this should use a previously saved token 133 $userdata = $service->getUser(); 134 135 // processing 136 $userdata = $this->validateUserData($userdata, $cookie['servicename']); 137 $userdata = $this->processUserData($userdata, $cookie['servicename']); 138 139 $session->setUser($userdata); // log in 140 return true; 141 } 142 143 // endregion 144 145 /** 146 * Clean and validate the user data provided from the service 147 * 148 * @param array $userdata 149 * @param string $servicename 150 * @return array 151 * @throws Exception 152 * @todo test 153 */ 154 protected function validateUserData($userdata, $servicename) 155 { 156 /** @var \auth_plugin_oauth */ 157 global $auth; 158 159 // mail is required 160 if (empty($userdata['mail'])) { 161 throw new Exception("$servicename did not provide the an email address. Can't log you in"); 162 } 163 164 // mail needs to be allowed 165 /** @var \helper_plugin_oauth $hlp */ 166 $hlp = plugin_load('helper', 'oauth'); 167 $hlp->checkMail($userdata['mail']); 168 169 // make username from mail if empty 170 $userdata['user'] = $auth->cleanUser((string)$userdata['user']); 171 if ($userdata === '') { 172 list($userdata['user']) = explode('@', $userdata['mail']); 173 } 174 175 // make full name from username if empty 176 if (empty($userdata['name'])) { 177 $userdata['name'] = $userdata['user']; 178 } 179 180 return $userdata; 181 } 182 183 /** 184 * Process the userdata, update the user info array and create the user if necessary 185 * 186 * Uses the global $auth object for user management 187 * 188 * @param array $userdata User info received from authentication 189 * @param string $servicename Auth service 190 * @return array the modified user info 191 * @throws Exception 192 */ 193 protected function processUserData($userdata, $servicename) 194 { 195 /** @var \auth_plugin_oauth $auth */ 196 global $auth; 197 198 // see if the user is known already 199 $localUser = $auth->getUserByEmail($userdata['mail']); 200 if ($localUser) { 201 $localUserInfo = $auth->getUserData($localUser); 202 // check if the user allowed access via this service 203 if (!in_array($auth->cleanGroup($servicename), $localUserInfo['grps'])) { 204 throw new Exception(sprintf($auth->getLang('authnotenabled'), $servicename)); 205 } 206 $userdata['user'] = $localUser; 207 $userdata['name'] = $localUserInfo['name']; 208 $userdata['grps'] = array_merge((array)$userdata['grps'], $localUserInfo['grps']); 209 } elseif (actionOK('register') || $auth->getConf('register-on-auth')) { 210 if (!$auth->registerOAuthUser($userdata, $servicename)) { 211 throw new Exception('something went wrong creating your user account. please try again later.'); 212 } 213 } else { 214 throw new Exception($auth->getLang('addUser not possible')); 215 } 216 217 return $userdata; 218 } 219 220 /** 221 * Instantiates a Service by name 222 * 223 * @param string $servicename 224 * @return Adapter 225 * @throws Exception 226 */ 227 protected function loadService($servicename) 228 { 229 /** @var \helper_plugin_oauth $hlp */ 230 $hlp = plugin_load('helper', 'oauth'); 231 $srv = $hlp->loadService($servicename); 232 233 if ($srv === null) throw new Exception("No such service $servicename"); 234 return $srv; 235 } 236 237} 238