123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397 |
- <?php
- class DrupalOAuthClient {
- public $version = OAUTH_COMMON_VERSION_1_RFC;
- protected $consumer;
- protected $requestToken;
- protected $accessToken;
- protected $signatureMethod;
- /**
- * Creates an instance of the DrupalOAuthClient.
- *
- * @param DrupalOAuthConsumer $consumer
- * The consumer.
- * @param OAuthToken $request_token
- * Optional. A request token to use.
- * @param OAuthSignatureMethod $signature_method
- * Optional. The signature method to use.
- * @param integer $version
- * Optional. The version to use - either OAUTH_COMMON_VERSION_1_RFC or OAUTH_COMMON_VERSION_1.
- */
- public function __construct($consumer, $request_token = NULL, $signature_method = NULL, $version = NULL) {
- $this->consumer = $consumer;
- $this->requestToken = $request_token;
- $this->signatureMethod = $signature_method;
- if ($version) {
- $this->version = $version;
- }
- // Set to the default signature method if no method was specified
- if (!$this->signatureMethod) {
- if (!empty($this->consumer->configuration['signature_method'])) {
- $signature_method = substr(strtolower($this->consumer->configuration['signature_method']), 5);
- }
- else {
- $signature_method = 'SHA1';
- }
- $this->signatureMethod = self::signatureMethod($signature_method);
- }
- }
- /**
- * Convenience function to get signing method implementations.
- *
- * @param string $method
- * Optional. The hmac hashing algorithm to use. Defaults to 'sha512' which
- * has superseded sha1 as the recommended alternative.
- * @param bool $fallback_to_sha1
- * Optional. Whether sha1 should be used as a fallback if the selected
- * hashing algorithm is unavailable.
- * @return OAuthSignatureMethod
- * The signature method object.
- */
- public static function signatureMethod($method = 'SHA1', $fallback_to_sha1 = TRUE) {
- $sign = NULL;
- if (in_array(drupal_strtolower($method), hash_algos())) {
- $sign = new OAuthSignatureMethod_HMAC($method);
- }
- else if ($fallback_to_sha1) {
- $sign = new OAuthSignatureMethod_HMAC('SHA1');
- }
- return $sign;
- }
- /**
- * Gets a request token from the provider.
- *
- * @param string $endpoint
- * Optional. The endpoint path for the provider.
- * - If you provide the full URL (e.g. "http://example.com/oauth/request_token"),
- * then it will be used.
- * - If you provide only the path (e.g. "oauth/request_token"), it will
- * be converted into a full URL by prepending the provider_url.
- * - If you provide nothing it will default to '/oauth/request_token'.
- * @param array $options
- * An associative array of additional optional options, with the following keys:
- * - 'params'
- * An associative array of parameters that should be included in the
- * request.
- * - 'realm'
- * A string to be used as the http authentication realm in the request.
- * - 'get' (default FALSE)
- * Whether to use GET as the HTTP-method instead of POST.
- * - 'callback'
- * A full URL of where the user should be sent after the request token
- * has been authorized.
- * Only used by versions higher than OAUTH_COMMON_VERSION_1.
- * @return DrupalOAuthToken
- * The returned request token.
- */
- public function getRequestToken($endpoint = NULL, $options = array()) {
- if ($this->requestToken) {
- return clone $this->requestToken;
- }
- $options += array(
- 'params' => array(),
- 'realm' => NULL,
- 'get' => FALSE,
- 'callback' => NULL,
- );
- if (empty($endpoint)) {
- if (!empty($this->consumer->configuration['request_endpoint'])) {
- $endpoint = $this->consumer->configuration['request_endpoint'];
- }
- else {
- $endpoint = '/oauth/request_token';
- }
- }
- if ($this->version > OAUTH_COMMON_VERSION_1) {
- $options['params']['oauth_callback'] = $options['callback'] ? $options['callback'] : 'oob';
- }
- $response = $this->get($endpoint, array(
- 'params' => $options['params'],
- 'realm' => $options['realm'],
- 'get' => $options['get'],
- ));
- $params = array();
- parse_str($response, $params);
- if (empty($params['oauth_token']) || empty($params['oauth_token_secret'])) {
- throw new Exception('No valid request token was returned');
- }
- if ($this->version > OAUTH_COMMON_VERSION_1 && empty($params['oauth_callback_confirmed'])) {
- $this->version = OAUTH_COMMON_VERSION_1;
- }
- $this->requestToken = new DrupalOAuthToken($params['oauth_token'], $params['oauth_token_secret'], $this->consumer, array(
- 'type' => OAUTH_COMMON_TOKEN_TYPE_REQUEST,
- 'version' => $this->version,
- ));
- return clone $this->requestToken;
- }
- /**
- * Constructs the url that the user should be sent to to authorize the
- * request token.
- *
- * @param string $endpoint
- * Optional. The endpoint path for the provider.
- * - If you provide the full URL (e.g. "http://example.com/oauth/authorize"),
- * then it will be used.
- * - If you provide only the path (e.g. "oauth/authorize"), it will
- * be converted into a full URL by prepending the provider_url.
- * - If you provide nothing it will default to '/oauth/authorize'.
- * @param array $options
- * An associative array of additional optional options, with the following keys:
- * - 'params'
- * An associative array of parameters that should be included in the
- * request.
- * - 'callback'
- * A full URL of where the user should be sent after the request token
- * has been authorized.
- * Only used by version OAUTH_COMMON_VERSION_1.
- * @return string
- * The url.
- */
- public function getAuthorizationUrl($endpoint = NULL, $options = array()) {
- $options += array(
- 'params' => array(),
- 'callback' => NULL,
- );
- if (empty($endpoint)) {
- if (!empty($this->consumer->configuration['authorization_endpoint'])) {
- $endpoint = $this->consumer->configuration['authorization_endpoint'];
- }
- else {
- $endpoint = '/oauth/authorize';
- }
- }
- if ($this->version == OAUTH_COMMON_VERSION_1 && $options['callback']) {
- $options['params']['oauth_callback'] = $options['callback'];
- }
- $options['params']['oauth_token'] = $this->requestToken->key;
- $endpoint = $this->getAbsolutePath($endpoint);
- $append_query = strpos($endpoint, '?') === FALSE ? '?' : '&';
- return $endpoint . $append_query . http_build_query($options['params'], NULL, '&');
- }
- /**
- * Fetches the access token using the request token.
- *
- * @param string $endpoint
- * Optional. The endpoint path for the provider.
- * - If you provide the full URL (e.g. "http://example.com/oauth/access_token"),
- * then it will be used.
- * - If you provide only the path (e.g. "oauth/access_token"), it will
- * be converted into a full URL by prepending the provider_url.
- * - If you provide nothing it will default to '/oauth/access_token'.
- * @param array $options
- * An associative array of additional optional options, with the following keys:
- * - 'params'
- * An associative array of parameters that should be included in the
- * request.
- * - 'realm'
- * A string to be used as the http authentication realm in the request.
- * - 'get' (default FALSE)
- * Whether to use GET as the HTTP-method instead of POST.
- * - 'verifier'
- * A string containing a verifier for he user from the provider.
- * Only used by versions higher than OAUTH_COMMON_VERSION_1.
- * @return DrupalOAuthToken
- * The access token.
- */
- public function getAccessToken($endpoint = NULL, $options = array()) {
- if ($this->accessToken) {
- return clone $this->accessToken;
- }
- $options += array(
- 'params' => array(),
- 'realm' => NULL,
- 'get' => FALSE,
- 'verifier' => NULL,
- );
- if (empty($endpoint)) {
- if (!empty($this->consumer->configuration['access_endpoint'])) {
- $endpoint = $this->consumer->configuration['access_endpoint'];
- }
- else {
- $endpoint = '/oauth/access_token';
- }
- }
- if ($this->version > OAUTH_COMMON_VERSION_1 && $options['verifier'] !== NULL) {
- $options['params']['oauth_verifier'] = $options['verifier'];
- }
- $response = $this->get($endpoint, array(
- 'token' => TRUE,
- 'params' => $options['params'],
- 'realm' => $options['realm'],
- 'get' => $options['get'],
- ));
- $params = array();
- parse_str($response, $params);
- if (empty($params['oauth_token']) || empty($params['oauth_token_secret'])) {
- throw new Exception('No valid access token was returned');
- }
- // Check if we've has recieved this token previously and if so use the old one
- //TODO: Is this safe!? What if eg. multiple users are getting the same access token from the provider?
- $this->accessToken = DrupalOAuthToken::loadByKey($params['oauth_token'], $this->consumer);
- //TODO: Can a secret change even though the token doesn't? If so it needs to be changed.
- if (!$this->accessToken) {
- $this->accessToken = new DrupalOAuthToken($params['oauth_token'], $params['oauth_token_secret'], $this->consumer, array(
- 'type' => OAUTH_COMMON_TOKEN_TYPE_ACCESS,
- ));
- }
- return clone $this->accessToken;
- }
- /**
- * Make an OAuth request.
- *
- * @param string $path
- * The path being requested.
- * - If you provide the full URL (e.g. "http://example.com/oauth/request_token"),
- * then it will be used.
- * - If you provide only the path (e.g. "oauth/request_token"), it will
- * be converted into a full URL by prepending the provider_url.
- * @param array $options
- * An associative array of additional options, with the following keys:
- * - 'token' (default FALSE)
- * Whether a token should be used or not.
- * - 'params'
- * An associative array of parameters that should be included in the
- * request.
- * - 'realm'
- * A string to be used as the http authentication realm in the request.
- * - 'get' (default FALSE)
- * Whether to use GET as the HTTP-method instead of POST.
- * @return string
- * a string containing the response body.
- */
- protected function get($path, $options = array()) {
- $options += array(
- 'token' => FALSE,
- 'params' => array(),
- 'realm' => NULL,
- 'get' => FALSE,
- );
- if (empty($options['realm']) && !empty($this->consumer->configuration['authentication_realm'])) {
- $options['realm'] = $this->consumer->configuration['authentication_realm'];
- }
- $token = $options['token'] ? $this->requestToken : NULL;
- $path = $this->getAbsolutePath($path);
- $req = OAuthRequest::from_consumer_and_token($this->consumer, $token,
- $options['get'] ? 'GET' : 'POST', $path, $options['params']);
- $req->sign_request($this->signatureMethod, $this->consumer, $token);
- $url = $req->get_normalized_http_url();
- $params = array();
- foreach ($req->get_parameters() as $param_key => $param_value) {
- if (substr($param_key, 0, 5) != 'oauth') {
- $params[$param_key] = $param_value;
- }
- }
- if (!empty($params)) {
- $url .= '?' . http_build_query($params);
- }
- $headers = array(
- 'Accept: application/x-www-form-urlencoded',
- $req->to_header($options['realm']),
- );
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url);
- if (!$options['get']) {
- curl_setopt($ch, CURLOPT_POST, 1);
- curl_setopt($ch, CURLOPT_POSTFIELDS, '');
- }
- $oauth_version = _oauth_common_version();
- curl_setopt($ch, CURLOPT_USERAGENT, 'Drupal/' . VERSION . ' OAuth/' . $oauth_version);
- curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
- curl_setopt($ch, CURLOPT_HEADER, 1);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- $response = curl_exec($ch);
- $error = curl_error($ch);
- curl_close($ch);
- if ($error) {
- throw new Exception($error);
- }
- $result = $this->interpretResponse($response);
- if ($result->responseCode != 200) {
- throw new Exception('Failed to fetch data from url "' . $path . '" (HTTP response code ' . $result->responseCode . ' ' . $result->responseMessage . '): ' . $result->body, $result->responseCode);
- }
- return $result->body;
- }
- /**
- * Makes sure a path is an absolute path
- *
- * Prepends provider url if the path isn't absolute.
- *
- * @param string $path
- * The path to make absolute.
- * @return string
- * The absolute path.
- */
- protected function getAbsolutePath($path) {
- $protocols = array(
- 'http',
- 'https'
- );
- $protocol = strpos($path, '://');
- $protocol = $protocol ? substr($path, 0, $protocol) : '';
- if (!in_array($protocol, $protocols)) {
- $path = $this->consumer->configuration['provider_url'] . $path;
- }
- return $path;
- }
- protected function interpretResponse($res) {
- list($headers, $body) = preg_split('/\r\n\r\n/', $res, 2);
- $obj = (object) array(
- 'headers' => $headers,
- 'body' => $body,
- );
- $matches = array();
- if (preg_match('/HTTP\/1.\d (\d{3}) (.*)/', $headers, $matches)) {
- $obj->responseCode = trim($matches[1]);
- $obj->responseMessage = trim($matches[2]);
- // Handle HTTP/1.1 100 Continue
- if ($obj->responseCode == 100) {
- return $this->interpretResponse($body);
- }
- }
- return $obj;
- }
- }
|