diff --git a/modules/oauth/bin/demo.php b/modules/oauth/bin/demo.php new file mode 100755 index 0000000000000000000000000000000000000000..e73fe124ea6054b9859fffbd1af8dbd3835dfdc2 --- /dev/null +++ b/modules/oauth/bin/demo.php @@ -0,0 +1,43 @@ +#!/usr/bin/env php +<?php + +/* This is the base directory of the simpleSAMLphp installation. */ +$baseDir = dirname(dirname(dirname(dirname(__FILE__)))); + +/* Add library autoloader. */ +require_once($baseDir . '/lib/_autoload.php'); + + +require_once(dirname(dirname(__FILE__)) . '/libextinc/OAuth.php'); + +$baseurl = (isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : 'https://foodle.feide.no/simplesaml'); +$key = (isset($_SERVER['argv'][2]) ? $_SERVER['argv'][1] : 'key'); +$secret = (isset($_SERVER['argv'][3]) ? $_SERVER['argv'][1] : 'secret'); + +echo 'Welcome to the OAuth CLI client' . "\n"; +$consumer = new sspmod_oauth_Consumer($key, $secret); + +// Get the request token +$requestToken = $consumer->getRequestToken($baseurl . '/module.php/oauth/requestToken.php'); +echo "Got a request token from the OAuth service provider [" . $requestToken->key . "] with the secret [" . $requestToken->secret . "]\n"; + +// Authorize the request token +$consumer->getAuthorizeRequest($baseurl . '/module.php/oauth/authorize.php', $requestToken); + +// Replace the request token with an access token +$accessToken = $consumer->getAccessToken( $baseurl . '/module.php/oauth/accessToken.php', $requestToken); +echo "Got an access token from the OAuth service provider [" . $accessToken->key . "] with the secret [" . $accessToken->secret . "]\n"; + +$userdata = $consumer->getUserInfo($baseurl . '/module.php/oauth/getUserInfo.php', $accessToken); + + +echo 'You are successfully authenticated to this Command Line CLI. ' . "\n"; +echo 'Got data [' . join(', ', array_keys($userdata)) . ']' . "\n"; +echo 'Your user ID is : ' . $userdata['eduPersonPrincipalName'][0] . "\n"; + + + + + + + diff --git a/modules/oauth/default-disable b/modules/oauth/default-disable new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/modules/oauth/lib/Consumer.php b/modules/oauth/lib/Consumer.php new file mode 100644 index 0000000000000000000000000000000000000000..4a7407442412d0d4358d3b0e54ca0dc5aa1d15f0 --- /dev/null +++ b/modules/oauth/lib/Consumer.php @@ -0,0 +1,83 @@ +<?php + +require_once(dirname(dirname(__FILE__)) . '/libextinc/OAuth.php'); + +/** + * OAuth Consumer + * + * @author Andreas Ă…kre Solberg, <andreas.solberg@uninett.no>, UNINETT AS. + * @package simpleSAMLphp + * @version $Id$ + */ +class sspmod_oauth_Consumer { + + private $consumer; + private $signer; + + public function __construct($key, $secret) { + $this->consumer = new OAuthConsumer($key, $secret, NULL); + $this->signer = new OAuthSignatureMethod_HMAC_SHA1(); + } + + public function getRequestToken($url) { + $req_req = OAuthRequest::from_consumer_and_token($this->consumer, NULL, "GET", $url, NULL); + $req_req->sign_request($this->signer, $this->consumer, NULL); + + echo "Requesting a request token\n"; + // echo 'go to url: ' . $req_req->to_url() . "\n"; exit; + $response_req = file_get_contents($req_req->to_url()); + + parse_str($response_req, $responseParsed); + + if(array_key_exists('error', $responseParsed)) + throw new Exception('Error getting request token: ') . $responseParsed['error']; + + $requestToken = $responseParsed['oauth_token']; + $requestTokenSecret = $responseParsed['oauth_token_secret']; + + return new OAuthToken($requestToken, $requestTokenSecret); + } + + public function getAuthorizeRequest($url, $requestToken) { + $authorizeURL = $url . '?oauth_token=' . $requestToken->key; + + echo "Please go to this URL to authorize access: " . $authorizeURL . "\n"; + system("open " . $authorizeURL); + + echo "Waiting 15 seconds for you to authenticate. Usually you should let the user enter return or click a continue button.\n"; + + sleep(15); + } + + public function getAccessToken($url, $requestToken) { + + $acc_req = OAuthRequest::from_consumer_and_token($this->consumer, $requestToken, "GET", $url, NULL); + $acc_req->sign_request($this->signer, $this->consumer, $requestToken); + + $response_acc = file_get_contents($acc_req->to_url()); + + parse_str($response_acc, $accessResponseParsed); + + if(array_key_exists('error', $accessResponseParsed)) + throw new Exception('Error getting request token: ') . $accessResponseParsed['error']; + + $accessToken = $accessResponseParsed['oauth_token']; + $accessTokenSecret = $accessResponseParsed['oauth_token_secret']; + + return new OAuthToken($accessToken, $accessTokenSecret); + } + + public function getUserInfo($url, $accessToken) { + + $data_req = OAuthRequest::from_consumer_and_token($this->consumer, $accessToken, "GET", $url, NULL); + $data_req->sign_request($this->signer, $this->consumer, $accessToken); + + $data = file_get_contents($data_req->to_url()); + // print_r($data); + + $dataDecoded = json_decode($data, TRUE); + return $dataDecoded; + } + +} + diff --git a/modules/oauth/lib/OAuthServer.php b/modules/oauth/lib/OAuthServer.php new file mode 100644 index 0000000000000000000000000000000000000000..195ab24be618c06fdb429c2a97fb01fa69c42d7a --- /dev/null +++ b/modules/oauth/lib/OAuthServer.php @@ -0,0 +1,17 @@ +<?php + +require_once(dirname(dirname(__FILE__)) . '/libextinc/OAuth.php'); + +/** + * OAuth Provider implementation.. + * + * @author Andreas Ă…kre Solberg, <andreas.solberg@uninett.no>, UNINETT AS. + * @package simpleSAMLphp + * @version $Id$ + */ +class sspmod_oauth_OAuthServer extends OAuthServer { + public function get_signature_methods() { + return $this->signature_methods; + } +} + diff --git a/modules/oauth/lib/OAuthStore.php b/modules/oauth/lib/OAuthStore.php new file mode 100644 index 0000000000000000000000000000000000000000..20e8ab3f49dcb5f45ae4c5734cf949fbc4f6e7e4 --- /dev/null +++ b/modules/oauth/lib/OAuthStore.php @@ -0,0 +1,94 @@ +<?php + +require_once(dirname(dirname(__FILE__)) . '/libextinc/OAuth.php'); + +/** + * OAuth Provider implementation.. + * + * @author Andreas Ă…kre Solberg, <andreas.solberg@uninett.no>, UNINETT AS. + * @package simpleSAMLphp + * @version $Id$ + */ +class sspmod_oauth_OAuthStore extends OAuthDataStore { + + private $path; + + function __construct($path = '/tmp/oauth/') { + $this->path = $path; + } + + private function filename($key) { + return $this->path . sha1($key) . '.oauthstore'; + } + + private function exists($key) { + return file_exists($this->filename($key)); + } + + private function get($key) { + error_log( 'Getting :' . $key . ' : ' . ($this->exists($key) ? 'FOUND' : 'NOTFOUND')); + if (!$this->exists($key)) return NULL; + return unserialize(file_get_contents($this->filename($key))); + } + + private function set($key, $value) { + error_log('Setting :' . $key . ' : ' . ($this->exists($key) ? 'FOUND' : 'NOTFOUND')); + file_put_contents($this->filename($key), serialize($value)); + } + + private function remove($key) { + unlink($this->filename($key)); + } + + public function authorize($requestToken, $data) { + $this->set('validated.request.' . $requestToken, $data); + } + + public function isAuthorized($requestToken) { + return $this->exists('validated.request.' . $requestToken); + } + + public function getAuthorizedData($token) { + return $this->get('validated.request.' . $token); + } + + public function moveAuthorizedData($requestToken, $accessToken) { + $this->authorize($accessToken, $this->getAuthorizedData($requestToken)); + $this->remove('validated.request.' . $requestToken); + } + + private function tokenTag($tokenType = 'default', $token) { + return 'token.' . $token . '.' . $tokenType; + } + + function lookup_consumer($consumer_key) { + if ($consumer_key == 'key') return new OAuthConsumer("key", "secret", NULL); + return NULL; + } + + function lookup_token($consumer, $tokenType = 'default', $token) { + return $this->get($this->tokenTag($tokenType, $token)); + } + + function lookup_nonce($consumer, $token, $nonce, $timestamp) { + $nonceTag = 'nonce.' . $consumer->key . '.' . $nonce; + if ($this->exists($nonceTag)) + return TRUE; + + $this->set($nonceTag, $timestamp); + return FALSE; + } + + function new_request_token($consumer) { + $token = new OAuthToken(SimpleSAML_Utilities::generateID(), SimpleSAML_Utilities::generateID()); + $this->set($this->tokenTag('request', $token->key), $token); + return $token; + } + + function new_access_token($token, $consumer) { + $token = new OAuthToken(SimpleSAML_Utilities::generateID(), SimpleSAML_Utilities::generateID()); + $this->set($this->tokenTag('access', $token->key), $token); + return $token; + } + +} diff --git a/modules/oauth/libextinc/OAuth.php b/modules/oauth/libextinc/OAuth.php new file mode 100644 index 0000000000000000000000000000000000000000..029166175c5f4b6ad07fac4bb47ea0502c7b95f9 --- /dev/null +++ b/modules/oauth/libextinc/OAuth.php @@ -0,0 +1,770 @@ +<?php +// vim: foldmethod=marker + +/* Generic exception class + */ +class OAuthException extends Exception {/*{{{*/ + // pass +}/*}}}*/ + +class OAuthConsumer {/*{{{*/ + public $key; + public $secret; + + function __construct($key, $secret, $callback_url=NULL) {/*{{{*/ + $this->key = $key; + $this->secret = $secret; + $this->callback_url = $callback_url; + }/*}}}*/ + + function __toString() {/*{{{*/ + return "OAuthConsumer[key=$this->key,secret=$this->secret]"; + }/*}}}*/ +}/*}}}*/ + +class OAuthToken {/*{{{*/ + // access tokens and request tokens + public $key; + public $secret; + + /** + * key = the token + * secret = the token secret + */ + function __construct($key, $secret) {/*{{{*/ + $this->key = $key; + $this->secret = $secret; + }/*}}}*/ + + /** + * generates the basic string serialization of a token that a server + * would respond to request_token and access_token calls with + */ + function to_string() {/*{{{*/ + return "oauth_token=" . OAuthUtil::urlencode_rfc3986($this->key) . + "&oauth_token_secret=" . OAuthUtil::urlencode_rfc3986($this->secret); + }/*}}}*/ + + function __toString() {/*{{{*/ + return $this->to_string(); + }/*}}}*/ +}/*}}}*/ + +class OAuthSignatureMethod {/*{{{*/ + public function check_signature(&$request, $consumer, $token, $signature) { + $built = $this->build_signature($request, $consumer, $token); + return $built == $signature; + } +}/*}}}*/ + +class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {/*{{{*/ + function get_name() {/*{{{*/ + return "HMAC-SHA1"; + }/*}}}*/ + + public function build_signature($request, $consumer, $token) {/*{{{*/ + $base_string = $request->get_signature_base_string(); + $request->base_string = $base_string; + + $key_parts = array( + $consumer->secret, + ($token) ? $token->secret : "" + ); + + $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); + $key = implode('&', $key_parts); + + return base64_encode( hash_hmac('sha1', $base_string, $key, true)); + }/*}}}*/ +}/*}}}*/ + +class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {/*{{{*/ + public function get_name() {/*{{{*/ + return "PLAINTEXT"; + }/*}}}*/ + + public function build_signature($request, $consumer, $token) {/*{{{*/ + $sig = array( + OAuthUtil::urlencode_rfc3986($consumer->secret) + ); + + if ($token) { + array_push($sig, OAuthUtil::urlencode_rfc3986($token->secret)); + } else { + array_push($sig, ''); + } + + $raw = implode("&", $sig); + // for debug purposes + $request->base_string = $raw; + + return OAuthUtil::urlencode_rfc3986($raw); + }/*}}}*/ +}/*}}}*/ + +class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {/*{{{*/ + public function get_name() {/*{{{*/ + return "RSA-SHA1"; + }/*}}}*/ + + protected function fetch_public_cert(&$request) {/*{{{*/ + // not implemented yet, ideas are: + // (1) do a lookup in a table of trusted certs keyed off of consumer + // (2) fetch via http using a url provided by the requester + // (3) some sort of specific discovery code based on request + // + // either way should return a string representation of the certificate + throw Exception("fetch_public_cert not implemented"); + }/*}}}*/ + + protected function fetch_private_cert(&$request) {/*{{{*/ + // not implemented yet, ideas are: + // (1) do a lookup in a table of trusted certs keyed off of consumer + // + // either way should return a string representation of the certificate + throw Exception("fetch_private_cert not implemented"); + }/*}}}*/ + + public function build_signature(&$request, $consumer, $token) {/*{{{*/ + $base_string = $request->get_signature_base_string(); + $request->base_string = $base_string; + + // Fetch the private key cert based on the request + $cert = $this->fetch_private_cert($request); + + // Pull the private key ID from the certificate + $privatekeyid = openssl_get_privatekey($cert); + + // Sign using the key + $ok = openssl_sign($base_string, $signature, $privatekeyid); + + // Release the key resource + openssl_free_key($privatekeyid); + + return base64_encode($signature); + } /*}}}*/ + + public function check_signature(&$request, $consumer, $token, $signature) {/*{{{*/ + $decoded_sig = base64_decode($signature); + + $base_string = $request->get_signature_base_string(); + + // Fetch the public key cert based on the request + $cert = $this->fetch_public_cert($request); + + // Pull the public key ID from the certificate + $publickeyid = openssl_get_publickey($cert); + + // Check the computed signature against the one passed in the query + $ok = openssl_verify($base_string, $decoded_sig, $publickeyid); + + // Release the key resource + openssl_free_key($publickeyid); + + return $ok == 1; + } /*}}}*/ +}/*}}}*/ + +class OAuthRequest {/*{{{*/ + private $parameters; + private $http_method; + private $http_url; + // for debug purposes + public $base_string; + public static $version = '1.0'; + + function __construct($http_method, $http_url, $parameters=NULL) {/*{{{*/ + @$parameters or $parameters = array(); + $this->parameters = $parameters; + $this->http_method = $http_method; + $this->http_url = $http_url; + }/*}}}*/ + + + /** + * attempt to build up a request from what was passed to the server + */ + public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {/*{{{*/ + $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") ? 'http' : 'https'; + @$http_url or $http_url = $scheme . '://' . $_SERVER['HTTP_HOST'] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI']; + @$http_method or $http_method = $_SERVER['REQUEST_METHOD']; + + $request_headers = OAuthRequest::get_headers(); + + // let the library user override things however they'd like, if they know + // which parameters to use then go for it, for example XMLRPC might want to + // do this + if ($parameters) { + $req = new OAuthRequest($http_method, $http_url, $parameters); + } else { + // collect request parameters from query string (GET) and post-data (POST) if appropriate (note: POST vars have priority) + $req_parameters = $_GET; + if ($http_method == "POST" && @strstr($request_headers["Content-Type"], "application/x-www-form-urlencoded") ) { + $req_parameters = array_merge($req_parameters, $_POST); + } + + // next check for the auth header, we need to do some extra stuff + // if that is the case, namely suck in the parameters from GET or POST + // so that we can include them in the signature + if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") { + $header_parameters = OAuthRequest::split_header($request_headers['Authorization']); + $parameters = array_merge($req_parameters, $header_parameters); + $req = new OAuthRequest($http_method, $http_url, $parameters); + } else $req = new OAuthRequest($http_method, $http_url, $req_parameters); + } + + return $req; + }/*}}}*/ + + /** + * pretty much a helper function to set up the request + */ + public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {/*{{{*/ + @$parameters or $parameters = array(); + $defaults = array("oauth_version" => OAuthRequest::$version, + "oauth_nonce" => OAuthRequest::generate_nonce(), + "oauth_timestamp" => OAuthRequest::generate_timestamp(), + "oauth_consumer_key" => $consumer->key); + $parameters = array_merge($defaults, $parameters); + + if ($token) { + $parameters['oauth_token'] = $token->key; + } + return new OAuthRequest($http_method, $http_url, $parameters); + }/*}}}*/ + + public function set_parameter($name, $value) {/*{{{*/ + $this->parameters[$name] = $value; + }/*}}}*/ + + public function get_parameter($name) {/*{{{*/ + return isset($this->parameters[$name]) ? $this->parameters[$name] : null; + }/*}}}*/ + + public function get_parameters() {/*{{{*/ + return $this->parameters; + }/*}}}*/ + + /** + * Returns the normalized parameters of the request + * + * This will be all (except oauth_signature) parameters, + * sorted first by key, and if duplicate keys, then by + * value. + * + * The returned string will be all the key=value pairs + * concated by &. + * + * @return string + */ + public function get_signable_parameters() {/*{{{*/ + // Grab all parameters + $params = $this->parameters; + + // Remove oauth_signature if present + if (isset($params['oauth_signature'])) { + unset($params['oauth_signature']); + } + + // Urlencode both keys and values + $keys = OAuthUtil::urlencode_rfc3986(array_keys($params)); + $values = OAuthUtil::urlencode_rfc3986(array_values($params)); + $params = array_combine($keys, $values); + + // Sort by keys (natsort) + uksort($params, 'strcmp'); + + // Generate key=value pairs + $pairs = array(); + foreach ($params as $key=>$value ) { + if (is_array($value)) { + // If the value is an array, it's because there are multiple + // with the same key, sort them, then add all the pairs + natsort($value); + foreach ($value as $v2) { + $pairs[] = $key . '=' . $v2; + } + } else { + $pairs[] = $key . '=' . $value; + } + } + + // Return the pairs, concated with & + return implode('&', $pairs); + }/*}}}*/ + + /** + * Returns the base string of this request + * + * The base string defined as the method, the url + * and the parameters (normalized), each urlencoded + * and the concated with &. + */ + public function get_signature_base_string() {/*{{{*/ + $parts = array( + $this->get_normalized_http_method(), + $this->get_normalized_http_url(), + $this->get_signable_parameters() + ); + + $parts = OAuthUtil::urlencode_rfc3986($parts); + + return implode('&', $parts); + }/*}}}*/ + + /** + * just uppercases the http method + */ + public function get_normalized_http_method() {/*{{{*/ + return strtoupper($this->http_method); + }/*}}}*/ + + /** + * parses the url and rebuilds it to be + * scheme://host/path + */ + public function get_normalized_http_url() {/*{{{*/ + $parts = parse_url($this->http_url); + + $port = @$parts['port']; + $scheme = $parts['scheme']; + $host = $parts['host']; + $path = @$parts['path']; + + $port or $port = ($scheme == 'https') ? '443' : '80'; + + if (($scheme == 'https' && $port != '443') + || ($scheme == 'http' && $port != '80')) { + $host = "$host:$port"; + } + return "$scheme://$host$path"; + }/*}}}*/ + + /** + * builds a url usable for a GET request + */ + public function to_url() {/*{{{*/ + $out = $this->get_normalized_http_url() . "?"; + $out .= $this->to_postdata(); + return $out; + }/*}}}*/ + + /** + * builds the data one would send in a POST request + * + * TODO(morten.fangel): + * this function might be easily replaced with http_build_query() + * and corrections for rfc3986 compatibility.. but not sure + */ + public function to_postdata() {/*{{{*/ + $total = array(); + foreach ($this->parameters as $k => $v) { + if (is_array($v)) { + foreach ($v as $va) { + $total[] = OAuthUtil::urlencode_rfc3986($k) . "[]=" . OAuthUtil::urlencode_rfc3986($va); + } + } else { + $total[] = OAuthUtil::urlencode_rfc3986($k) . "=" . OAuthUtil::urlencode_rfc3986($v); + } + } + $out = implode("&", $total); + return $out; + }/*}}}*/ + + /** + * builds the Authorization: header + */ + public function to_header() {/*{{{*/ + $out ='Authorization: OAuth realm=""'; + $total = array(); + foreach ($this->parameters as $k => $v) { + if (substr($k, 0, 5) != "oauth") continue; + if (is_array($v)) throw new OAuthException('Arrays not supported in headers'); + $out .= ',' . OAuthUtil::urlencode_rfc3986($k) . '="' . OAuthUtil::urlencode_rfc3986($v) . '"'; + } + return $out; + }/*}}}*/ + + public function __toString() {/*{{{*/ + return $this->to_url(); + }/*}}}*/ + + + public function sign_request($signature_method, $consumer, $token) {/*{{{*/ + $this->set_parameter("oauth_signature_method", $signature_method->get_name()); + $signature = $this->build_signature($signature_method, $consumer, $token); + $this->set_parameter("oauth_signature", $signature); + }/*}}}*/ + + public function build_signature($signature_method, $consumer, $token) {/*{{{*/ + $signature = $signature_method->build_signature($this, $consumer, $token); + return $signature; + }/*}}}*/ + + /** + * util function: current timestamp + */ + private static function generate_timestamp() {/*{{{*/ + return time(); + }/*}}}*/ + + /** + * util function: current nonce + */ + private static function generate_nonce() {/*{{{*/ + $mt = microtime(); + $rand = mt_rand(); + + return md5($mt . $rand); // md5s look nicer than numbers + }/*}}}*/ + + /** + * util function for turning the Authorization: header into + * parameters, has to do some unescaping + */ + private static function split_header($header) {/*{{{*/ + $pattern = '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/'; + $offset = 0; + $params = array(); + while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) { + $match = $matches[0]; + $header_name = $matches[2][0]; + $header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0]; + $params[$header_name] = OAuthUtil::urldecode_rfc3986( $header_content ); + $offset = $match[1] + strlen($match[0]); + } + + if (isset($params['realm'])) { + unset($params['realm']); + } + + return $params; + }/*}}}*/ + + /** + * helper to try to sort out headers for people who aren't running apache + */ + private static function get_headers() {/*{{{*/ + if (function_exists('apache_request_headers')) { + // we need this to get the actual Authorization: header + // because apache tends to tell us it doesn't exist + return apache_request_headers(); + } + // otherwise we don't have apache and are just going to have to hope + // that $_SERVER actually contains what we need + $out = array(); + foreach ($_SERVER as $key => $value) { + if (substr($key, 0, 5) == "HTTP_") { + // this is chaos, basically it is just there to capitalize the first + // letter of every word that is not an initial HTTP and strip HTTP + // code from przemek + $key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5))))); + $out[$key] = $value; + } + } + return $out; + }/*}}}*/ +}/*}}}*/ + +class OAuthServer {/*{{{*/ + protected $timestamp_threshold = 300; // in seconds, five minutes + protected $version = 1.0; // hi blaine + protected $signature_methods = array(); + + protected $data_store; + + function __construct($data_store) {/*{{{*/ + $this->data_store = $data_store; + }/*}}}*/ + + public function add_signature_method($signature_method) {/*{{{*/ + $this->signature_methods[$signature_method->get_name()] = + $signature_method; + }/*}}}*/ + + // high level functions + + /** + * process a request_token request + * returns the request token on success + */ + public function fetch_request_token(&$request) {/*{{{*/ + $this->get_version($request); + + $consumer = $this->get_consumer($request); + + // no token required for the initial token request + $token = NULL; + + $this->check_signature($request, $consumer, $token); + + $new_token = $this->data_store->new_request_token($consumer); + + return $new_token; + }/*}}}*/ + + /** + * process an access_token request + * returns the access token on success + */ + public function fetch_access_token(&$request) {/*{{{*/ + $this->get_version($request); + + $consumer = $this->get_consumer($request); + + // requires authorized request token + $token = $this->get_token($request, $consumer, "request"); + + + $this->check_signature($request, $consumer, $token); + + $new_token = $this->data_store->new_access_token($token, $consumer); + + return $new_token; + }/*}}}*/ + + /** + * verify an api call, checks all the parameters + */ + public function verify_request(&$request) {/*{{{*/ + $this->get_version($request); + $consumer = $this->get_consumer($request); + $token = $this->get_token($request, $consumer, "access"); + $this->check_signature($request, $consumer, $token); + return array($consumer, $token); + }/*}}}*/ + + // Internals from here + /** + * version 1 + */ + private function get_version(&$request) {/*{{{*/ + $version = $request->get_parameter("oauth_version"); + if (!$version) { + $version = 1.0; + } + if ($version && $version != $this->version) { + throw new OAuthException("OAuth version '$version' not supported"); + } + return $version; + }/*}}}*/ + + /** + * figure out the signature with some defaults + */ + private function get_signature_method(&$request) {/*{{{*/ + $signature_method = + @$request->get_parameter("oauth_signature_method"); + if (!$signature_method) { + $signature_method = "PLAINTEXT"; + } + if (!in_array($signature_method, + array_keys($this->signature_methods))) { + throw new OAuthException( + "Signature method '$signature_method' not supported try one of the following: " . implode(", ", array_keys($this->signature_methods)) + ); + } + return $this->signature_methods[$signature_method]; + }/*}}}*/ + + /** + * try to find the consumer for the provided request's consumer key + */ + private function get_consumer(&$request) {/*{{{*/ + $consumer_key = @$request->get_parameter("oauth_consumer_key"); + if (!$consumer_key) { + throw new OAuthException("Invalid consumer key"); + } + + $consumer = $this->data_store->lookup_consumer($consumer_key); + if (!$consumer) { + throw new OAuthException("Invalid consumer"); + } + + return $consumer; + }/*}}}*/ + + /** + * try to find the token for the provided request's token key + */ + private function get_token(&$request, $consumer, $token_type="access") {/*{{{*/ + $token_field = @$request->get_parameter('oauth_token'); + $token = $this->data_store->lookup_token( + $consumer, $token_type, $token_field + ); + if (!$token) { + throw new OAuthException("Invalid $token_type token: $token_field"); + } + return $token; + }/*}}}*/ + + /** + * all-in-one function to check the signature on a request + * should guess the signature method appropriately + */ + private function check_signature(&$request, $consumer, $token) {/*{{{*/ + // this should probably be in a different method + $timestamp = @$request->get_parameter('oauth_timestamp'); + $nonce = @$request->get_parameter('oauth_nonce'); + + $this->check_timestamp($timestamp); + $this->check_nonce($consumer, $token, $nonce, $timestamp); + + $signature_method = $this->get_signature_method($request); + + $signature = $request->get_parameter('oauth_signature'); + $valid_sig = $signature_method->check_signature( + $request, + $consumer, + $token, + $signature + ); + + if (!$valid_sig) { + throw new OAuthException("Invalid signature"); + } + }/*}}}*/ + + /** + * check that the timestamp is new enough + */ + private function check_timestamp($timestamp) {/*{{{*/ + // verify that timestamp is recentish + $now = time(); + if ($now - $timestamp > $this->timestamp_threshold) { + throw new OAuthException("Expired timestamp, yours $timestamp, ours $now"); + } + }/*}}}*/ + + /** + * check that the nonce is not repeated + */ + private function check_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/ + // verify that the nonce is uniqueish + $found = $this->data_store->lookup_nonce($consumer, $token, $nonce, $timestamp); + if ($found) { + throw new OAuthException("Nonce already used: $nonce"); + } + }/*}}}*/ + + + +}/*}}}*/ + +class OAuthDataStore {/*{{{*/ + function lookup_consumer($consumer_key) {/*{{{*/ + // implement me + }/*}}}*/ + + function lookup_token($consumer, $token_type, $token) {/*{{{*/ + // implement me + }/*}}}*/ + + function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/ + // implement me + }/*}}}*/ + + function new_request_token($consumer) {/*{{{*/ + // return a new token attached to this consumer + }/*}}}*/ + + function new_access_token($token, $consumer) {/*{{{*/ + // return a new access token attached to this consumer + // for the user associated with this token if the request token + // is authorized + // should also invalidate the request token + }/*}}}*/ + +}/*}}}*/ + + +/* A very naive dbm-based oauth storage + */ +class SimpleOAuthDataStore extends OAuthDataStore {/*{{{*/ + private $dbh; + + function __construct($path = "oauth.gdbm") {/*{{{*/ + $this->dbh = dba_popen($path, 'c', 'gdbm'); + }/*}}}*/ + + function __destruct() {/*{{{*/ + dba_close($this->dbh); + }/*}}}*/ + + function lookup_consumer($consumer_key) {/*{{{*/ + $rv = dba_fetch("consumer_$consumer_key", $this->dbh); + if ($rv === FALSE) { + return NULL; + } + $obj = unserialize($rv); + if (!($obj instanceof OAuthConsumer)) { + return NULL; + } + return $obj; + }/*}}}*/ + + function lookup_token($consumer, $token_type, $token) {/*{{{*/ + $rv = dba_fetch("${token_type}_${token}", $this->dbh); + if ($rv === FALSE) { + return NULL; + } + $obj = unserialize($rv); + if (!($obj instanceof OAuthToken)) { + return NULL; + } + return $obj; + }/*}}}*/ + + function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/ + if (dba_exists("nonce_$nonce", $this->dbh)) { + return TRUE; + } else { + dba_insert("nonce_$nonce", "1", $this->dbh); + return FALSE; + } + }/*}}}*/ + + function new_token($consumer, $type="request") {/*{{{*/ + $key = md5(time()); + $secret = time() + time(); + $token = new OAuthToken($key, md5(md5($secret))); + if (!dba_insert("${type}_$key", serialize($token), $this->dbh)) { + throw new OAuthException("doooom!"); + } + return $token; + }/*}}}*/ + + function new_request_token($consumer) {/*{{{*/ + return $this->new_token($consumer, "request"); + }/*}}}*/ + + function new_access_token($token, $consumer) {/*{{{*/ + + $token = $this->new_token($consumer, 'access'); + dba_delete("request_" . $token->key, $this->dbh); + return $token; + }/*}}}*/ +}/*}}}*/ + +class OAuthUtil {/*{{{*/ + public static function urlencode_rfc3986($input) {/*{{{*/ + if (is_array($input)) { + return array_map(array('OAuthUtil','urlencode_rfc3986'), $input); + } else if (is_scalar($input)) { + return str_replace('+', ' ', + str_replace('%7E', '~', rawurlencode($input))); + } else { + return ''; + } + }/*}}}*/ + + + // This decode function isn't taking into consideration the above + // modifications to the encoding process. However, this method doesn't + // seem to be used anywhere so leaving it as is. + public static function urldecode_rfc3986($string) {/*{{{*/ + return rawurldecode($string); + }/*}}}*/ +}/*}}}*/ + +?> diff --git a/modules/oauth/templates/authorized.php b/modules/oauth/templates/authorized.php new file mode 100644 index 0000000000000000000000000000000000000000..8820a8ac89a1cc995a3279bae67854e97b2ec938 --- /dev/null +++ b/modules/oauth/templates/authorized.php @@ -0,0 +1,15 @@ +<?php + +$this->data['header'] = 'OAuth Authorization'; +$this->includeAtTemplateBase('includes/header.php'); + +?> + + <p style="margin-top: 2em"> + You are now successfully authenticated, and you may click <em>Continue</em> in the application where you initiated authentication. + </p> + + +<?php +$this->includeAtTemplateBase('includes/footer.php'); +?> \ No newline at end of file diff --git a/modules/oauth/www/accessToken.php b/modules/oauth/www/accessToken.php new file mode 100644 index 0000000000000000000000000000000000000000..d4de1f7f1da910d762d3015aa37de591c5d89b2f --- /dev/null +++ b/modules/oauth/www/accessToken.php @@ -0,0 +1,27 @@ +<?php + +require_once(dirname(dirname(__FILE__)) . '/libextinc/OAuth.php'); + +$store = new sspmod_oauth_OAuthStore(); +$server = new sspmod_oauth_OAuthServer($store); + +$hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); +$plaintext_method = new OAuthSignatureMethod_PLAINTEXT(); + +$server->add_signature_method($hmac_method); +$server->add_signature_method($plaintext_method); + +$req = OAuthRequest::from_request(); +$requestToken = $req->get_parameter('oauth_token'); + + +if (!$store->isAuthorized($requestToken)) { + throw new Exception('Your request was not authorized. Request token [' . $requestToken . '] not found.'); +} + +$accessToken = $server->fetch_access_token($req); + +$data = $store->moveAuthorizedData($requestToken, $accessToken->key); + +echo $accessToken; + diff --git a/modules/oauth/www/authorize.php b/modules/oauth/www/authorize.php new file mode 100644 index 0000000000000000000000000000000000000000..fe67606dd94a76530089b5444bcbfbab6e6a75df --- /dev/null +++ b/modules/oauth/www/authorize.php @@ -0,0 +1,52 @@ +<?php + +require_once(dirname(dirname(__FILE__)) . '/libextinc/OAuth.php'); + +if(!array_key_exists('oauth_token', $_REQUEST)) { + throw new Exception('Required URL parameter [oauth_token] is missing.'); +} +$requestToken = $_REQUEST['oauth_token']; + +$store = new sspmod_oauth_OAuthStore(); +$server = new sspmod_oauth_OAuthServer($store); + +$hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); +$plaintext_method = new OAuthSignatureMethod_PLAINTEXT(); + +$server->add_signature_method($hmac_method); +$server->add_signature_method($plaintext_method); + + + + +$config = SimpleSAML_Configuration::getInstance(); +$session = SimpleSAML_Session::getInstance(); + +$as = 'saml2'; +if (!$session->isValid($as)) { + SimpleSAML_Auth_Default::initLogin($as, SimpleSAML_Utilities::selfURL()); +} + +$attributes = $session->getAttributes(); + +#print_r($attributes); + +$store->authorize($requestToken, $attributes); + +$t = new SimpleSAML_XHTML_Template($config, 'oauth:authorized.php'); + +$t->data['header'] = '{status:header_saml20_sp}'; +$t->data['remaining'] = $session->remainingTime(); +$t->data['sessionsize'] = $session->getSize(); +$t->data['attributes'] = $attributes; +$t->data['logouturl'] = SimpleSAML_Utilities::selfURLNoQuery() . '?logout'; +$t->data['icon'] = 'bino.png'; +$t->show(); + + + + +// +// $req = OAuthRequest::from_request(); +// $token = $server->fetch_request_token($req); +// echo $token; diff --git a/modules/oauth/www/getUserInfo.php b/modules/oauth/www/getUserInfo.php new file mode 100644 index 0000000000000000000000000000000000000000..4045288d5c8e731893202692b99424f6eece081b --- /dev/null +++ b/modules/oauth/www/getUserInfo.php @@ -0,0 +1,19 @@ +<?php + +require_once(dirname(dirname(__FILE__)) . '/libextinc/OAuth.php'); + +$store = new sspmod_oauth_OAuthStore(); +$server = new sspmod_oauth_OAuthServer($store); + +$hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); +$plaintext_method = new OAuthSignatureMethod_PLAINTEXT(); + +$server->add_signature_method($hmac_method); +$server->add_signature_method($plaintext_method); + +$req = OAuthRequest::from_request(); +list($consumer, $token) = $server->verify_request($req); + +$data = $store->getAuthorizedData($token->key); + +echo json_encode($data); \ No newline at end of file diff --git a/modules/oauth/www/requestToken.php b/modules/oauth/www/requestToken.php new file mode 100644 index 0000000000000000000000000000000000000000..f77a56def473821dcc9575c8f405bc07bb97940c --- /dev/null +++ b/modules/oauth/www/requestToken.php @@ -0,0 +1,18 @@ +<?php + +require_once(dirname(dirname(__FILE__)) . '/libextinc/OAuth.php'); + +$store = new sspmod_oauth_OAuthStore(); +$server = new sspmod_oauth_OAuthServer($store); + + +$hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); +$plaintext_method = new OAuthSignatureMethod_PLAINTEXT(); + +$server->add_signature_method($hmac_method); +$server->add_signature_method($plaintext_method); + +$req = OAuthRequest::from_request(); +$token = $server->fetch_request_token($req); + +echo $token;