From 6c8c03142db734678d460ee085a73df1d05a88a1 Mon Sep 17 00:00:00 2001 From: brettp Date: Tue, 15 Mar 2011 03:46:37 +0000 Subject: Refs #3115. Moved oauth_lib to oauth_api. git-svn-id: http://code.elgg.org/elgg/trunk@8715 36083f99-b078-4883-b0ff-0f9b5a30f544 --- .../vendors/oauth/library/OAuthRequest.php | 801 +++++++++++++++++++++ 1 file changed, 801 insertions(+) create mode 100644 mod/oauth_api/vendors/oauth/library/OAuthRequest.php (limited to 'mod/oauth_api/vendors/oauth/library/OAuthRequest.php') diff --git a/mod/oauth_api/vendors/oauth/library/OAuthRequest.php b/mod/oauth_api/vendors/oauth/library/OAuthRequest.php new file mode 100644 index 000000000..c0d6ddbc7 --- /dev/null +++ b/mod/oauth_api/vendors/oauth/library/OAuthRequest.php @@ -0,0 +1,801 @@ + + * @date Nov 16, 2007 12:20:31 PM + * + * The MIT License + * + * Copyright (c) 2007-2008 Mediamatic Lab + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +require_once dirname(__FILE__) . '/OAuthException.php'; + +/** + * Object to parse an incoming OAuth request or prepare an outgoing OAuth request + */ +class OAuthRequest +{ + /* the realm for this request */ + protected $realm; + + /* all the parameters, RFC3986 encoded name/value pairs */ + protected $param = array(); + + /* the parsed request uri */ + protected $uri_parts; + + /* the raw request uri */ + protected $uri; + + /* the request headers */ + protected $headers; + + /* the request method */ + protected $method; + + /* the body of the OAuth request */ + protected $body; + + + /** + * Construct from the current request. Useful for checking the signature of a request. + * When not supplied with any parameters this will use the current request. + * + * @param string uri might include parameters + * @param string method GET, PUT, POST etc. + * @param string parameters additional post parameters, urlencoded (RFC1738) + * @param array headers headers for request + * @param string body optional body of the OAuth request (POST or PUT) + */ + function __construct ( $uri = null, $method = 'GET', $parameters = '', $headers = array(), $body = null ) + { + if (empty($uri)) + { + if (is_object($_SERVER)) + { + // Tainted arrays - the normal stuff in anyMeta + $method = $_SERVER->REQUEST_METHOD->getRawUnsafe(); + $uri = $_SERVER->REQUEST_URI->getRawUnsafe(); + } + else + { + // non anyMeta systems + $method = $_SERVER['REQUEST_METHOD']; + $uri = $_SERVER['REQUEST_URI']; + } + $headers = getallheaders(); + $parameters = ''; + $this->method = strtoupper($method); + + // If this is a post then also check the posted variables + if (strcasecmp($method, 'POST') == 0) + { + /* + // TODO: what to do with 'multipart/form-data'? + if ($this->getRequestContentType() == 'multipart/form-data') + { + throw new OAuthException('Unsupported POST content type, expected "application/x-www-form-urlencoded" got "'.@$_SERVER['CONTENT_TYPE'].'"'); + } + */ + if ($this->getRequestContentType() == 'application/x-www-form-urlencoded') + { + // Get the posted body (when available) + if (!isset($headers['X-OAuth-Test'])) + { + $parameters .= $this->getRequestBody(); + } + } + else + { + $body = $this->getRequestBody(); + } + } + else if (strcasecmp($method, 'PUT') == 0) + { + $body = $this->getRequestBody(); + } + } + + $this->method = strtoupper($method); + $this->headers = $headers; + // Store the values, prepare for oauth + $this->uri = $uri; + $this->body = $body; + $this->parseUri($parameters); + $this->parseHeaders(); + $this->transcodeParams(); + } + + + /** + * Return the signature base string. + * Note that we can't use rawurlencode due to specified use of RFC3986. + * + * @return string + */ + function signatureBaseString () + { + $sig = array(); + $sig[] = $this->method; + $sig[] = $this->getRequestUrl(); + $sig[] = $this->getNormalizedParams(); + + return implode('&', array_map(array($this, 'urlencode'), $sig)); + } + + + /** + * Calculate the signature of the request, using the method in oauth_signature_method. + * The signature is returned encoded in the form as used in the url. So the base64 and + * urlencoding has been done. + * + * @param string consumer_secret + * @param string token_secret + * @exception when not all parts available + * @return string + */ + function calculateSignature ( $consumer_secret, $token_secret, $token_type = 'access' ) + { + $required = array( + 'oauth_consumer_key', + 'oauth_signature_method', + 'oauth_timestamp', + 'oauth_nonce' + ); + + if ($token_type !== false) + { + $required[] = 'oauth_token'; + } + + foreach ($required as $req) + { + if (!isset($this->param[$req])) + { + throw new OAuthException('Can\'t sign request, missing parameter "'.$req.'"'); + } + } + + $this->checks(); + + $base = $this->signatureBaseString(); + $signature = $this->calculateDataSignature($base, $consumer_secret, $token_secret, $this->param['oauth_signature_method']); + return $signature; + } + + + /** + * Calculate the signature of a string. + * Uses the signature method from the current parameters. + * + * @param string data + * @param string consumer_secret + * @param string token_secret + * @param string signature_method + * @exception OAuthException thrown when the signature method is unknown + * @return string signature + */ + function calculateDataSignature ( $data, $consumer_secret, $token_secret, $signature_method ) + { + if (is_null($data)) + { + $data = ''; + } + + $sig = $this->getSignatureMethod($signature_method); + return $sig->signature($this, $data, $consumer_secret, $token_secret); + } + + + /** + * Select a signature method from the list of available methods. + * We try to check the most secure methods first. + * + * @todo Let the signature method tell us how secure it is + * @param array methods + * @exception OAuthException when we don't support any method in the list + * @return string + */ + public function selectSignatureMethod ( $methods ) + { + if (in_array('HMAC-SHA1', $methods)) + { + $method = 'HMAC-SHA1'; + } + else if (in_array('MD5', $methods)) + { + $method = 'MD5'; + } + else + { + $method = false; + foreach ($methods as $m) + { + $m = strtoupper($m); + $m = preg_replace('/[^A-Z0-9]/', '_', $m); + if (file_exists(dirname(__FILE__).'/signature_method/OAuthSignatureMethod_'.$m.'.php')) + { + $method = $m; + break; + } + } + + if (empty($method)) + { + throw new OAuthException('None of the signing methods is supported.'); + } + } + return $method; + } + + + /** + * Fetch the signature object used for calculating and checking the signature base string + * + * @param string method + * @return OAuthSignatureMethod object + */ + function getSignatureMethod ( $method ) + { + $m = strtoupper($method); + $m = preg_replace('/[^A-Z0-9]/', '_', $m); + $class = 'OAuthSignatureMethod_'.$m; + + if (file_exists(dirname(__FILE__).'/signature_method/'.$class.'.php')) + { + require_once dirname(__FILE__).'/signature_method/'.$class.'.php'; + $sig = new $class(); + } + else + { + throw new OAuthException('Unsupported signature method "'.$m.'".'); + } + return $sig; + } + + + /** + * Perform some sanity checks. + * + * @exception OAuthException thrown when sanity checks failed + */ + function checks () + { + if (isset($this->param['oauth_version'])) + { + $version = $this->urldecode($this->param['oauth_version']); + if ($version != '1.0') + { + throw new OAuthException('Expected OAuth version 1.0, got "'.$this->param['oauth_version'].'"'); + } + } + } + + + /** + * Return the request method + * + * @return string + */ + function getMethod () + { + return $this->method; + } + + /** + * Return the complete parameter string for the signature check. + * All parameters are correctly urlencoded and sorted on name and value + * + * @return string + */ + function getNormalizedParams () + { + /* + // sort by name, then by value + // (needed when we start allowing multiple values with the same name) + $keys = array_keys($this->param); + $values = array_values($this->param); + array_multisort($keys, SORT_ASC, $values, SORT_ASC); + */ + $params = $this->param; + $normalized = array(); + + ksort($params); + foreach ($params as $key => $value) + { + // all names and values are already urlencoded, exclude the oauth signature + if ($key != 'oauth_signature') + { + if (is_array($value)) + { + $value_sort = $value; + sort($value_sort); + foreach ($value_sort as $v) + { + $normalized[] = $key.'='.$v; + } + } + else + { + $normalized[] = $key.'='.$value; + } + } + } + return implode('&', $normalized); + } + + + /** + * Return the normalised url for signature checks + */ + function getRequestUrl () + { + $url = $this->uri_parts['scheme'] . '://' + . $this->uri_parts['user'] . (!empty($this->uri_parts['pass']) ? ':' : '') + . $this->uri_parts['pass'] . (!empty($this->uri_parts['user']) ? '@' : '') + . $this->uri_parts['host']; + + if ( $this->uri_parts['port'] + && $this->uri_parts['port'] != $this->defaultPortForScheme($this->uri_parts['scheme'])) + { + $url .= ':'.$this->uri_parts['port']; + } + if (!empty($this->uri_parts['path'])) + { + $url .= $this->uri_parts['path']; + } + return $url; + } + + + /** + * Get a parameter, value is always urlencoded + * + * @param string name + * @param boolean urldecode set to true to decode the value upon return + * @return string value false when not found + */ + function getParam ( $name, $urldecode = false ) + { + if (isset($this->param[$name])) + { + $s = $this->param[$name]; + } + else if (isset($this->param[$this->urlencode($name)])) + { + $s = $this->param[$this->urlencode($name)]; + } + else + { + $s = false; + } + if (!empty($s) && $urldecode) + { + if (is_array($s)) + { + $s = array_map(array($this,'urldecode'), $s); + } + else + { + $s = $this->urldecode($s); + } + } + return $s; + } + + /** + * Set a parameter + * + * @param string name + * @param string value + * @param boolean encoded set to true when the values are already encoded + */ + function setParam ( $name, $value, $encoded = false ) + { + if (!$encoded) + { + $name_encoded = $this->urlencode($name); + if (is_array($value)) + { + foreach ($value as $v) + { + $this->param[$name_encoded][] = $this->urlencode($v); + } + } + else + { + $this->param[$name_encoded] = $this->urlencode($value); + } + } + else + { + $this->param[$name] = $value; + } + } + + + /** + * Re-encode all parameters so that they are encoded using RFC3986. + * Updates the $this->param attribute. + */ + protected function transcodeParams () + { + $params = $this->param; + $this->param = array(); + + foreach ($params as $name=>$value) + { + if (is_array($value)) + { + $this->param[$this->urltranscode($name)] = array_map(array($this,'urltranscode'), $value); + } + else + { + $this->param[$this->urltranscode($name)] = $this->urltranscode($value); + } + } + } + + + + /** + * Return the body of the OAuth request. + * + * @return string null when no body + */ + function getBody () + { + return $this->body; + } + + + /** + * Return the body of the OAuth request. + * + * @return string null when no body + */ + function setBody ( $body ) + { + $this->body = $body; + } + + + /** + * Parse the uri into its parts. Fill in the missing parts. + * + * @todo check for the use of https, right now we default to http + * @todo support for multiple occurences of parameters + * @param string $parameters optional extra parameters (from eg the http post) + */ + protected function parseUri ( $parameters ) + { + $ps = parse_url($this->uri); + + // Get the current/requested method + if (empty($ps['scheme'])) + { + $ps['scheme'] = 'http'; + } + else + { + $ps['scheme'] = strtolower($ps['scheme']); + } + + // Get the current/requested host + if (empty($ps['host'])) + { + if (isset($_SERVER['HTTP_HOST'])) + { + $ps['host'] = $_SERVER['HTTP_HOST']; + } + else + { + $ps['host'] = ''; + } + } + $ps['host'] = mb_strtolower($ps['host']); + if (!preg_match('/^[a-z0-9\.\-]+$/', $ps['host'])) + { + throw new OAuthException('Unsupported characters in host name'); + } + + // Get the port we are talking on + if (empty($ps['port'])) + { + $ps['port'] = $this->defaultPortForScheme($ps['scheme']); + } + + if (empty($ps['user'])) + { + $ps['user'] = ''; + } + if (empty($ps['pass'])) + { + $ps['pass'] = ''; + } + if (empty($ps['path'])) + { + $ps['path'] = '/'; + } + if (empty($ps['query'])) + { + $ps['query'] = ''; + } + if (empty($ps['fragment'])) + { + $ps['fragment'] = ''; + } + + // Now all is complete - parse all parameters + foreach (array($ps['query'], $parameters) as $params) + { + if (strlen($params) > 0) + { + $params = explode('&', $params); + foreach ($params as $p) + { + @list($name, $value) = explode('=', $p, 2); + $this->param[$name] = $value; + } + } + } + $this->uri_parts = $ps; + } + + + /** + * Return the default port for a scheme + * + * @param string scheme + * @return int + */ + protected function defaultPortForScheme ( $scheme ) + { + switch ($scheme) + { + case 'http': return 80; + case 'https': return 43; + default: + throw new OAuthException('Unsupported scheme type, expected http or https, got "'.$scheme.'"'); + break; + } + } + + + /** + * Encode a string according to the RFC3986 + * + * @param string s + * @return string + */ + function urlencode ( $s ) + { + if ($s === false) + { + return $s; + } + else + { + return str_replace('%7E', '~', rawurlencode($s)); + } + } + + /** + * Decode a string according to RFC3986. + * Also correctly decodes RFC1738 urls. + * + * @param string s + * @return string + */ + function urldecode ( $s ) + { + if ($s === false) + { + return $s; + } + else + { + return rawurldecode($s); + } + } + + /** + * urltranscode - make sure that a value is encoded using RFC3986. + * We use a basic urldecode() function so that any use of '+' as the + * encoding of the space character is correctly handled. + * + * @param string s + * @return string + */ + function urltranscode ( $s ) + { + if ($s === false) + { + return $s; + } + else + { + return $this->urlencode(urldecode($s)); + } + } + + + /** + * Parse the oauth parameters from the request headers + * Looks for something like: + * + * Authorization: OAuth realm="http://photos.example.net/authorize", + * oauth_consumer_key="dpf43f3p2l4k3l03", + * oauth_token="nnch734d00sl2jdk", + * oauth_signature_method="HMAC-SHA1", + * oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D", + * oauth_timestamp="1191242096", + * oauth_nonce="kllo9940pd9333jh", + * oauth_version="1.0" + */ + private function parseHeaders () + { +/* + $this->headers['Authorization'] = 'OAuth realm="http://photos.example.net/authorize", + oauth_consumer_key="dpf43f3p2l4k3l03", + oauth_token="nnch734d00sl2jdk", + oauth_signature_method="HMAC-SHA1", + oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D", + oauth_timestamp="1191242096", + oauth_nonce="kllo9940pd9333jh", + oauth_version="1.0"'; +*/ + if (isset($this->headers['Authorization'])) + { + $auth = trim($this->headers['Authorization']); + if (strncasecmp($auth, 'OAuth', 4) == 0) + { + $vs = explode(',', substr($auth, 6)); + foreach ($vs as $v) + { + if (strpos($v, '=')) + { + $v = trim($v); + list($name,$value) = explode('=', $v, 2); + if (!empty($value) && $value{0} == '"' && substr($value, -1) == '"') + { + $value = substr(substr($value, 1), 0, -1); + } + + if (strcasecmp($name, 'realm') == 0) + { + $this->realm = $value; + } + else + { + $this->param[$name] = $value; + } + } + } + } + } + } + + + /** + * Fetch the content type of the current request + * + * @return string + */ + private function getRequestContentType () + { + $content_type = 'application/octet-stream'; + if (!empty($_SERVER) && array_key_exists('CONTENT_TYPE', $_SERVER)) + { + list($content_type) = explode(';', $_SERVER['CONTENT_TYPE']); + } + return trim($content_type); + } + + + /** + * Get the body of a POST or PUT. + * + * Used for fetching the post parameters and to calculate the body signature. + * + * @return string null when no body present (or wrong content type for body) + */ + private function getRequestBody () + { + $body = null; + if ($this->method == 'POST' || $this->method == 'PUT') + { + $body = ''; + $fh = @fopen('php://input', 'r'); + if ($fh) + { + while (!feof($fh)) + { + $s = fread($fh, 1024); + if (is_string($s)) + { + $body .= $s; + } + } + fclose($fh); + } + } + return $body; + } + + + /** + * Simple function to perform a redirect (GET). + * Redirects the User-Agent, does not return. + * + * @param string uri + * @param array params parameters, urlencoded + * @exception OAuthException when redirect uri is illegal + */ + public function redirect ( $uri, $params ) + { + if (!empty($params)) + { + $q = array(); + foreach ($params as $name=>$value) + { + $q[] = $name.'='.$value; + } + $q_s = implode('&', $q); + + if (strpos($uri, '?')) + { + $uri .= '&'.$q_s; + } + else + { + $uri .= '?'.$q_s; + } + } + + // simple security - multiline location headers can inject all kinds of extras + $uri = preg_replace('/\s/', '%20', $uri); + if (strncasecmp($uri, 'http://', 7) && strncasecmp($uri, 'https://', 8)) + { + if (strpos($uri, '://')) + { + throw new OAuthException('Illegal protocol in redirect uri '.$uri); + } + $uri = 'http://'.$uri; + } + + header('HTTP/1.1 302 Found'); + header('Location: '.$uri); + echo ''; + exit(); + } + +} + + +/* vi:set ts=4 sts=4 sw=4 binary noeol: */ + +?> \ No newline at end of file -- cgit v1.2.3