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