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 // endregion 141 142 /** 143 * The ID we store authentication data as 144 * 145 * @param string $mail 146 * @return string 147 */ 148 protected function getStorageId($mail) 149 { 150 return md5($mail); 151 } 152 153 /** 154 * Clean and validate the user data provided from the service 155 * 156 * @param array $userdata 157 * @param string $servicename 158 * @return array 159 * @throws Exception 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 $userdata['mail'] = strtolower($userdata['mail']); 172 173 // mail needs to be allowed 174 /** @var \helper_plugin_oauth $hlp */ 175 $hlp = plugin_load('helper', 'oauth'); 176 $hlp->checkMail($userdata['mail']); 177 178 // make username from mail if empty 179 if (!isset($userdata['user'])) $userdata['user'] = ''; 180 $userdata['user'] = $auth->cleanUser((string)$userdata['user']); 181 if ($userdata['user'] === '') { 182 list($userdata['user']) = explode('@', $userdata['mail']); 183 } 184 185 // make full name from username if empty 186 if (empty($userdata['name'])) { 187 $userdata['name'] = $userdata['user']; 188 } 189 190 // make sure groups are array and valid 191 if (!isset($userdata['grps'])) $userdata['grps'] = []; 192 $userdata['grps'] = array_map([$auth, 'cleanGroup'], (array)$userdata['grps']); 193 194 return $userdata; 195 } 196 197 /** 198 * Process the userdata, update the user info array and create the user if necessary 199 * 200 * Uses the global $auth object for user management 201 * 202 * @param array $userdata User info received from authentication 203 * @param string $servicename Auth service 204 * @return array the modified user info 205 * @throws Exception 206 */ 207 protected function processUserData($userdata, $servicename) 208 { 209 /** @var \auth_plugin_oauth $auth */ 210 global $auth; 211 212 // see if the user is known already 213 $localUser = $auth->getUserByEmail($userdata['mail']); 214 if ($localUser) { 215 $localUserInfo = $auth->getUserData($localUser); 216 // check if the user allowed access via this service 217 if (!in_array($auth->cleanGroup($servicename), $localUserInfo['grps'])) { 218 throw new Exception(sprintf($auth->getLang('authnotenabled'), $servicename)); 219 } 220 $userdata['user'] = $localUser; 221 $userdata['name'] = $localUserInfo['name']; 222 $userdata['grps'] = array_merge((array)$userdata['grps'], $localUserInfo['grps']); 223 } elseif (actionOK('register') || $auth->getConf('register-on-auth')) { 224 if (!$auth->registerOAuthUser($userdata, $servicename)) { 225 throw new Exception('something went wrong creating your user account. please try again later.'); 226 } 227 } else { 228 throw new Exception($auth->getLang('addUser not possible')); 229 } 230 231 return $userdata; 232 } 233 234 /** 235 * Instantiates a Service by name 236 * 237 * @param string $servicename 238 * @return Adapter 239 * @throws Exception 240 */ 241 protected function loadService($servicename) 242 { 243 /** @var \helper_plugin_oauth $hlp */ 244 $hlp = plugin_load('helper', 'oauth'); 245 $srv = $hlp->loadService($servicename); 246 247 if ($srv === null) throw new Exception("No such service $servicename"); 248 return $srv; 249 } 250 251} 252