protected function loadOAuthAccountData()
 {
     $uri = new PhutilURI('https://api.amazon.com/user/profile');
     $uri->setQueryParam('access_token', $this->getAccessToken());
     $future = new HTTPSFuture($uri);
     list($body) = $future->resolvex();
     try {
         return phutil_json_decode($body);
     } catch (PhutilJSONParserException $ex) {
         throw new PhutilProxyException(pht('Expected valid JSON response from Amazon account data request.'), $ex);
     }
 }
 protected function loadOAuthAccountData()
 {
     $uri = new PhutilURI('https://api.amazon.com/user/profile');
     $uri->setQueryParam('access_token', $this->getAccessToken());
     $future = new HTTPSFuture($uri);
     list($body) = $future->resolvex();
     $data = json_decode($body, true);
     if (!is_array($data)) {
         throw new Exception("Expected valid JSON response from Amazon account data request, " . "got: " . $body);
     }
     return $data;
 }
 protected function loadOAuthAccountData()
 {
     $uri = new PhutilURI('https://api.github.com/user');
     $uri->setQueryParam('access_token', $this->getAccessToken());
     $future = new HTTPSFuture($uri);
     // NOTE: GitHub requires a User-Agent string.
     $future->addHeader('User-Agent', __CLASS__);
     list($body) = $future->resolvex();
     try {
         return phutil_json_decode($body);
     } catch (PhutilJSONParserException $ex) {
         throw new PhutilProxyException(pht('Expected valid JSON response from GitHub account data request.'), $ex);
     }
 }
 protected function loadOAuthAccountData()
 {
     $uri = new PhutilURI('https://api.github.com/user');
     $uri->setQueryParam('access_token', $this->getAccessToken());
     $future = new HTTPSFuture($uri);
     // NOTE: GitHub requires a User-Agent string.
     $future->addHeader('User-Agent', 'PhutilAuthAdapterOAuthGitHub');
     list($body) = $future->resolvex();
     $data = json_decode($body, true);
     if (!is_array($data)) {
         throw new Exception("Expected valid JSON response from GitHub account data request, " . "got: " . $body);
     }
     return $data;
 }
 protected function loadOAuthAccountData()
 {
     $uri = new PhutilURI('https://disqus.com/api/3.0/users/details.json');
     $uri->setQueryParam('api_key', $this->getClientID());
     $uri->setQueryParam('access_token', $this->getAccessToken());
     $uri = (string) $uri;
     $future = new HTTPSFuture($uri);
     $future->setMethod('GET');
     list($body) = $future->resolvex();
     $data = json_decode($body, true);
     if (!is_array($data)) {
         throw new Exception("Expected valid JSON response from Disqus account data request, " . "got: " . $body);
     }
     return $data['response'];
 }
 protected function loadOAuthAccountData()
 {
     $uri = new PhutilURI('https://www.googleapis.com/plus/v1/people/me');
     $uri->setQueryParam('access_token', $this->getAccessToken());
     $future = new HTTPSFuture($uri);
     list($status, $body) = $future->resolve();
     if ($status->isError()) {
         $this->tryToThrowSpecializedError($status, $body);
         throw $status;
     }
     try {
         return phutil_json_decode($body);
     } catch (PhutilJSONParserException $ex) {
         throw new PhutilProxyException(pht('Expected valid JSON response from Google account data request.'), $ex);
     }
 }
 protected function loadOAuthAccountData()
 {
     $uri = new PhutilURI('https://www.googleapis.com/plus/v1/people/me');
     $uri->setQueryParam('access_token', $this->getAccessToken());
     $future = new HTTPSFuture($uri);
     list($status, $body) = $future->resolve();
     if ($status->isError()) {
         $this->tryToThrowSpecializedError($status, $body);
         throw $status;
     }
     $data = json_decode($body, true);
     if (!is_array($data)) {
         throw new Exception("Expected valid JSON response from Google account data request, " . "got: " . $body);
     }
     return $data;
 }
 protected function loadOAuthAccountData()
 {
     $uri = new PhutilURI('https://disqus.com/api/3.0/users/details.json');
     $uri->setQueryParam('api_key', $this->getClientID());
     $uri->setQueryParam('access_token', $this->getAccessToken());
     $uri = (string) $uri;
     $future = new HTTPSFuture($uri);
     $future->setMethod('GET');
     list($body) = $future->resolvex();
     try {
         $data = phutil_json_decode($body);
         return $data['response'];
     } catch (PhutilJSONParserException $ex) {
         throw new PhutilProxyException(pht('Expected valid JSON response from Disqus account data request.'), $ex);
     }
 }
Example #9
0
 protected function getProxiedFuture()
 {
     if (!$this->future) {
         $params = $this->params;
         if (!$this->action) {
             throw new Exception(pht('You must %s!', 'setRawSlackQuery()'));
         }
         if (!$this->accessToken) {
             throw new Exception(pht('You must %s!', 'setAccessToken()'));
         }
         $uri = new PhutilURI('https://slack.com/');
         $uri->setPath('/api/' . $this->action);
         $uri->setQueryParam('token', $this->accessToken);
         $future = new HTTPSFuture($uri);
         $future->setData($this->params);
         $future->setMethod($this->method);
         $this->future = $future;
     }
     return $this->future;
 }
 protected function getProxiedFuture()
 {
     if (!$this->future) {
         $params = $this->params;
         if (!$this->action) {
             throw new Exception(pht('You must %s!', 'setRawAsanaQuery()'));
         }
         if (!$this->accessToken) {
             throw new Exception(pht('You must %s!', 'setAccessToken()'));
         }
         $uri = new PhutilURI('https://app.asana.com/');
         $uri->setPath('/api/1.0/' . ltrim($this->action, '/'));
         $future = new HTTPSFuture($uri);
         $future->setData($this->params);
         $future->addHeader('Authorization', 'Bearer ' . $this->accessToken);
         $future->setMethod($this->method);
         $this->future = $future;
     }
     return $this->future;
 }
 protected function getProxiedFuture()
 {
     if (!$this->future) {
         $params = $this->params;
         if (!$this->action) {
             throw new Exception("You must setRawWordPressQuery()!");
         }
         if (!$this->accessToken) {
             throw new Exception("You must setAccessToken()!");
         }
         $uri = new PhutilURI('https://public-api.wordpress.com/');
         $uri->setPath('/rest/v1/' . ltrim($this->action, '/'));
         $future = new HTTPSFuture($uri);
         $future->setData($this->params);
         $future->setMethod($this->method);
         // NOTE: This is how WordPress.com REST API authenticates
         $future->addHeader('Authorization', 'Bearer ' . $this->accessToken);
         $this->future = $future;
     }
     return $this->future;
 }
Example #12
0
 protected function getProxiedFuture()
 {
     if (!$this->future) {
         $params = $this->params;
         if (!$this->action) {
             throw new Exception(pht('You must %s!', 'setRawGitHubQuery()'));
         }
         if (!$this->accessToken) {
             throw new Exception(pht('You must %s!', 'setAccessToken()'));
         }
         $uri = new PhutilURI('https://api.github.com/');
         $uri->setPath('/' . ltrim($this->action, '/'));
         $future = new HTTPSFuture($uri);
         $future->setData($this->params);
         $future->addHeader('Authorization', 'token ' . $this->accessToken);
         // NOTE: GitHub requires a 'User-Agent' header.
         $future->addHeader('User-Agent', __CLASS__);
         $future->setMethod($this->method);
         foreach ($this->headers as $header) {
             list($key, $value) = $header;
             $future->addHeader($key, $value);
         }
         $this->future = $future;
     }
     return $this->future;
 }
Example #13
0
 protected function run()
 {
     $argv = $this->getArgv();
     if (count($argv) !== 1) {
         throw new Exception(pht('Usage: %s %s', __CLASS__, '<json_config_file>'));
     }
     $json_raw = Filesystem::readFile($argv[0]);
     try {
         $config = phutil_json_decode($json_raw);
     } catch (PhutilJSONParserException $ex) {
         throw new PhutilProxyException(pht("File '%s' is not valid JSON!", $argv[0]), $ex);
     }
     $nick = idx($config, 'nick', 'phabot');
     $handlers = idx($config, 'handlers', array());
     $protocol_adapter_class = idx($config, 'protocol-adapter', 'PhabricatorIRCProtocolAdapter');
     $this->pollFrequency = idx($config, 'poll-frequency', 1);
     $this->config = $config;
     foreach ($handlers as $handler) {
         $obj = newv($handler, array($this));
         $this->handlers[] = $obj;
     }
     $ca_bundle = idx($config, 'https.cabundle');
     if ($ca_bundle) {
         HTTPSFuture::setGlobalCABundleFromPath($ca_bundle);
     }
     $conduit_uri = idx($config, 'conduit.uri');
     if ($conduit_uri) {
         $conduit_token = idx($config, 'conduit.token');
         // Normalize the path component of the URI so users can enter the
         // domain without the "/api/" part.
         $conduit_uri = new PhutilURI($conduit_uri);
         $conduit_host = (string) $conduit_uri->setPath('/');
         $conduit_uri = (string) $conduit_uri->setPath('/api/');
         $conduit = new ConduitClient($conduit_uri);
         if ($conduit_token) {
             $conduit->setConduitToken($conduit_token);
         } else {
             $conduit_user = idx($config, 'conduit.user');
             $conduit_cert = idx($config, 'conduit.cert');
             $response = $conduit->callMethodSynchronous('conduit.connect', array('client' => __CLASS__, 'clientVersion' => '1.0', 'clientDescription' => php_uname('n') . ':' . $nick, 'host' => $conduit_host, 'user' => $conduit_user, 'certificate' => $conduit_cert));
         }
         $this->conduit = $conduit;
     }
     // Instantiate Protocol Adapter, for now follow same technique as
     // handler instantiation
     $this->protocolAdapter = newv($protocol_adapter_class, array());
     $this->protocolAdapter->setConfig($this->config)->connect();
     $this->runLoop();
     $this->protocolAdapter->disconnect();
 }
 public function callMethod($method, array $params)
 {
     $meta = array();
     if ($this->sessionKey) {
         $meta['sessionKey'] = $this->sessionKey;
     }
     if ($this->connectionID) {
         $meta['connectionID'] = $this->connectionID;
     }
     if ($method == 'conduit.connect') {
         $certificate = idx($params, 'certificate');
         if ($certificate) {
             $token = time();
             $params['authToken'] = $token;
             $params['authSignature'] = sha1($token . $certificate);
         }
         unset($params['certificate']);
     }
     if ($meta) {
         $params['__conduit__'] = $meta;
     }
     $uri = id(clone $this->uri)->setPath('/api/' . $method);
     $data = array('params' => json_encode($params), 'output' => 'json', '__conduit__' => true);
     // Always use the cURL-based HTTPSFuture, for proxy support and other
     // protocol edge cases that HTTPFuture does not support.
     $core_future = new HTTPSFuture($uri, $data);
     $core_future->setMethod('POST');
     $core_future->setTimeout($this->timeout);
     if ($this->username !== null) {
         $core_future->setHTTPBasicAuthCredentials($this->username, $this->password);
     }
     $conduit_future = new ConduitFuture($core_future);
     $conduit_future->setClient($this, $method);
     $conduit_future->beginProfile($data);
     $conduit_future->isReady();
     return $conduit_future;
 }
Example #15
0
 protected function getProxiedFuture()
 {
     if (!$this->future) {
         $params = $this->params;
         if (!$this->action) {
             throw new Exception('You must setRawTwitchQuery()!');
         }
         if (!$this->accessToken) {
             throw new Exception('You must setAccessToken()!');
         }
         $uri = new PhutilURI('https://api.twitch.tv/');
         $uri->setPath('/kraken/' . ltrim($this->action, '/'));
         $uri->setQueryParam('oauth_token', $this->accessToken);
         $future = new HTTPSFuture($uri);
         $future->setData($this->params);
         $future->setMethod($this->method);
         // NOTE: This is how the Twitch API is versioned.
         $future->addHeader('Accept', 'application/vnd.twitchtv.2+json');
         // NOTE: This is required to avoid rate limiting.
         $future->addHeader('Client-ID', $this->clientID);
         $this->future = $future;
     }
     return $this->future;
 }
 public function send()
 {
     $user = PhabricatorEnv::getEnvConfig('sendgrid.api-user');
     $key = PhabricatorEnv::getEnvConfig('sendgrid.api-key');
     if (!$user || !$key) {
         throw new Exception("Configure 'sendgrid.api-user' and 'sendgrid.api-key' to use " . "SendGrid for mail delivery.");
     }
     $params = array();
     $ii = 0;
     foreach (idx($this->params, 'tos', array()) as $to) {
         $params['to[' . $ii++ . ']'] = $to;
     }
     $params['subject'] = idx($this->params, 'subject');
     $params['text'] = idx($this->params, 'body');
     if (idx($this->params, 'html-body')) {
         $params['html'] = idx($this->params, 'html-body');
     }
     $params['from'] = idx($this->params, 'from');
     if (idx($this->params, 'from-name')) {
         $params['fromname'] = $this->params['from-name'];
     }
     if (idx($this->params, 'reply-to')) {
         $replyto = $this->params['reply-to'];
         // Pick off the email part, no support for the name part in this API.
         $params['replyto'] = $replyto[0]['email'];
     }
     foreach (idx($this->params, 'files', array()) as $name => $data) {
         $params['files[' . $name . ']'] = $data;
     }
     $headers = idx($this->params, 'headers', array());
     // See SendGrid Support Ticket #29390; there's no explicit REST API support
     // for CC right now but it works if you add a generic "Cc" header.
     //
     // SendGrid said this is supported:
     //   "You can use CC as you are trying to do there [by adding a generic
     //    header]. It is supported despite our limited documentation to this
     //    effect, I am glad you were able to figure it out regardless. ..."
     if (idx($this->params, 'ccs')) {
         $headers[] = array('Cc', implode(', ', $this->params['ccs']));
     }
     if ($headers) {
         // Convert to dictionary.
         $headers = ipull($headers, 1, 0);
         $headers = json_encode($headers);
         $params['headers'] = $headers;
     }
     $params['api_user'] = $user;
     $params['api_key'] = $key;
     $future = new HTTPSFuture('https://sendgrid.com/api/mail.send.json', $params);
     $future->setMethod('POST');
     list($body) = $future->resolvex();
     $response = json_decode($body, true);
     if (!is_array($response)) {
         throw new Exception("Failed to JSON decode response: {$body}");
     }
     if ($response['message'] !== 'success') {
         $errors = implode(';', $response['errors']);
         throw new Exception("Request failed with errors: {$errors}.");
     }
     return true;
 }
Example #17
0
 /**
  * Load contents of remote URI. Behaves pretty much like
  *  `@file_get_contents($uri)` but doesn't require `allow_url_fopen`.
  *
  * @param string
  * @param float
  * @return string|false
  */
 public static function loadContent($uri, $timeout = null)
 {
     $future = new HTTPSFuture($uri);
     if ($timeout !== null) {
         $future->setTimeout($timeout);
     }
     try {
         list($body) = $future->resolvex();
         return $body;
     } catch (Status_HTTPFutureResponseStatus $ex) {
         return false;
     }
 }
 public function send()
 {
     $key = PhabricatorEnv::getEnvConfig('mailgun.api-key');
     $domain = PhabricatorEnv::getEnvConfig('mailgun.domain');
     $params = array();
     $params['to'] = implode(', ', idx($this->params, 'tos', array()));
     $params['subject'] = idx($this->params, 'subject');
     $params['text'] = idx($this->params, 'body');
     if (idx($this->params, 'html-body')) {
         $params['html'] = idx($this->params, 'html-body');
     }
     $from = idx($this->params, 'from');
     if (idx($this->params, 'from-name')) {
         $params['from'] = "{$this->params['from-name']} <{$from}>";
     } else {
         $params['from'] = $from;
     }
     if (idx($this->params, 'reply-to')) {
         $replyto = $this->params['reply-to'];
         $params['h:reply-to'] = implode(', ', $replyto);
     }
     if (idx($this->params, 'ccs')) {
         $params['cc'] = implode(', ', $this->params['ccs']);
     }
     foreach (idx($this->params, 'headers', array()) as $header) {
         list($name, $value) = $header;
         $params['h:' . $name] = $value;
     }
     $future = new HTTPSFuture("https://*****:*****@api.mailgun.net/v2/{$domain}/messages", $params);
     $future->setMethod('POST');
     foreach ($this->attachments as $attachment) {
         $future->attachFileData('attachment', $attachment['data'], $attachment['name'], $attachment['type']);
     }
     list($body) = $future->resolvex();
     $response = json_decode($body, true);
     if (!is_array($response)) {
         throw new Exception("Failed to JSON decode response: {$body}");
     }
     if (!idx($response, 'id')) {
         $message = $response['message'];
         throw new Exception("Request failed with errors: {$message}.");
     }
     return true;
 }
Example #19
0
     // to some degree, e.g. "arc install-certificate" does it for you.
     $conduit_uri = new PhutilURI($conduit_uri);
     $conduit_uri->setPath('/api/');
     $conduit_uri = (string) $conduit_uri;
 }
 $workflow->setConduitURI($conduit_uri);
 // Apply global CA bundle from configs.
 $ca_bundle = $configuration_manager->getConfigFromAnySource('https.cabundle');
 if ($ca_bundle) {
     $ca_bundle = Filesystem::resolvePath($ca_bundle, $working_copy->getProjectRoot());
     HTTPSFuture::setGlobalCABundleFromPath($ca_bundle);
 }
 $blind_key = 'https.blindly-trust-domains';
 $blind_trust = $configuration_manager->getConfigFromAnySource($blind_key);
 if ($blind_trust) {
     HTTPSFuture::setBlindlyTrustDomains($blind_trust);
 }
 if ($need_conduit) {
     if (!$conduit_uri) {
         $message = phutil_console_format("This command requires arc to connect to a Phabricator install, but " . "no Phabricator installation is configured. To configure a " . "Phabricator URI:\n\n" . "  - set a default location with `arc set-config default <uri>`; or\n" . "  - specify '--conduit-uri=uri' explicitly; or\n" . "  - run 'arc' in a working copy with an '.arcconfig'.\n");
         $message = phutil_console_wrap($message);
         throw new ArcanistUsageException($message);
     }
     $workflow->establishConduit();
 }
 $hosts_config = idx($user_config, 'hosts', array());
 $host_config = idx($hosts_config, $conduit_uri, array());
 $user_name = idx($host_config, 'user');
 $certificate = idx($host_config, 'cert');
 $description = implode(' ', $original_argv);
 $credentials = array('user' => $user_name, 'certificate' => $certificate, 'description' => $description);
Example #20
0
 public function isReady()
 {
     if (isset($this->result)) {
         return true;
     }
     $uri = $this->getURI();
     $data = $this->getData();
     if ($data) {
         // NOTE: PHP's cURL implementation has a piece of magic which treats
         // parameters as file paths if they begin with '@'. This means that
         // an array like "array('name' => '@/usr/local/secret')" will attempt to
         // read that file off disk and send it to the remote server. This behavior
         // is pretty surprising, and it can easily become a relatively severe
         // security vulnerability which allows an attacker to read any file the
         // HTTP process has access to. Since this feature is very dangerous and
         // not particularly useful, we prevent its use.
         //
         // After PHP 5.2.0, it is sufficient to pass a string to avoid this
         // "feature" (it is only invoked in the array version). Prior to
         // PHP 5.2.0, we block any request which have string data beginning with
         // '@' (they would not work anyway).
         if (is_array($data)) {
             // Explicitly build a query string to prevent "@" security problems.
             $data = http_build_query($data, '', '&');
         }
         if ($data[0] == '@' && version_compare(phpversion(), '5.2.0', '<')) {
             throw new Exception("Attempting to make an HTTP request including string data that " . "begins with '@'. Prior to PHP 5.2.0, this reads files off disk, " . "which creates a wide attack window for security vulnerabilities. " . "Upgrade PHP or avoid making cURL requests which begin with '@'.");
         }
     } else {
         $data = null;
     }
     $profiler = PhutilServiceProfiler::getInstance();
     $this->profilerCallID = $profiler->beginServiceCall(array('type' => 'http', 'uri' => $uri));
     // NOTE: If possible, we reuse the handle so we can take advantage of
     // keepalives. This means every option has to be set every time, because
     // cURL will not clear the settings between requests.
     if (!self::$handle) {
         self::$handle = curl_init();
     }
     $curl = self::$handle;
     curl_setopt($curl, CURLOPT_URL, $uri);
     curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
     $headers = $this->getHeaders();
     if ($headers) {
         for ($ii = 0; $ii < count($headers); $ii++) {
             list($name, $value) = $headers[$ii];
             $headers[$ii] = $name . ': ' . $value;
         }
         curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
     } else {
         curl_setopt($curl, CURLOPT_HTTPHEADER, array());
     }
     // Set the requested HTTP method, e.g. GET / POST / PUT.
     curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->getMethod());
     // Make sure we get the headers and data back.
     curl_setopt($curl, CURLOPT_HEADER, true);
     curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
     curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
     curl_setopt($curl, CURLOPT_MAXREDIRS, 20);
     if (defined('CURLOPT_TIMEOUT_MS')) {
         // If CURLOPT_TIMEOUT_MS is available, use the higher-precision timeout.
         $timeout = max(1, ceil(1000 * $this->getTimeout()));
         curl_setopt($curl, CURLOPT_TIMEOUT_MS, $timeout);
     } else {
         // Otherwise, fall back to the lower-precision timeout.
         $timeout = max(1, ceil($this->getTimeout()));
         curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
     }
     $ini_val = ini_get('curl.cainfo');
     if (!$ini_val) {
         $caroot = dirname(phutil_get_library_root('phutil')) . '/resources/ssl/';
         if (Filesystem::pathExists($caroot . 'custom.pem')) {
             $cabundle = $caroot . 'custom.pem';
         } else {
             $cabundle = $caroot . 'default.pem';
         }
         curl_setopt($curl, CURLOPT_CAINFO, $cabundle);
     }
     curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
     $result = curl_exec($curl);
     $err_code = curl_errno($curl);
     if ($err_code) {
         $status = new HTTPFutureResponseStatusCURL($err_code, $uri);
         $body = null;
         $headers = array();
         $this->result = array($status, $body, $headers);
     } else {
         // cURL returns headers of all redirects, we strip all but the final one.
         $redirects = curl_getinfo($curl, CURLINFO_REDIRECT_COUNT);
         $result = preg_replace('/^(.*\\r\\n\\r\\n){' . $redirects . '}/sU', '', $result);
         $this->result = $this->parseRawHTTPResponse($result);
     }
     // NOTE: Don't call curl_close(), we want to use keepalive if possible.
     $profiler = PhutilServiceProfiler::getInstance();
     $profiler->endServiceCall($this->profilerCallID, array());
     return true;
 }
 public function retrieveUserProfileImage()
 {
     $uri = $this->userData['image'];
     return HTTPSFuture::loadContent($uri);
 }
 private function executeRequest($path, array $data, $method = 'GET')
 {
     $uri = new PhutilURI($this->uri);
     $uri->setPath($this->index);
     $uri->appendPath($path);
     $data = json_encode($data);
     $future = new HTTPSFuture($uri, $data);
     if ($method != 'GET') {
         $future->setMethod($method);
     }
     if ($this->getTimeout()) {
         $future->setTimeout($this->getTimeout());
     }
     list($body) = $future->resolvex();
     if ($method != 'GET') {
         return null;
     }
     try {
         return phutil_json_decode($body);
     } catch (PhutilJSONParserException $ex) {
         throw new PhutilProxyException(pht('ElasticSearch server returned invalid JSON!'), $ex);
     }
 }
 public function processRequest()
 {
     $provider = $this->provider;
     $auth_enabled = $provider->isProviderEnabled();
     $client_id = $provider->getClientID();
     $client_secret = $provider->getClientSecret();
     $key = $provider->getProviderKey();
     $name = phutil_escape_html($provider->getProviderName());
     $res_ok = '<strong style="color: #00aa00;">OK</strong>';
     $res_no = '<strong style="color: #aa0000;">NO</strong>';
     $res_na = '<strong style="color: #999999;">N/A</strong>';
     $results = array();
     $auth_key = $key . '.auth-enabled';
     if (!$auth_enabled) {
         $results[$auth_key] = array($res_no, 'false', $name . ' authentication is disabled in the configuration. Edit the ' . 'Phabricator configuration to enable "' . $auth_key . '".');
     } else {
         $results[$auth_key] = array($res_ok, 'true', $name . ' authentication is enabled.');
     }
     $client_id_key = $key . '.application-id';
     if (!$client_id) {
         $results[$client_id_key] = array($res_no, null, 'No ' . $name . ' Application ID is configured. Edit the Phabricator ' . 'configuration to specify an application ID in ' . '"' . $client_id_key . '". ' . $provider->renderGetClientIDHelp());
     } else {
         $results[$client_id_key] = array($res_ok, $client_id, 'Application ID is set.');
     }
     $client_secret_key = $key . '.application-secret';
     if (!$client_secret) {
         $results[$client_secret_key] = array($res_no, null, 'No ' . $name . ' Application secret is configured. Edit the ' . 'Phabricator configuration to specify an Application Secret, in ' . '"' . $client_secret_key . '". ' . $provider->renderGetClientSecretHelp());
     } else {
         $results[$client_secret_key] = array($res_ok, "It's a secret!", 'Application secret is set.');
     }
     $timeout = 5;
     $internet = HTTPSFuture::loadContent("http://google.com/", $timeout);
     if ($internet === false) {
         $results['internet'] = array($res_no, null, 'Unable to make an HTTP request to Google. Check your outbound ' . 'internet connection and firewall/filtering settings.');
     } else {
         $results['internet'] = array($res_ok, null, 'Internet seems OK.');
     }
     $test_uris = $provider->getTestURIs();
     foreach ($test_uris as $uri) {
         $success = HTTPSFuture::loadContent($uri, $timeout);
         if ($success === false) {
             $results[$uri] = array($res_no, null, "Unable to make an HTTP request to {$uri}. {$name} may be " . 'down or inaccessible.');
         } else {
             $results[$uri] = array($res_ok, null, 'Made a request to ' . $uri . '.');
         }
     }
     if ($provider->shouldDiagnoseAppLogin()) {
         $test_uri = new PhutilURI($provider->getTokenURI());
         $test_uri->setQueryParams(array('client_id' => $client_id, 'client_secret' => $client_secret, 'grant_type' => 'client_credentials'));
         $future = new HTTPSFuture($test_uri);
         $future->setTimeout($timeout);
         try {
             list($body) = $future->resolvex();
             $results['App Login'] = array($res_ok, '(A Valid Token)', "Raw application login to {$name} works.");
         } catch (Exception $ex) {
             if ($ex instanceof HTTPFutureResponseStatusCURL) {
                 $results['App Login'] = array($res_no, null, "Unable to perform an application login with your Application ID " . "and Application Secret. You may have mistyped or misconfigured " . "them; {$name} may have revoked your authorization; or {$name} " . "may be having technical problems.");
             } else {
                 $data = json_decode($token_value, true);
                 if (!is_array($data)) {
                     $results['App Login'] = array($res_no, $token_value, "Application Login failed but the provider did not respond " . "with valid JSON error information. {$name} may be experiencing " . "technical problems.");
                 } else {
                     $results['App Login'] = array($res_no, null, "Application Login failed with error: " . $token_value);
                 }
             }
         }
     }
     return $this->renderResults($results);
 }
 private function assertSignature($expect, HTTPSFuture $signed)
 {
     $authorization = null;
     foreach ($signed->getHeaders() as $header) {
         list($key, $value) = $header;
         if (phutil_utf8_strtolower($key) === 'authorization') {
             $authorization = $value;
             break;
         }
     }
     $expect = str_replace("\n\n", ' ', $expect);
     $expect = str_replace("\n", '', $expect);
     $this->assertEqual($expect, $authorization);
 }
Example #25
0
 public function isReady()
 {
     if (isset($this->result)) {
         return true;
     }
     $uri = $this->getURI();
     $domain = id(new PhutilURI($uri))->getDomain();
     if (!$this->handle) {
         $profiler = PhutilServiceProfiler::getInstance();
         $this->profilerCallID = $profiler->beginServiceCall(array('type' => 'http', 'uri' => $uri));
         if (!self::$multi) {
             self::$multi = curl_multi_init();
             if (!self::$multi) {
                 throw new Exception('curl_multi_init() failed!');
             }
         }
         if (!empty(self::$pool[$domain])) {
             $curl = array_pop(self::$pool[$domain]);
         } else {
             $curl = curl_init();
             if (!$curl) {
                 throw new Exception('curl_init() failed!');
             }
         }
         $this->handle = $curl;
         curl_multi_add_handle(self::$multi, $curl);
         curl_setopt($curl, CURLOPT_URL, $uri);
         if (defined('CURLOPT_PROTOCOLS')) {
             // cURL supports a lot of protocols, and by default it will honor
             // redirects across protocols (for instance, from HTTP to POP3). Beyond
             // being very silly, this also has security implications:
             //
             //   http://blog.volema.com/curl-rce.html
             //
             // Disable all protocols other than HTTP and HTTPS.
             $allowed_protocols = CURLPROTO_HTTPS | CURLPROTO_HTTP;
             curl_setopt($curl, CURLOPT_PROTOCOLS, $allowed_protocols);
             curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS, $allowed_protocols);
         }
         $data = $this->getData();
         if ($data) {
             // NOTE: PHP's cURL implementation has a piece of magic which treats
             // parameters as file paths if they begin with '@'. This means that
             // an array like "array('name' => '@/usr/local/secret')" will attempt to
             // read that file off disk and send it to the remote server. This
             // behavior is pretty surprising, and it can easily become a relatively
             // severe security vulnerability which allows an attacker to read any
             // file the HTTP process has access to. Since this feature is very
             // dangerous and not particularly useful, we prevent its use.
             //
             // After PHP 5.2.0, it is sufficient to pass a string to avoid this
             // "feature" (it is only invoked in the array version). Prior to
             // PHP 5.2.0, we block any request which have string data beginning with
             // '@' (they would not work anyway).
             if (is_array($data)) {
                 // Explicitly build a query string to prevent "@" security problems.
                 $data = http_build_query($data, '', '&');
             }
             if ($data[0] == '@' && version_compare(phpversion(), '5.2.0', '<')) {
                 throw new Exception("Attempting to make an HTTP request including string data that " . "begins with '@'. Prior to PHP 5.2.0, this reads files off disk, " . "which creates a wide attack window for security vulnerabilities. " . "Upgrade PHP or avoid making cURL requests which begin with '@'.");
             }
             curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
         } else {
             curl_setopt($curl, CURLOPT_POSTFIELDS, null);
         }
         $headers = $this->getHeaders();
         $saw_expect = false;
         for ($ii = 0; $ii < count($headers); $ii++) {
             list($name, $value) = $headers[$ii];
             $headers[$ii] = $name . ': ' . $value;
             if (!strncasecmp($name, 'Expect')) {
                 $saw_expect = true;
             }
         }
         if (!$saw_expect) {
             // cURL sends an "Expect" header by default for certain requests. While
             // there is some reasoning behind this, it causes a practical problem
             // in that lighttpd servers reject these requests with a 417. Both sides
             // are locked in an eternal struggle (lighttpd has introduced a
             // 'server.reject-expect-100-with-417' option to deal with this case).
             //
             // The ostensibly correct way to suppress this behavior on the cURL side
             // is to add an empty "Expect:" header. If we haven't seen some other
             // explicit "Expect:" header, do so.
             //
             // See here, for example, although this issue is fairly widespread:
             //   http://curl.haxx.se/mail/archive-2009-07/0008.html
             $headers[] = 'Expect:';
         }
         curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
         // Set the requested HTTP method, e.g. GET / POST / PUT.
         curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->getMethod());
         // Make sure we get the headers and data back.
         curl_setopt($curl, CURLOPT_HEADER, true);
         curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
         curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
         curl_setopt($curl, CURLOPT_MAXREDIRS, 20);
         if (defined('CURLOPT_TIMEOUT_MS')) {
             // If CURLOPT_TIMEOUT_MS is available, use the higher-precision timeout.
             $timeout = max(1, ceil(1000 * $this->getTimeout()));
             curl_setopt($curl, CURLOPT_TIMEOUT_MS, $timeout);
         } else {
             // Otherwise, fall back to the lower-precision timeout.
             $timeout = max(1, ceil($this->getTimeout()));
             curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
         }
         // Try some decent fallbacks here:
         // - First, check if a cabundle path is already set (e.g. externally).
         // - Then, if a local custom.pem exists, use that, because it probably
         //   means that the user wants to override everything (also because the
         //   user might not have access to change the box's php.ini to add
         //   curl.cainfo.
         // - Otherwise, try using curl.cainfo. If it's set explicitly, it's
         //   probably reasonable to try using it before we fall back to what
         //   libphutil ships with.
         // - Lastly, try the default that libphutil ships with. If it doesn't
         //   work, give up and yell at the user.
         if (!$this->getCABundle()) {
             $caroot = dirname(phutil_get_library_root('phutil')) . '/resources/ssl/';
             $ini_val = ini_get('curl.cainfo');
             if (Filesystem::pathExists($caroot . 'custom.pem')) {
                 $this->setCABundleFromPath($caroot . 'custom.pem');
             } else {
                 if ($ini_val) {
                     // TODO: We can probably do a pathExists() here, even.
                     $this->setCABundleFromPath($ini_val);
                 } else {
                     $this->setCABundleFromPath($caroot . 'default.pem');
                 }
             }
         }
         curl_setopt($curl, CURLOPT_CAINFO, $this->getCABundle());
         curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
         curl_setopt($curl, CURLOPT_SSLVERSION, 0);
     } else {
         $curl = $this->handle;
         if (!self::$results) {
             // NOTE: In curl_multi_select(), PHP calls curl_multi_fdset() but does
             // not check the return value of &maxfd for -1 until recent versions
             // of PHP (5.4.8 and newer). cURL may return -1 as maxfd in some unusual
             // situations; if it does, PHP enters select() with nfds=0, which blocks
             // until the timeout is reached.
             //
             // We could try to guess whether this will happen or not by examining
             // the version identifier, but we can also just sleep for only a short
             // period of time.
             curl_multi_select(self::$multi, 0.01);
         }
     }
     do {
         $active = null;
         $result = curl_multi_exec(self::$multi, $active);
     } while ($result == CURLM_CALL_MULTI_PERFORM);
     while ($info = curl_multi_info_read(self::$multi)) {
         if ($info['msg'] == CURLMSG_DONE) {
             self::$results[(int) $info['handle']] = $info;
         }
     }
     if (!array_key_exists((int) $curl, self::$results)) {
         return false;
     }
     $info = self::$results[(int) $curl];
     $result = curl_multi_getcontent($curl);
     $err_code = $info['result'];
     if ($err_code) {
         $status = new HTTPFutureResponseStatusCURL($err_code, $uri);
         $body = null;
         $headers = array();
         $this->result = array($status, $body, $headers);
     } else {
         // cURL returns headers of all redirects, we strip all but the final one.
         $redirects = curl_getinfo($curl, CURLINFO_REDIRECT_COUNT);
         $result = preg_replace('/^(.*\\r\\n\\r\\n){' . $redirects . '}/sU', '', $result);
         $this->result = $this->parseRawHTTPResponse($result);
     }
     curl_multi_remove_handle(self::$multi, $curl);
     unset(self::$results[(int) $curl]);
     // NOTE: We want to use keepalive if possible. Return the handle to a
     // pool for the domain; don't close it.
     self::$pool[$domain][] = $curl;
     $profiler = PhutilServiceProfiler::getInstance();
     $profiler->endServiceCall($this->profilerCallID, array());
     return true;
 }
Example #26
0
 public function isReady()
 {
     if (isset($this->result)) {
         return true;
     }
     $uri = $this->getURI();
     $domain = id(new PhutilURI($uri))->getDomain();
     if (!$this->handle) {
         $uri_object = new PhutilURI($uri);
         $proxy = PhutilHTTPEngineExtension::buildHTTPProxyURI($uri_object);
         $profiler = PhutilServiceProfiler::getInstance();
         $this->profilerCallID = $profiler->beginServiceCall(array('type' => 'http', 'uri' => $uri, 'proxy' => (string) $proxy));
         if (!self::$multi) {
             self::$multi = curl_multi_init();
             if (!self::$multi) {
                 throw new Exception(pht('%s failed!', 'curl_multi_init()'));
             }
         }
         if (!empty(self::$pool[$domain])) {
             $curl = array_pop(self::$pool[$domain]);
         } else {
             $curl = curl_init();
             if (!$curl) {
                 throw new Exception(pht('%s failed!', 'curl_init()'));
             }
         }
         $this->handle = $curl;
         curl_multi_add_handle(self::$multi, $curl);
         curl_setopt($curl, CURLOPT_URL, $uri);
         if (defined('CURLOPT_PROTOCOLS')) {
             // cURL supports a lot of protocols, and by default it will honor
             // redirects across protocols (for instance, from HTTP to POP3). Beyond
             // being very silly, this also has security implications:
             //
             //   http://blog.volema.com/curl-rce.html
             //
             // Disable all protocols other than HTTP and HTTPS.
             $allowed_protocols = CURLPROTO_HTTPS | CURLPROTO_HTTP;
             curl_setopt($curl, CURLOPT_PROTOCOLS, $allowed_protocols);
             curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS, $allowed_protocols);
         }
         if (strlen($this->rawBody)) {
             if ($this->getData()) {
                 throw new Exception(pht('You can not execute an HTTP future with both a raw request ' . 'body and structured request data.'));
             }
             // We aren't actually going to use this file handle, since we are
             // just pushing data through the callback, but cURL gets upset if
             // we don't hand it a real file handle.
             $tmp = new TempFile();
             $this->fileHandle = fopen($tmp, 'r');
             // NOTE: We must set CURLOPT_PUT here to make cURL use CURLOPT_INFILE.
             // We'll possibly overwrite the method later on, unless this is really
             // a PUT request.
             curl_setopt($curl, CURLOPT_PUT, true);
             curl_setopt($curl, CURLOPT_INFILE, $this->fileHandle);
             curl_setopt($curl, CURLOPT_INFILESIZE, strlen($this->rawBody));
             curl_setopt($curl, CURLOPT_READFUNCTION, array($this, 'willWriteBody'));
         } else {
             $data = $this->formatRequestDataForCURL();
             curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
         }
         $headers = $this->getHeaders();
         $saw_expect = false;
         for ($ii = 0; $ii < count($headers); $ii++) {
             list($name, $value) = $headers[$ii];
             $headers[$ii] = $name . ': ' . $value;
             if (!strncasecmp($name, 'Expect', strlen('Expect'))) {
                 $saw_expect = true;
             }
         }
         if (!$saw_expect) {
             // cURL sends an "Expect" header by default for certain requests. While
             // there is some reasoning behind this, it causes a practical problem
             // in that lighttpd servers reject these requests with a 417. Both sides
             // are locked in an eternal struggle (lighttpd has introduced a
             // 'server.reject-expect-100-with-417' option to deal with this case).
             //
             // The ostensibly correct way to suppress this behavior on the cURL side
             // is to add an empty "Expect:" header. If we haven't seen some other
             // explicit "Expect:" header, do so.
             //
             // See here, for example, although this issue is fairly widespread:
             //   http://curl.haxx.se/mail/archive-2009-07/0008.html
             $headers[] = 'Expect:';
         }
         curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
         // Set the requested HTTP method, e.g. GET / POST / PUT.
         curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->getMethod());
         // Make sure we get the headers and data back.
         curl_setopt($curl, CURLOPT_HEADER, true);
         curl_setopt($curl, CURLOPT_WRITEFUNCTION, array($this, 'didReceiveDataCallback'));
         if ($this->followLocation) {
             curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
             curl_setopt($curl, CURLOPT_MAXREDIRS, 20);
         }
         if (defined('CURLOPT_TIMEOUT_MS')) {
             // If CURLOPT_TIMEOUT_MS is available, use the higher-precision timeout.
             $timeout = max(1, ceil(1000 * $this->getTimeout()));
             curl_setopt($curl, CURLOPT_TIMEOUT_MS, $timeout);
         } else {
             // Otherwise, fall back to the lower-precision timeout.
             $timeout = max(1, ceil($this->getTimeout()));
             curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
         }
         // We're going to try to set CAINFO below. This doesn't work at all on
         // OSX around Yosemite (see T5913). On these systems, we'll use the
         // system CA and then try to tell the user that their settings were
         // ignored and how to fix things if we encounter a CA-related error.
         // Assume we have custom CA settings to start with; we'll clear this
         // flag if we read the default CA info below.
         // Try some decent fallbacks here:
         // - First, check if a bundle is set explicitly for this request, via
         //   `setCABundle()` or similar.
         // - Then, check if a global bundle is set explicitly for all requests,
         //   via `setGlobalCABundle()` or similar.
         // - Then, if a local custom.pem exists, use that, because it probably
         //   means that the user wants to override everything (also because the
         //   user might not have access to change the box's php.ini to add
         //   curl.cainfo).
         // - Otherwise, try using curl.cainfo. If it's set explicitly, it's
         //   probably reasonable to try using it before we fall back to what
         //   libphutil ships with.
         // - Lastly, try the default that libphutil ships with. If it doesn't
         //   work, give up and yell at the user.
         if (!$this->getCABundle()) {
             $caroot = dirname(phutil_get_library_root('phutil')) . '/resources/ssl/';
             $ini_val = ini_get('curl.cainfo');
             if (self::getGlobalCABundle()) {
                 $this->setCABundleFromPath(self::getGlobalCABundle());
             } else {
                 if (Filesystem::pathExists($caroot . 'custom.pem')) {
                     $this->setCABundleFromPath($caroot . 'custom.pem');
                 } else {
                     if ($ini_val) {
                         // TODO: We can probably do a pathExists() here, even.
                         $this->setCABundleFromPath($ini_val);
                     } else {
                         $this->setCABundleFromPath($caroot . 'default.pem');
                     }
                 }
             }
         }
         if ($this->canSetCAInfo()) {
             curl_setopt($curl, CURLOPT_CAINFO, $this->getCABundle());
         }
         $verify_peer = 1;
         $verify_host = 2;
         $extensions = PhutilHTTPEngineExtension::getAllExtensions();
         foreach ($extensions as $extension) {
             if ($extension->shouldTrustAnySSLAuthorityForURI($uri_object)) {
                 $verify_peer = 0;
             }
             if ($extension->shouldTrustAnySSLHostnameForURI($uri_object)) {
                 $verify_host = 0;
             }
         }
         curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $verify_peer);
         curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $verify_host);
         curl_setopt($curl, CURLOPT_SSLVERSION, 0);
         if ($proxy) {
             curl_setopt($curl, CURLOPT_PROXY, (string) $proxy);
         }
     } else {
         $curl = $this->handle;
         if (!self::$results) {
             // NOTE: In curl_multi_select(), PHP calls curl_multi_fdset() but does
             // not check the return value of &maxfd for -1 until recent versions
             // of PHP (5.4.8 and newer). cURL may return -1 as maxfd in some unusual
             // situations; if it does, PHP enters select() with nfds=0, which blocks
             // until the timeout is reached.
             //
             // We could try to guess whether this will happen or not by examining
             // the version identifier, but we can also just sleep for only a short
             // period of time.
             curl_multi_select(self::$multi, 0.01);
         }
     }
     do {
         $active = null;
         $result = curl_multi_exec(self::$multi, $active);
     } while ($result == CURLM_CALL_MULTI_PERFORM);
     while ($info = curl_multi_info_read(self::$multi)) {
         if ($info['msg'] == CURLMSG_DONE) {
             self::$results[(int) $info['handle']] = $info;
         }
     }
     if (!array_key_exists((int) $curl, self::$results)) {
         return false;
     }
     // The request is complete, so release any temporary files we wrote
     // earlier.
     $this->temporaryFiles = array();
     $info = self::$results[(int) $curl];
     $result = $this->responseBuffer;
     $err_code = $info['result'];
     if ($err_code) {
         if ($err_code == CURLE_SSL_CACERT && !$this->canSetCAInfo()) {
             $status = new HTTPFutureCertificateResponseStatus(HTTPFutureCertificateResponseStatus::ERROR_IMMUTABLE_CERTIFICATES, $uri);
         } else {
             $status = new HTTPFutureCURLResponseStatus($err_code, $uri);
         }
         $body = null;
         $headers = array();
         $this->result = array($status, $body, $headers);
     } else {
         // cURL returns headers of all redirects, we strip all but the final one.
         $redirects = curl_getinfo($curl, CURLINFO_REDIRECT_COUNT);
         $result = preg_replace('/^(.*\\r\\n\\r\\n){' . $redirects . '}/sU', '', $result);
         $this->result = $this->parseRawHTTPResponse($result);
     }
     curl_multi_remove_handle(self::$multi, $curl);
     unset(self::$results[(int) $curl]);
     // NOTE: We want to use keepalive if possible. Return the handle to a
     // pool for the domain; don't close it.
     if ($this->shouldReuseHandles()) {
         self::$pool[$domain][] = $curl;
     }
     $profiler = PhutilServiceProfiler::getInstance();
     $profiler->endServiceCall($this->profilerCallID, array());
     return true;
 }
 private function refreshProfileImage(PhabricatorUserOAuthInfo $oauth_info)
 {
     $user = $this->getRequest()->getUser();
     $provider = $this->provider;
     $error = false;
     $userinfo_uri = new PhutilURI($provider->getUserInfoURI());
     $token = $oauth_info->getToken();
     try {
         $userinfo_uri->setQueryParam('access_token', $token);
         $user_data = HTTPSFuture::loadContent($userinfo_uri);
         $provider->setUserData($user_data);
         $provider->setAccessToken($token);
         $image = $provider->retrieveUserProfileImage();
         if ($image) {
             $file = PhabricatorFile::newFromFileData($image, array('name' => $provider->getProviderKey() . '-profile.jpg', 'authorPHID' => $user->getPHID()));
             $xformer = new PhabricatorImageTransformer();
             // Resize OAuth image to a reasonable size
             $small_xformed = $xformer->executeProfileTransform($file, $width = 50, $min_height = 50, $max_height = 50);
             $user->setProfileImagePHID($small_xformed->getPHID());
             $user->save();
         } else {
             $error = 'Unable to retrieve image.';
         }
     } catch (Exception $e) {
         if ($e instanceof PhabricatorOAuthProviderException) {
             $error = sprintf('Unable to retrieve image from %s', $provider->getProviderName());
         } else {
             $error = 'Unable to save image.';
         }
     }
     $notice = new AphrontErrorView();
     if ($error) {
         $notice->setTitle('Error Refreshing Profile Picture')->setErrors(array($error));
     } else {
         $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE)->setTitle('Successfully Refreshed Profile Picture');
     }
     return $notice;
 }
 public function retrieveUserProfileImage()
 {
     $uri = idx($this->userData, 'avatar_url');
     if ($uri) {
         return HTTPSFuture::loadContent($uri);
     }
     return null;
 }
 private function executeRequest($path, array $data, $is_write = false)
 {
     $uri = new PhutilURI($this->uri);
     $data = json_encode($data);
     $uri->setPath($path);
     $future = new HTTPSFuture($uri, $data);
     if ($is_write) {
         $future->setMethod('PUT');
     }
     if ($this->getTimeout()) {
         $future->setTimeout($this->getTimeout());
     }
     list($body) = $future->resolvex();
     if ($is_write) {
         return null;
     }
     $body = json_decode($body, true);
     if (!is_array($body)) {
         throw new Exception("elasticsearch server returned invalid JSON!");
     }
     return $body;
 }
 private function makeTokenRequest(array $params)
 {
     $uri = $this->getTokenBaseURI();
     $query_data = array('client_id' => $this->getClientID(), 'client_secret' => $this->getClientSecret()->openEnvelope(), 'redirect_uri' => $this->getRedirectURI()) + $params;
     $future = new HTTPSFuture($uri, $query_data);
     $future->setMethod('POST');
     list($body) = $future->resolvex();
     $data = $this->readAccessTokenResponse($body);
     if (isset($data['expires_in'])) {
         $data['expires_epoch'] = $data['expires_in'];
     } else {
         if (isset($data['expires'])) {
             $data['expires_epoch'] = $data['expires'];
         }
     }
     // If we got some "expires" value back, interpret it as an epoch timestamp
     // if it's after the year 2010 and as a relative number of seconds
     // otherwise.
     if (isset($data['expires_epoch'])) {
         if ($data['expires_epoch'] < 60 * 60 * 24 * 365 * 40) {
             $data['expires_epoch'] += time();
         }
     }
     if (isset($data['error'])) {
         throw new Exception('Access token error: ' . $data['error']);
     }
     return $data;
 }