From e39c0f7349073292b10497adbedb572287c52ad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=85kre=20Solberg?= <andreas.solberg@uninett.no> Date: Wed, 2 Feb 2011 11:52:28 +0000 Subject: [PATCH] Large update to the oauth module. Support for RSASHA1 signatures, consent, callbackurl, verifier code and more... Tested against demo script and twitter.. git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@2730 44740490-163a-0410-bde0-09ae8108e29a --- modules/oauth/bin/demo.php | 61 ++++--- .../oauth/config-template/module_oauth.php | 10 ++ modules/oauth/lib/Consumer.php | 63 ++++++- .../oauth/lib/OAuthSignatureMethodRSASHA1.php | 32 ++++ modules/oauth/lib/OAuthStore.php | 155 +++++++++++++++--- modules/oauth/lib/Registry.php | 13 +- modules/oauth/libextinc/OAuth.php | 61 +++++-- modules/oauth/templates/authorized.php | 5 + modules/oauth/templates/consent.php | 19 +++ modules/oauth/www/accessToken.php | 41 +++-- modules/oauth/www/authorize.php | 96 +++++++---- modules/oauth/www/getUserInfo.php | 3 +- modules/oauth/www/registry.edit.php | 21 ++- modules/oauth/www/registry.php | 23 ++- modules/oauth/www/requestToken.php | 35 +++- 15 files changed, 491 insertions(+), 147 deletions(-) create mode 100644 modules/oauth/lib/OAuthSignatureMethodRSASHA1.php create mode 100644 modules/oauth/templates/consent.php diff --git a/modules/oauth/bin/demo.php b/modules/oauth/bin/demo.php index 0c75e0e3c..e4021e76c 100755 --- a/modules/oauth/bin/demo.php +++ b/modules/oauth/bin/demo.php @@ -7,49 +7,56 @@ function readline($prompt = '') { return rtrim( fgets( STDIN ), "\n" ); } -/* This is the base directory of the simpleSAMLphp installation. */ -$baseDir = dirname(dirname(dirname(dirname(__FILE__)))); +try { -/* Add library autoloader. */ -require_once($baseDir . '/lib/_autoload.php'); + /* This is the base directory of the simpleSAMLphp installation. */ + $baseDir = dirname(dirname(dirname(dirname(__FILE__)))); -require_once(dirname(dirname(__FILE__)) . '/libextinc/OAuth.php'); + /* Add library autoloader. */ + require_once($baseDir . '/lib/_autoload.php'); -// Needed in order to make session_start to be called before output is printed. -$session = SimpleSAML_Session::getInstance(); -$baseurl = (isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : 'https://foodle.feide.no/simplesaml'); -$key = (isset($_SERVER['argv'][2]) ? $_SERVER['argv'][2] : 'key'); -$secret = (isset($_SERVER['argv'][3]) ? $_SERVER['argv'][3] : 'secret'); + require_once(dirname(dirname(__FILE__)) . '/libextinc/OAuth.php'); -echo 'Welcome to the OAuth CLI client' . "\n"; -$consumer = new sspmod_oauth_Consumer($key, $secret); + // Needed in order to make session_start to be called before output is printed. + $session = SimpleSAML_Session::getInstance(); -// 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"; + //$baseurl = (isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : 'https://foodle.feide.no/simplesaml'); + $baseurl = (isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : 'http://mars.foodle.local/simplesaml'); + $key = (isset($_SERVER['argv'][2]) ? $_SERVER['argv'][2] : 'key'); + $secret = (isset($_SERVER['argv'][3]) ? $_SERVER['argv'][3] : 'secret'); -// Authorize the request token -$url = $consumer->getAuthorizeRequest($baseurl . '/module.php/oauth/authorize.php', $requestToken, FALSE); + echo 'Welcome to the OAuth CLI client' . "\n"; + $consumer = new sspmod_oauth_Consumer($key, $secret); -echo('Go to this URL to authenticate/authorize the request: ' . $url . "\n"); -system('open ' . $url); + // 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"; -readline('Click enter when you have completed the authorization step using your web browser...'); + // Authorize the request token + $url = $consumer->getAuthorizeRequest($baseurl . '/module.php/oauth/authorize.php', $requestToken, FALSE); -// 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"; + echo('Go to this URL to authenticate/authorize the request: ' . $url . "\n"); + system('open ' . $url); -$userdata = $consumer->getUserInfo($baseurl . '/module.php/oauth/getUserInfo.php', $accessToken); + readline('Click enter when you have completed the authorization step using your web browser...'); + // 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"; -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"; + $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"; + +} catch(Exception $e) { + echo 'Error occured: ' . $e->getMessage() . "\n\n"; +} + diff --git a/modules/oauth/config-template/module_oauth.php b/modules/oauth/config-template/module_oauth.php index 2d725d460..9a346a103 100644 --- a/modules/oauth/config-template/module_oauth.php +++ b/modules/oauth/config-template/module_oauth.php @@ -20,5 +20,15 @@ $config = array ( // Tag to run storage cleanup script using the cron module... 'cron_tag' => 'hourly', + // auth is the idp to use for admin authentication, + // useridattr is the attribute-name that contains the userid as returned from idp + 'auth' => 'default-sp', + 'useridattr', 'user', + + // default OAuth version, defines behaviour of requestToken/accessToken-handling + // supported are '1.0' or '1.0a'; default to '1.0' +// 'defaultversion' => '1.0a', + 'defaultversion' => '1.0', + ); diff --git a/modules/oauth/lib/Consumer.php b/modules/oauth/lib/Consumer.php index 22fd902e9..e76efdfa7 100644 --- a/modules/oauth/lib/Consumer.php +++ b/modules/oauth/lib/Consumer.php @@ -22,11 +22,59 @@ class sspmod_oauth_Consumer { // Used only to load the libextinc library early. public static function dummy() {} + + public static function getOAuthError($hrh) { + foreach($hrh AS $h) { + if (preg_match('|OAuth-Error:\s([^;]*)|i', $h, $matches)) { + return $matches[1]; + } + } + return null; + } + + public static function getContentType($hrh) { + foreach($hrh AS $h) { + if (preg_match('|Content-Type:\s([^;]*)|i', $h, $matches)) { + return $matches[1]; + } + } + return null; + } + + /* + * This static helper function wraps file_get_contents + * and throws an exception with diagnostics messages if it appear + * to be failing on an OAuth endpoint. + * + * If the status code is not 200, an exception is thrown. If the content-type + * of the response if text/plain, the content of the response is included in + * the text of the Exception thrown. + */ + public static function getHTTP($url, $context = '') { + $response = @file_get_contents($url); + + if ($response === FALSE) { + $statuscode = 'unknown'; + if (preg_match('/^HTTP.*\s([0-9]{3})/', $http_response_header[0], $matches)) $statuscode = $matches[1]; + + $error = $context . ' [statuscode: ' . $statuscode . ']: '; + $contenttype = self::getContentType($http_response_header); + $oautherror = self::getOAuthError($http_response_header); + + if (!empty($oautherror)) $error .= $oautherror; + + throw new Exception($error . ':' . $url); + } + // Fall back to return response, if could not reckognize HTTP header. Should not happen. + return $response; + } + 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); - $response_req = SimpleSAML_Utilities::fetch($req_req->to_url()); + $response_req = self::getHTTP($req_req->to_url(), + 'Contacting request_token endpoint on the OAuth Provider'); parse_str($response_req, $responseParsed); @@ -56,7 +104,10 @@ class sspmod_oauth_Consumer { $acc_req = OAuthRequest::from_consumer_and_token($this->consumer, $requestToken, "GET", $url, NULL); $acc_req->sign_request($this->signer, $this->consumer, $requestToken); - $response_acc = SimpleSAML_Utilities::fetch($acc_req->to_url()); + $response_acc = file_get_contents($acc_req->to_url()); + if ($response_acc === FALSE) { + throw new Exception('Error contacting request_token endpoint on the OAuth Provider'); + } SimpleSAML_Logger::debug('oauth: Reponse to get access token: '. $response_acc); @@ -90,7 +141,11 @@ class sspmod_oauth_Consumer { 'header' => 'Content-Type: application/x-www-form-urlencoded', ), ); - $response = SimpleSAML_Utilities::fetch($url, $opts); + $context = stream_context_create($opts); + $response = file_get_contents($url, FALSE, $context); + if ($response === FALSE) { + throw new SimpleSAML_Error_Exception('Failed to push definition file to ' . $pushURL); + } return $response; } @@ -99,7 +154,7 @@ class sspmod_oauth_Consumer { $data_req = OAuthRequest::from_consumer_and_token($this->consumer, $accessToken, "GET", $url, NULL); $data_req->sign_request($this->signer, $this->consumer, $accessToken); - $data = SimpleSAML_Utilities::fetch($data_req->to_url()); + $data = file_get_contents($data_req->to_url()); #print_r($data); $dataDecoded = json_decode($data, TRUE); diff --git a/modules/oauth/lib/OAuthSignatureMethodRSASHA1.php b/modules/oauth/lib/OAuthSignatureMethodRSASHA1.php new file mode 100644 index 000000000..8c2e0449e --- /dev/null +++ b/modules/oauth/lib/OAuthSignatureMethodRSASHA1.php @@ -0,0 +1,32 @@ +<?php + +require_once(dirname(dirname(__FILE__)) . '/libextinc/OAuth.php'); + + +class sspmod_oauth_OAuthSignatureMethodRSASHA1 extends OAuthSignatureMethod_RSA_SHA1 { + protected $_store; + + public function __construct() { + $this->_store = new sspmod_core_Storage_SQLPermanentStorage('oauth'); + } + + /** + * Returns the secret that was registered with a Consumer<br/> + * In case of RSA_SHA1, the consumer secret is initialized with the certificate containing the public key + * @param $request OAuthRequest instance of the request to be handled; must contain oauth_consumer_key parameter + * @return string value containing the public key that was registered with the consumer identified by + * consumer_key from the request + */ + protected function fetch_public_cert(&$request) { + $consumer_key = @$request->get_parameter('oauth_consumer_key'); + + $oConsumer = $this->_OAuthStore->lookup_consumer($consumer_key); + + if (! $oConsumer) { + return NULL; + } + + return $oConsumer->secret; + } +} +?> \ No newline at end of file diff --git a/modules/oauth/lib/OAuthStore.php b/modules/oauth/lib/OAuthStore.php index 2471e1014..c639a80ee 100644 --- a/modules/oauth/lib/OAuthStore.php +++ b/modules/oauth/lib/OAuthStore.php @@ -1,11 +1,14 @@ <?php - require_once(dirname(dirname(__FILE__)) . '/libextinc/OAuth.php'); /** * OAuth Store + * + * Updated version, works with consumer-callbacks, certificates and 1.0-RevA protocol + * behaviour (requestToken-callbacks and verifiers) * * @author Andreas Ă…kre Solberg, <andreas.solberg@uninett.no>, UNINETT AS. + * @author Mark Dobrinic, <mdobrinic@cozmanova.com>, Cozmanova bv * @package simpleSAMLphp * @version $Id$ */ @@ -13,41 +16,112 @@ class sspmod_oauth_OAuthStore extends OAuthDataStore { private $store; private $config; + private $defaultversion; + protected $_store_tables = array( + 'consumers' => 'consumer = array with consumer attributes', + 'nonce' => 'nonce+consumer_key = -boolean-', + 'requesttorequest' => 'requestToken.key = array(version,callback,consumerKey,)', + 'authorized' => 'requestToken.key, verifier = array(authenticated-user-attributes)', + 'access' => 'accessToken.key+consumerKey = accestoken', + 'request' => 'requestToken.key+consumerKey = requesttoken', + ); + function __construct() { - $this->store = new sspmod_core_Storage_SQLPermanentStorage('oauth'); + $this->store = new sspmod_core_Storage_SQLPermanentStorage('oauth'); $this->config = SimpleSAML_Configuration::getOptionalConfig('module_oauth.php'); + + $this->defaultversion = $this->config->getValue('defaultversion', '1.0'); } - public function authorize($requestToken, $data) { - # set($type, $key1, $key2, $value, $duration = NULL) { - $this->store->set('authorized', $requestToken, '', $data, $this->config->getValue('requestTokenDuration', 60*30) ); + + /** + * Attach the data to the token, and establish the Callback URL (and verifier for 1.0a protocol handling) + * @param $requestTokenKey RequestToken that was authorized + * @param $data Data that is authorized and to be attached to the requestToken + * @return array(string:url, string:verifier) ; empty verifier for 1.0-response + */ + public function authorize($requestTokenKey, $data) { + $url = null; + $verifier = ''; + $version = $this->defaultversion; + + // See whether to remember values from the original requestToken request: + $request_attributes = $this->store->get('requesttorequest', $requestTokenKey, ''); // must be there .. + if ($request_attributes['value']) { + // establish version to work with + $v = $request_attributes['value']['version']; + if ($v) $version = $v; + + // establish callback to use + if ($request_attributes['value']['callback']) { + $url = $request_attributes['value']['callback']; + } + } + + + // Is there a callback registered? This is leading, even over a supplied oauth_callback-parameter + $oConsumer = $this->lookup_consumer($request_attributes['value']['consumerKey']); + + if ($oConsumer && ($oConsumer->callback_url)) $url = $oConsumer->callback_url; + + if ($version == '1.0a') { + $verifier = SimpleSAML_Utilities::generateID(); + $url = SimpleSAML_Utilities::addURLparameter($url, array("oauth_verifier"=>$verifier)); + } + + $this->store->set('authorized', $requestTokenKey, $verifier, $data, $this->config->getValue('requestTokenDuration', 60*30) ); + + return array($url, $verifier); } - public function isAuthorized($requestToken) { + /** + * Perform lookup whether a given token exists in the list of authorized tokens; if a verifier is + * passed as well, the verifier *must* match the verifier that was registered with the token<br/> + * Note that an accessToken should never be stored with a verifier + * @param $requestToken + * @param $verifier + * @return unknown_type + */ + public function isAuthorized($requestToken, $verifier='') { SimpleSAML_Logger::info('OAuth isAuthorized(' . $requestToken . ')'); - return $this->store->exists('authorized', $requestToken, ''); + return $this->store->exists('authorized', $requestToken, $verifier); } - public function getAuthorizedData($token) { + public function getAuthorizedData($token, $verifier = '') { SimpleSAML_Logger::info('OAuth getAuthorizedData(' . $token . ')'); - $data = $this->store->get('authorized', $token, ''); + $data = $this->store->get('authorized', $token, $verifier); return $data['value']; } - public function moveAuthorizedData($requestToken, $accessToken) { - SimpleSAML_Logger::info('OAuth moveAuthorizedData(' . $requestToken . ', ' . $accessToken . ')'); - $this->authorize($accessToken, $this->getAuthorizedData($requestToken)); - $this->store->remove('authorized', $requestToken, ''); + public function moveAuthorizedData($requestToken, $verifier, $accessTokenKey) { + SimpleSAML_Logger::info('OAuth moveAuthorizedData(' . $requestToken . ', ' . $accessTokenKey . ')'); + + // Retrieve authorizedData from authorized.requestToken (with provider verifier) + $authorizedData = $this->getAuthorizedData($requestToken, $verifier); + + // Remove the requesttoken+verifier from authorized store + $this->store->remove('authorized', $requestToken, $verifier); + + // Add accesstoken with authorizedData to authorized store (with empty verifier) + // accessTokenKey+consumer => accessToken is already registered in 'access'-table + $this->store->set('authorized', $accessTokenKey, '', $authorizedData, $this->config->getValue('accessTokenDuration', 60*60*24)); } public function lookup_consumer($consumer_key) { - SimpleSAML_Logger::info('OAuth lookup_consumer(' . $consumer_key . ')'); if (! $this->store->exists('consumers', $consumer_key, '')) return NULL; $consumer = $this->store->get('consumers', $consumer_key, ''); + + $callback = NULL; + if ($consumer['value']['callback_url']) $callback = $consumer['value']['callback_url']; + // SimpleSAML_Logger::info('OAuth consumer dump(' . var_export($consumer, TRUE) . ')'); - return new OAuthConsumer($consumer['value']['key'], $consumer['value']['secret'], NULL); + if ($consumer['value']['RSAcertificate']) { + return new OAuthConsumer($consumer['value']['key'], $consumer['value']['RSAcertificate'], $callback); + } else { + return new OAuthConsumer($consumer['value']['key'], $consumer['value']['secret'], $callback); + } } function lookup_token($consumer, $tokenType = 'default', $token) { @@ -64,19 +138,56 @@ class sspmod_oauth_OAuthStore extends OAuthDataStore { return FALSE; } - function new_request_token($consumer) { + function new_request_token($consumer, $callback = null, $version = null) { SimpleSAML_Logger::info('OAuth new_request_token(' . $consumer . ')'); + + $lifetime = $this->config->getValue('requestTokenDuration', 60*30); + $token = new OAuthToken(SimpleSAML_Utilities::generateID(), SimpleSAML_Utilities::generateID()); - $this->store->set('request', $token->key, $consumer->key, $token, $this->config->getValue('requestTokenDuration', 60*30) ); + $token->callback = $callback; // OAuth1.0-RevA + $this->store->set('request', $token->key, $consumer->key, $token, $lifetime); + + // also store in requestToken->key => array('callback'=>CallbackURL, 'version'=>oauth_version + $request_attributes = array( + 'callback' => $callback, + 'version' => ($version?$version:$this->defaultversion), + 'consumerKey' => $consumer->key, + ); + $this->store->set('requesttorequest', $token->key, '', $request_attributes, $lifetime); + + // also store in requestToken->key => Consumer->key (enables consumer-lookup during reqToken-authorization stage) + $this->store->set('requesttoconsumer', $token->key, '', $consumer->key, $lifetime); + return $token; } - function new_access_token($requestToken, $consumer) { + function new_access_token($requestToken, $consumer, $verifier = null) { SimpleSAML_Logger::info('OAuth new_access_token(' . $requestToken . ',' . $consumer . ')'); - $token = new OAuthToken(SimpleSAML_Utilities::generateID(), SimpleSAML_Utilities::generateID()); - // SimpleSAML_Logger::info('OAuth new_access_token(' . $requestToken . ',' . $consumer . ',' . $token . ')'); - $this->store->set('access', $token->key, $consumer->key, $token, $this->config->getValue('accessTokenDuration', 60*60*24) ); - return $token; + $accestoken = new OAuthToken(SimpleSAML_Utilities::generateID(), SimpleSAML_Utilities::generateID()); + // SimpleSAML_Logger::info('OAuth new_access_token(' . $requestToken . ',' . $consumer . ',' . $accestoken . ')'); + $this->store->set('access', $accestoken->key, $consumer->key, $accestoken, $this->config->getValue('accessTokenDuration', 60*60*24) ); + return $accestoken; } + + /** + * Return OAuthConsumer-instance that a given requestToken was issued to + * @param $requestTokenKey + * @return unknown_type + */ + public function lookup_consumer_by_requestToken($requestTokenKey) { + SimpleSAML_Logger::info('OAuth lookup_consumer_by_requestToken(' . $requestTokenKey . ')'); + if (! $this->store->exists('requesttorequest', $requestTokenKey, '')) return NULL; + + $request = $this->store->get('requesttorequest', $requestTokenKey, ''); + $consumerKey = $request['value']['consumerKey']; + if (! $consumerKey) { + return NULL; + } + + $consumer = $this->store->get('consumers', $consumerKey['value'], ''); + return $consumer['value']; + } + + } diff --git a/modules/oauth/lib/Registry.php b/modules/oauth/lib/Registry.php index 06abcd426..363819f7f 100644 --- a/modules/oauth/lib/Registry.php +++ b/modules/oauth/lib/Registry.php @@ -23,7 +23,9 @@ class sspmod_oauth_Registry { $this->getStandardField($request, $entry, 'description'); $this->getStandardField($request, $entry, 'key'); $this->getStandardField($request, $entry, 'secret'); - + $this->getStandardField($request, $entry, 'RSAcertificate'); + $this->getStandardField($request, $entry, 'callback_url'); + if ($override) { foreach($override AS $key => $value) { $entry[$key] = $value; @@ -43,6 +45,7 @@ class sspmod_oauth_Registry { public function checkForm($request) { $this->requireStandardField($request, 'name'); $this->requireStandardField($request, 'description'); + $this->requireStandardField($request, 'key'); } @@ -121,10 +124,12 @@ class sspmod_oauth_Registry { $this->standardField($metadata, 'name', 'Name of client') . $this->standardField($metadata, 'description', 'Description of client', TRUE) . $this->readonlyField($metadata, 'owner', 'Owner') . - $this->readonlyField($metadata, 'key', 'Consumer Key') . - $this->readonlyField($metadata, 'secret', 'Consumer Secret') . + $this->standardField($metadata, 'key', 'Consumer Key') . + $this->readonlyField($metadata, 'secret', 'Consumer Secret<br/>(Used for HMAC_SHA1 signatures)') . + $this->standardField($metadata, 'RSAcertificate', 'RSA certificate (PEM)<br/>(Used for RSA_SHA1 signatures)', TRUE) . + $this->standardField($metadata, 'callback_url', 'Static/enforcing callback-url') . - $this->hiddenField('field_key', $metadata['key']) . +// $this->hiddenField('field_key', $metadata['key']) . $this->hiddenField('field_secret', $metadata['secret']) . '</table></div>' . diff --git a/modules/oauth/libextinc/OAuth.php b/modules/oauth/libextinc/OAuth.php index b967da6ff..944dfe757 100644 --- a/modules/oauth/libextinc/OAuth.php +++ b/modules/oauth/libextinc/OAuth.php @@ -171,7 +171,7 @@ class OAuthRequest {/*{{{*/ private $http_url; // for debug purposes public $base_string; - public static $version = '1.0'; + public static $default_version = '1.0'; function __construct($http_method, $http_url, $parameters=NULL) {/*{{{*/ @$parameters or $parameters = array(); @@ -185,8 +185,11 @@ class OAuthRequest {/*{{{*/ * 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) {/*{{{*/ + // pre-process $_SERVER['HTTP_HOST'] to ensure no port is included in HTTP_HOST + $http_host = explode( ':', $_SERVER['HTTP_HOST']); + $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_url or $http_url = $scheme . '://' . $http_host[0] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI']; @$http_method or $http_method = $_SERVER['REQUEST_METHOD']; $request_headers = OAuthRequest::get_headers(); @@ -218,10 +221,11 @@ class OAuthRequest {/*{{{*/ /** * pretty much a helper function to set up the request + * set new values in $parameters to overrule defaults */ 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, + $defaults = array("oauth_version" => OAuthRequest::$default_version, "oauth_nonce" => OAuthRequest::generate_nonce(), "oauth_timestamp" => OAuthRequest::generate_timestamp(), "oauth_consumer_key" => $consumer->key); @@ -244,6 +248,12 @@ class OAuthRequest {/*{{{*/ public function get_parameters() {/*{{{*/ return $this->parameters; }/*}}}*/ + + public function get_version() {/*{{{*/ + $v = $this->get_parameter("oauth_version"); + if ($v != NULL) return $v; + return self::$default_version; + }/*}}}*. /** * Returns the normalized parameters of the request @@ -330,7 +340,9 @@ class OAuthRequest {/*{{{*/ $port = @$parts['port']; $scheme = $parts['scheme']; - $host = $parts['host']; + // ensure no port definition in hosts + $hostname = explode(':', $parts['host']); + $host = $hostname[0]; $path = @$parts['path']; $port or $port = ($scheme == 'https') ? '443' : '80'; @@ -470,7 +482,8 @@ class OAuthRequest {/*{{{*/ class OAuthServer {/*{{{*/ protected $timestamp_threshold = 300; // in seconds, five minutes - protected $version = 1.0; // hi blaine + // protected $version = 1.0; // hi blaine + protected $versions = array('1.0', '1.0a'); // dopey says hi protected $signature_methods = array(); protected $data_store; @@ -491,7 +504,7 @@ class OAuthServer {/*{{{*/ * returns the request token on success */ public function fetch_request_token(&$request) {/*{{{*/ - $this->get_version($request); + $v = $this->get_version($request); $consumer = $this->get_consumer($request); @@ -500,7 +513,15 @@ class OAuthServer {/*{{{*/ $this->check_signature($request, $consumer, $token); - $new_token = $this->data_store->new_request_token($consumer); + // Rev A change + $callback = $request->get_parameter('oauth_callback'); // null if not passed + + if ($consumer->callback_url) $callback = $consumer->callback_url; // overrule if present in consumer definition + + if ($v == '1.0a') { + assert('$callback != NULL /* callback must be provided for 1.0a requests */'); + } + $new_token = $this->data_store->new_request_token($consumer, $callback, $v); // dopey: add version to the request return $new_token; }/*}}}*/ @@ -510,17 +531,20 @@ class OAuthServer {/*{{{*/ * returns the access token on success */ public function fetch_access_token(&$request) {/*{{{*/ - $this->get_version($request); + $v = $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); + // Rev A change + $verifier = $request->get_parameter('oauth_verifier'); if ($verifier == null) $verifier = ''; + $new_token = $this->data_store->new_access_token($token, $consumer, $verifier); + + // $new_token = $this->data_store->new_access_token($token, $consumer); return $new_token; }/*}}}*/ @@ -545,7 +569,7 @@ class OAuthServer {/*{{{*/ if (!$version) { $version = 1.0; } - if ($version && $version != $this->version) { + if ($version && !in_array($version, $this->versions)) { throw new OAuthException("OAuth version '$version' not supported"); } return $version; @@ -590,17 +614,22 @@ class OAuthServer {/*{{{*/ * 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_key = @$request->get_parameter('oauth_token'); // SimpleSAML_Logger::info('request: ' . var_export($request, TRUE)); // SimpleSAML_Logger::info('token_type: ' . var_export($token_type, TRUE)); // SimpleSAML_Logger::info('token_field: ' . var_export($token_field, TRUE)); + // + // $bt = SimpleSAML_Utilities::buildBacktrace(new Exception()); + // foreach ($bt AS $t) { + // SimpleSAML_Logger::info(' ' . $t); + // } $token = $this->data_store->lookup_token( - $consumer, $token_type, $token_field + $consumer, $token_type, $token_key ); if (!$token) { - throw new OAuthException("Invalid $token_type token: $token_field"); + throw new OAuthException("Invalid $token_type token: $token_key"); } return $token; }/*}}}*/ @@ -671,11 +700,11 @@ class OAuthDataStore {/*{{{*/ // implement me }/*}}}*/ - function new_request_token($consumer) {/*{{{*/ + function new_request_token($consumer, $callback = null) {/*{{{*/ // return a new token attached to this consumer }/*}}}*/ - function new_access_token($token, $consumer) {/*{{{*/ + function new_access_token($token, $consumer, $verifier = null) {/*{{{*/ // return a new access token attached to this consumer // for the user associated with this token if the request token // is authorized diff --git a/modules/oauth/templates/authorized.php b/modules/oauth/templates/authorized.php index 8820a8ac8..4eb99a9ae 100644 --- a/modules/oauth/templates/authorized.php +++ b/modules/oauth/templates/authorized.php @@ -8,6 +8,11 @@ $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 if ($this->data['oauth_verifier']) {?> + <p> + When asked, the verifier code to finish the procedure, is: <b><?php echo $this->data['oauth_verifier'];?></b>. + </p> +<?php } ?> <?php diff --git a/modules/oauth/templates/consent.php b/modules/oauth/templates/consent.php new file mode 100644 index 000000000..f02333151 --- /dev/null +++ b/modules/oauth/templates/consent.php @@ -0,0 +1,19 @@ +<?php + +$this->data['header'] = 'OAuth Authorization'; +$this->includeAtTemplateBase('includes/header.php'); + +?> + + <p style="margin-top: 2em"> + Do you agree to let the application at <b><?php echo $this->data['consumer']['name']?></b> use Foodle on your behalf? + </p> + <p> + <a href="<?php echo $this->data['urlAgree']; ?>">Yes I agree</a> | + <a href="javascript:alert('Please close this browser.');">No, cancel the request.</a> + </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 index 598b91617..31c81325e 100644 --- a/modules/oauth/www/accessToken.php +++ b/modules/oauth/www/accessToken.php @@ -2,31 +2,40 @@ 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(); +try { -$server->add_signature_method($hmac_method); -$server->add_signature_method($plaintext_method); + $store = new sspmod_oauth_OAuthStore(); + $server = new sspmod_oauth_OAuthServer($store); -$req = OAuthRequest::from_request(); + $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); + $plaintext_method = new OAuthSignatureMethod_PLAINTEXT(); + $rsa_method = new sspmod_oauth_OAuthSignatureMethodRSASHA1(); + $server->add_signature_method($hmac_method); + $server->add_signature_method($plaintext_method); + $server->add_signature_method($rsa_method); -$requestToken = $req->get_parameter('oauth_token'); + $req = OAuthRequest::from_request(); + $requestToken = $req->get_parameter('oauth_token'); + $verifier = $req->get_parameter("oauth_verifier"); if ($verifier == null) $verifier = ''; -if (!$store->isAuthorized($requestToken)) { - throw new Exception('Your request was not authorized. Request token [' . $requestToken . '] not found.'); -} - + if (!$store->isAuthorized($requestToken, $verifier)) { + throw new Exception('Your request was not authorized. Request token [' . $requestToken . '] not found.'); + } + $accessToken = $server->fetch_access_token($req); + $data = $store->moveAuthorizedData($requestToken, $verifier, $accessToken->key); -$accessToken = $server->fetch_access_token($req); + echo $accessToken; -$data = $store->moveAuthorizedData($requestToken, $accessToken->key); - -echo $accessToken; +} catch (Exception $e) { + + header('Content-type: text/plain; utf-8', TRUE, 500); + header('OAuth-Error: ' . $e->getMessage()); + print_r($e); + +} diff --git a/modules/oauth/www/authorize.php b/modules/oauth/www/authorize.php index 3b700f61f..a2329d1ba 100644 --- a/modules/oauth/www/authorize.php +++ b/modules/oauth/www/authorize.php @@ -2,54 +2,88 @@ require_once(dirname(dirname(__FILE__)) . '/libextinc/OAuth.php'); -$oauthconfig = SimpleSAML_Configuration::getOptionalConfig('module_oauth.php'); - -if(!array_key_exists('oauth_token', $_REQUEST)) { - throw new Exception('Required URL parameter [oauth_token] is missing.'); -} -$requestToken = $_REQUEST['oauth_token']; +try { + -$store = new sspmod_oauth_OAuthStore(); -$server = new sspmod_oauth_OAuthServer($store); -$hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); -$plaintext_method = new OAuthSignatureMethod_PLAINTEXT(); + $oauthconfig = SimpleSAML_Configuration::getOptionalConfig('module_oauth.php'); -$server->add_signature_method($hmac_method); -$server->add_signature_method($plaintext_method); + 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(); + $rsa_method = new sspmod_oauth_OAuthSignatureMethodRSASHA1(); + $server->add_signature_method($hmac_method); + $server->add_signature_method($plaintext_method); + $server->add_signature_method($rsa_method); -$config = SimpleSAML_Configuration::getInstance(); -$session = SimpleSAML_Session::getInstance(); -$as = $oauthconfig->getString('auth'); -$as = new SimpleSAML_Auth_Simple($as); -$as->requireAuth(); -$attributes = $as->getAttributes(); + $config = SimpleSAML_Configuration::getInstance(); + $session = SimpleSAML_Session::getInstance(); -#print_r($attributes); + $as = $oauthconfig->getString('auth'); + if (!$session->isValid($as)) { + SimpleSAML_Auth_Default::initLogin($as, SimpleSAML_Utilities::selfURL()); + } -$store->authorize($requestToken, $attributes); -if (isset($_REQUEST['oauth_callback'])) { + if (!empty($_REQUEST['consent'])) { + $consumer = $store->lookup_consumer_by_requestToken($requestToken); + + $t = new SimpleSAML_XHTML_Template($config, 'oauth:consent.php'); + $t->data['header'] = '{status:header_saml20_sp}'; + $t->data['consumer'] = $consumer; // array containint {name, description, key, secret, owner} keys + $t->data['urlAgree'] = SimpleSAML_Utilities::addURLparameter( SimpleSAML_Utilities::selfURL(), array("consent" => "yes") ); + $t->data['logouturl'] = SimpleSAML_Utilities::selfURLNoQuery() . '?logout'; - SimpleSAML_Utilities::redirect($_REQUEST['oauth_callback']); + $t->show(); -} else { + exit(); // and be done. + } + $attributes = $session->getAttributes(); - $t = new SimpleSAML_XHTML_Template($config, 'oauth:authorized.php'); + // Assume user consent at this point and proceed with authorizing the token + list($url, $verifier) = $store->authorize($requestToken, $attributes); - $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->show(); -} + if ($url) { + // If authorize() returns a URL, take user there (oauth1.0a) + SimpleSAML_Utilities::redirect($url); + } + else if (isset($_REQUEST['oauth_callback'])) { + // If callback was provided in the request (oauth1.0) + SimpleSAML_Utilities::redirect($_REQUEST['oauth_callback']); + + } else { + // No callback provided, display standard template + + $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['oauth_verifier'] = $verifier; + $t->show(); + } + +} catch (Exception $e) { + + header('Content-type: text/plain; utf-8', TRUE, 500); + header('OAuth-Error: ' . $e->getMessage()); + + print_r($e); + +} // diff --git a/modules/oauth/www/getUserInfo.php b/modules/oauth/www/getUserInfo.php index dde46e1e8..516b06544 100644 --- a/modules/oauth/www/getUserInfo.php +++ b/modules/oauth/www/getUserInfo.php @@ -22,4 +22,5 @@ list($consumer, $token) = $server->verify_request($req); $data = $store->getAuthorizedData($token->key); -echo json_encode($data); \ No newline at end of file +echo json_encode($data); + diff --git a/modules/oauth/www/registry.edit.php b/modules/oauth/www/registry.edit.php index ed6a835aa..f4a5ea306 100644 --- a/modules/oauth/www/registry.edit.php +++ b/modules/oauth/www/registry.edit.php @@ -2,20 +2,25 @@ /* Load simpleSAMLphp, configuration and metadata */ $config = SimpleSAML_Configuration::getInstance(); +$session = SimpleSAML_Session::getInstance(); $oauthconfig = SimpleSAML_Configuration::getOptionalConfig('module_oauth.php'); $store = new sspmod_core_Storage_SQLPermanentStorage('oauth'); -$authsource = $oauthconfig->getValue('auth', 'admin'); +//$authsource = $oauthconfig->getValue('auth', 'admin'); +$authsource = "admin"; // force admin to authenticate as registry maintainer $useridattr = $oauthconfig->getValue('useridattr', 'user'); +//$useridattr = $oauthconfig->getValue('useridattr', 'uid'); -$as = new SimpleSAML_Auth_Simple($authsource); -$as->requireAuth(); -$attributes = $as->getAttributes(); -// Check if userid exists -if (!isset($attributes[$useridattr])) - throw new Exception('User ID is missing'); -$userid = $attributes[$useridattr][0]; +if ($session->isValid($authsource)) { + $attributes = $session->getAttributes(); + // Check if userid exists + if (!isset($attributes[$useridattr])) + throw new Exception('User ID is missing'); + $userid = $attributes[$useridattr][0]; +} else { + SimpleSAML_Auth_Default::initLogin($authsource, SimpleSAML_Utilities::selfURL()); +} function requireOwnership($entry, $userid) { if (!isset($entry['owner'])) diff --git a/modules/oauth/www/registry.php b/modules/oauth/www/registry.php index 31c32166a..d562bb06b 100644 --- a/modules/oauth/www/registry.php +++ b/modules/oauth/www/registry.php @@ -2,20 +2,25 @@ /* Load simpleSAMLphp, configuration and metadata */ $config = SimpleSAML_Configuration::getInstance(); +$session = SimpleSAML_Session::getInstance(); $oauthconfig = SimpleSAML_Configuration::getOptionalConfig('module_oauth.php'); $store = new sspmod_core_Storage_SQLPermanentStorage('oauth'); -$authsource = $oauthconfig->getValue('auth', 'admin'); +//$authsource = $oauthconfig->getValue('auth', 'admin'); +$authsource = "admin"; // force admin to authenticate as registry maintainer $useridattr = $oauthconfig->getValue('useridattr', 'user'); - -$as = new SimpleSAML_Auth_Simple($authsource); -$as->requireAuth(); -$attributes = $as->getAttributes(); -// Check if userid exists -if (!isset($attributes[$useridattr])) - throw new Exception('User ID is missing'); -$userid = $attributes[$useridattr][0]; +//$useridattr = $oauthconfig->getValue('useridattr', 'uid'); + +if ($session->isValid($authsource)) { + $attributes = $session->getAttributes(); + // Check if userid exists + if (!isset($attributes[$useridattr])) + throw new Exception('User ID is missing'); + $userid = $attributes[$useridattr][0]; +} else { + SimpleSAML_Auth_Default::initLogin($authsource, SimpleSAML_Utilities::selfURL()); +} function requireOwnership($entry, $userid) { if (!isset($entry['owner'])) diff --git a/modules/oauth/www/requestToken.php b/modules/oauth/www/requestToken.php index f77a56def..054fe51f7 100644 --- a/modules/oauth/www/requestToken.php +++ b/modules/oauth/www/requestToken.php @@ -2,17 +2,34 @@ require_once(dirname(dirname(__FILE__)) . '/libextinc/OAuth.php'); -$store = new sspmod_oauth_OAuthStore(); -$server = new sspmod_oauth_OAuthServer($store); +try { + + $store = new sspmod_oauth_OAuthStore(); + $server = new sspmod_oauth_OAuthServer($store); -$hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); -$plaintext_method = new OAuthSignatureMethod_PLAINTEXT(); + $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); + $plaintext_method = new OAuthSignatureMethod_PLAINTEXT(); + $rsa_method = new sspmod_oauth_OAuthSignatureMethodRSASHA1(); -$server->add_signature_method($hmac_method); -$server->add_signature_method($plaintext_method); + $server->add_signature_method($hmac_method); + $server->add_signature_method($plaintext_method); + $server->add_signature_method($rsa_method); -$req = OAuthRequest::from_request(); -$token = $server->fetch_request_token($req); + $req = OAuthRequest::from_request(); + $token = $server->fetch_request_token($req, null, $req->get_version()); -echo $token; + // OAuth1.0-revA adds oauth_callback_confirmed to token + echo $token; + if ($req->get_version() == '1.0a') { + echo "&oauth_callback_confirmed=true"; + } + +} catch (Exception $e) { + + header('Content-type: text/plain; utf-8', TRUE, 500); + header('OAuth-Error: ' . $e->getMessage()); + + print_r($e); + +} -- GitLab