1<?php 2 3/** 4 * Class helper_plugin_sfauth 5 * 6 * Represents a single oAuth authenticated SalesForce User 7 */ 8class helper_plugin_sfauth extends DokuWiki_Plugin { 9 10 /** @var string current user to authenticate */ 11 protected $user = null; 12 13 /** @var array user data for above user */ 14 protected $userdata = null; 15 16 /** @var array authentication data for above user */ 17 protected $authdata = null; 18 19 /** @var int salesforce instance to use */ 20 protected $instance = 1; 21 22 /** 23 * Each Instantiated plugin is it's own user 24 * 25 * @return false 26 */ 27 public function isSingleton() { 28 return false; 29 } 30 31 /** 32 * The local URL that handles all the oAuth flow 33 * 34 * This is the URL that has to be configured in Salesforce 35 * 36 * @param int $instance the salesforce configuration instance to use (1 to 3) 37 * @return string 38 */ 39 public static function getLoginURL($instance) { 40 $instance = (int) $instance; 41 if($instance < 1 || $instance > 3) $instance = 1; 42 return DOKU_URL . DOKU_SCRIPT . '?do=login&u=sf&p=sf&sf='.$instance; 43 } 44 45 /** 46 * Get the current user 47 * 48 * @return bool|string 49 */ 50 public function getUser() { 51 if(is_null($this->user)) return false; 52 if(is_null($this->userdata)) return false; 53 return $this->user; 54 } 55 56 /** 57 * Get the user's data 58 * 59 * @return bool|array 60 */ 61 public function getUserData() { 62 if(is_null($this->userdata)) return false; 63 return $this->userdata; 64 } 65 66 /** 67 * Initialize the user object by the given user name 68 * 69 * @param $user 70 * @return bool true if the user was found, false otherwise 71 */ 72 public function init_by_user($user) { 73 try { 74 $this->loadFromFile($user); 75 return true; 76 } catch (Exception $e) { 77 return false; 78 } 79 } 80 81 /** 82 * Initialize the user by starting an oAuth flow 83 * 84 * @param int $instance Salesforce config instance 85 * @return bool true if the oAuth flow has completed successfully, false on error 86 */ 87 public function init_by_oauth($instance) { 88 global $INPUT; 89 global $ID; 90 $instance = (int) $instance; 91 if($instance < 1 || $instance > 3) $instance = 1; 92 $this->instance = $instance; 93 94 // login directly from Saleforce 95 if($INPUT->get->str('user') && $INPUT->get->str('sessionId')) { 96 if($this->oauth_directlogin($INPUT->get->str('user'), $INPUT->get->str('sessionId'), $INPUT->get->str('instance'))) { 97 if($this->loadUserDataFromSalesForce()) { 98 if($this->saveToFile()) { 99 $log = array('message' => 'logged in via Salesforce', 'user' => $this->user); 100 trigger_event('PLUGIN_LOGLOG_LOG',$log); 101 msg('Authentication successful', 1); 102 return true; 103 } 104 } 105 } 106 msg('Oops! something went wrong.', -1); 107 return false; 108 } 109 110 // oAuth step 2: request auth token 111 if($INPUT->get->str('code')) { 112 if($this->oauth_finish($INPUT->get->str('code'), $instance)) { 113 if($this->loadUserDataFromSalesForce()) { 114 if($this->saveToFile()) { 115 $log = array('message' => 'logged in via Salesforce', 'user' => $this->user); 116 trigger_event('PLUGIN_LOGLOG_LOG',$log); 117 msg('Authentication successful', 1); 118 return true; 119 } 120 } 121 } 122 msg('Oops! something went wrong.', -1); 123 return false; 124 } 125 126 // remember the page start started the login 127 $_SESSION['sfauth_id'] = getID(); 128 129 // oAuth step 1: redirect to salesforce 130 $this->oauth_start($this->instance); 131 return false; // will not be reached 132 } 133 134 /** 135 * Execute an API call with the current author 136 */ 137 public function apicall($method, $endpoint, $data = array(), $usejson = true) { 138 if(!$this->authdata) throw new Exception('No auth data to make API call'); 139 140 $json = new JSON(JSON_LOOSE_TYPE); 141 $url = $this->authdata['instance_url'] . '/services/data/v24.0' . $endpoint; 142 143 $http = new DokuHTTPClient(); 144 $http->timeout = 30; 145 $http->headers['Authorization'] = $this->authdata['access_token']; 146 $http->headers['Accept'] = 'application/json'; 147 $http->headers['X-PrettyPrint'] = '1'; 148 149 //$http->debug = 1; 150 151 if($data) { 152 if($usejson) { 153 $data = $json->encode($data); 154 $http->headers['Content-Type'] = 'application/json'; 155 } 156 // else default to standard POST encoding 157 } 158 $http->sendRequest($url, $data, $method); 159 if(!$http->resp_body) { 160 dbglog('err call' . print_r($http, true), 'sfauth'); 161 return false; 162 } 163 $resp = $json->decode($http->resp_body); 164 165 // session expired, request a new one and retry 166 if($resp[0]['errorCode'] == 'INVALID_SESSION_ID') { 167 if($this->oauth_refresh()) { 168 return $this->apicall($method, $endpoint, $data); 169 } else { 170 return false; 171 } 172 } 173 174 if($http->status < 200 || $http->status > 399) { 175 dbglog('err call' . print_r($http, true), 'sfauth'); 176 return false; 177 } 178 179 return $resp; 180 } 181 182 /** 183 * Initialize the OAuth process 184 * 185 * by redirecting the user to the login site 186 * @link http://bit.ly/y7WOmy 187 */ 188 protected function oauth_start($instance) { 189 global $ID; 190 $instance = (int) $instance; 191 if($instance < 1 || $instance > 3) $instance = 1; 192 $this->instance = $instance; 193 194 $_SESSION['sfauth_redirect'] = $ID; // where wanna go later 195 196 $data = array( 197 'response_type' => 'code', 198 'client_id' => $this->getConf('consumer key'), 199 'redirect_uri' => self::getLoginURL($this->instance), 200 'display' => 'page', // may popup 201 ); 202 203 $url = $this->getConf('auth url') . '/services/oauth2/authorize?' . buildURLparams($data, '&'); 204 send_redirect($url); 205 } 206 207 /** 208 * Request an authentication code with the given request token 209 * 210 * @param string $code request token 211 * @param int $instance Salesforce instance to authenticate with 212 * @return bool 213 */ 214 protected function oauth_finish($code, $instance) { 215 $instance = (int) $instance; 216 if($instance < 1 || $instance > 3) $instance = 1; 217 $this->instance = $instance; 218 219 /* 220 * request the authdata with the code 221 */ 222 $data = array( 223 'code' => $code, 224 'grant_type' => 'authorization_code', 225 'client_id' => $this->getIConf('consumer key', $this->instance), 226 'client_secret' => $this->getIConf('consumer secret', $this->instance), 227 'redirect_uri' => self::getLoginURL($this->instance) 228 ); 229 230 $url = $this->getConf('auth url') . '/services/oauth2/token'; 231 $http = new DokuHTTPClient(); 232 $http->headers['Accept'] = 'application/json'; 233 $resp = $http->post($url, $data); 234 235 if($resp === false) return false; 236 237 $json = new JSON(JSON_LOOSE_TYPE); 238 $resp = $json->decode($resp); 239 $resp['access_token'] = 'OAuth ' . $resp['access_token']; 240 241 $this->authdata = $resp; 242 return true; 243 } 244 245 /** 246 * request a new auth key 247 */ 248 protected function oauth_refresh() { 249 if(!$this->authdata) throw new Exception('No auth data to refresh oauth token'); 250 if(!isset($this->authdata['refresh_token'])) { 251 return false; 252 } 253 $data = array( 254 'grant_type' => 'refresh_token', 255 'refresh_token' => $this->authdata['refresh_token'], 256 'client_id' => $this->getIConf('consumer key', $this->instance), 257 'client_secret' => $this->getIConf('consumer secret', $this->instance) 258 ); 259 260 $url = $this->getConf('auth url') . '/services/oauth2/token?' . buildURLparams($data, '&'); 261 $http = new DokuHTTPClient(); 262 $http->headers['Accept'] = 'application/json'; 263 $resp = $http->post($url, array()); 264 if($resp === false) return false; 265 $json = new JSON(JSON_LOOSE_TYPE); 266 267 $resp = $json->decode($resp); 268 $this->authdata = $resp; 269 270 return $this->saveToFile(); 271 } 272 273 /** 274 * Does a direct login by setting the given sessionID as access token 275 * 276 * @param string $user 277 * @param string $sessionId 278 * @param string $instanceurl 279 * @return bool 280 */ 281 protected function oauth_directlogin($user, $sessionId, $instanceurl) { 282 $url = parse_url($instanceurl); 283 $this->authdata = array( 284 'id' => $user, 285 'instance_url' => sprintf('%s://%s', $url['scheme'], $url['host']), 286 'access_token' => 'Bearer ' . $sessionId 287 ); 288 return true; 289 } 290 291 /** 292 * Load current user's data into memory cache 293 * 294 * @return bool 295 */ 296 protected function loadUserDataFromSalesForce() { 297 global $conf; 298 $id = preg_replace('/^.*\//', '', $this->authdata['id']); 299 300 $resp = $this->apicall('GET', '/sobjects/User/' . rawurlencode($id)); 301 if(!$resp) return false; 302 303 $this->userdata = array( 304 'name' => $resp['Name'], 305 'mail' => $resp['Email'], 306 'grps' => explode(';', $resp['DokuWiki_Groups__c']), 307 'sfid' => $resp['Id'] 308 ); 309 310 // add instance as group and default group 311 $this->userdata['grps'][] = 'salesforce'.$this->instance; 312 $this->userdata['grps'][] = $conf['defaultgroup']; 313 314 $this->userdata['grps'] = array_unique($this->userdata['grps']); 315 $this->userdata['grps'] = array_filter($this->userdata['grps']); 316 317 $this->user = $this->transformMailToId($this->userdata['mail']); 318 return true; 319 } 320 321 /** 322 * Transforms a mail to ID 323 * 324 * @todo put this in the getUser() function 325 * @param $mail 326 * @return mixed 327 */ 328 protected function transformMailToId($mail) { 329 if(!strpos($mail, '@')) { 330 return $mail; 331 } 332 333 $ownerDomain = $this->getConf('owner domain'); 334 if(empty($ownerDomain)) { 335 return $mail; 336 } 337 338 $newMail = preg_replace('/' . preg_quote('@' . $ownerDomain, '/') . '$/i', '', $mail); 339 340 return $newMail; 341 } 342 343 /** 344 * Load user and auth data from local files 345 * 346 * @param $user 347 * @return bool 348 * @throws Exception 349 */ 350 protected function loadFromFile($user) { 351 $userdata = getCacheName($user,'.sfuser'); 352 $authdata = getCacheName($user,'.sfauth'); 353 354 if(file_exists($userdata)) { 355 $this->userdata = unserialize(io_readFile($userdata, false)); 356 } else { 357 throw new Exception('No such user'); 358 } 359 360 if(file_exists($authdata)) { 361 $this->authdata = unserialize(io_readFile($authdata, false)); 362 $this->instance = $this->authdata['dokuwiki-instance']; 363 } else { 364 throw new Exception('No such user'); 365 } 366 367 $this->user = $user; 368 return true; 369 } 370 371 /** 372 * Store user and auth data to local files 373 * 374 * @throws Exception 375 * @return bool 376 */ 377 protected function saveToFile() { 378 if(!$this->user) throw new Exception('No user info to save'); 379 380 $this->authdata['dokuwiki-instance'] = $this->instance; 381 382 $userdata = getCacheName($this->user,'.sfuser'); 383 $authdata = getCacheName($this->user,'.sfauth'); 384 $ok1 = io_saveFile($userdata, serialize($this->userdata)); 385 $ok2 = io_saveFile($authdata, serialize($this->authdata)); 386 387 return $ok1 && $ok2; 388 } 389 390 /** 391 * Get a config setting for the specified instance 392 * 393 * @param $config 394 * @param $instance 395 * @return mixed 396 */ 397 protected function getIConf($config, $instance) { 398 if($instance === 2 || $instance === 3) { 399 $postfix = ' '.$instance; 400 } else { 401 $postfix = ''; 402 } 403 404 return $this->getConf($config.$postfix); 405 } 406 407}