Revision: 3091
Author: olavmrk
Date: Mon May 14 04:34:55 2012
Log: aselect: New aselect-module.
New A-Select module with an updated A-Select authentication source. The
new module gives us support for request signing, but we lose support
for A-Select Cross.
Thanks to Wessel Dankers for creating this module, and to Thijs
Kinkhorst for providing the patch.
http://code.google.com/p/simplesamlphp/source/detail?r=3091
Added:
/trunk/modules/aselect/www/credentials.php
Modified:
/trunk/modules/aselect/docs/aselect.txt
/trunk/modules/aselect/lib/Auth/Source/aselect.php
/trunk/modules/aselect/www/linkback.php
=======================================
--- /dev/null
+++ /trunk/modules/aselect/www/credentials.php Mon May 14 04:34:55 2012
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * Check the credentials that the user got from the A-Select server.
+ * This function is called after the user returns from the A-Select server.
+ *
+ * @author Wessel Dankers, Tilburg University
+ */
+function check_credentials() {
+ $state =
SimpleSAML_Auth_State::loadState($_REQUEST['ssp_state'], 'aselect:login');
+
+ if(!array_key_exists('a-select-server', $_REQUEST))
+ SimpleSAML_Auth_State::throwException($state, new
SimpleSAML_Error_Exception("Missing a-select-server parameter"));
+ $server_id = $_REQUEST['a-select-server'];
+
+ if(!array_key_exists('aselect_credentials', $_REQUEST))
+ SimpleSAML_Auth_State::throwException($state, new
SimpleSAML_Error_Exception("Missing aselect_credentials parameter"));
+ $credentials = $_REQUEST['aselect_credentials'];
+
+ if(!array_key_exists('rid', $_REQUEST))
+ SimpleSAML_Auth_State::throwException($state, new
SimpleSAML_Error_Exception("Missing rid parameter"));
+ $rid = $_REQUEST['rid'];
+
+ try {
+ if(!array_key_exists('aselect::authid', $state))
+ throw new SimpleSAML_Error_Exception("ASelect authentication source
missing in state");
+ $authid = $state['aselect::authid'];
+ $aselect = SimpleSAML_Auth_Source::getById($authid);
+ if(is_null($aselect))
+ throw new SimpleSAML_Error_Exception("Could not find authentication
source with id $authid");
+ $creds = $aselect->verify_credentials($server_id, $credentials,
$rid);
+
+ if(array_key_exists('attributes', $creds)) {
+ $state['Attributes'] = $creds['attributes'];
+ } else {
+ $res = $creds['res'];
+ $state['Attributes'] = array('uid' =>
array($res['uid']), 'organization' => array($res['organization']));
+ }
+ } catch(Exception $e) {
+ SimpleSAML_Auth_State::throwException($state, $e);
+ }
+
+ SimpleSAML_Auth_Source::completeAuth($state);
+ SimpleSAML_Auth_State::throwException($state, new
SimpleSAML_Error_Exception("Internal error in A-Select component"));
+}
+
+check_credentials();
=======================================
--- /trunk/modules/aselect/docs/aselect.txt Mon Jul 12 05:44:08 2010
+++ /trunk/modules/aselect/docs/aselect.txt Mon May 14 04:34:55 2012
@@ -1,64 +1,44 @@
-Using the A-Select authentication source with simpleSAMLphp
-===========================================================
-
-This authentication source for A-Select is based on the a-select
-handler by Hans Zandbelt. The original source combines the possibility
-to use an A-Select server as an authentication source and the possibility
-to use simpleSAMLphp as an A-Select server. This module only acts as a
-authentication source. Signing is not (yet) supported.
-
-The structure applied follows the structure of the CAS authentication
source.
-
-
-Setting up the A-Select authentication module
-----------------------------------------------
-
-The first thing you need to do is to enable the aselect module:
-
- touch modules/aselect/enable
-
-The A-Select authentication module has two modes of operation.
-
-1. The module can act to the A-Select server as an application.
-
-Configuration in A-Select:
-
- <application id="app1" level="30">
- <attribute_policy>policyA</attribute_policy>
- <forced_authenticate>false</forced_authenticate>
- </application>
-
-Configuration in authsources.php
-
- 'example-aselect' => array(
+A-Select module for simpleSAMLphp
+---------------------------------
+
+This module allows one to use an A-Select server as authentication
+source for simpleSAMLphp.
+
+The module supports the A-Select protocol, including signing of
+requests. Not supported is A-Select Cross.
+
+Usage:
+
+Enable the module if not already enabled:
+$ touch modules/aselect/enabled
+
+In config/authsources.php, configure your A-Selectserver as an
+authentication source. The following is an example for a source
+named 'aselect':
+
+ 'aselect' => array(
'aselect:aselect',
- 'serverurl'
=> 'http://a-select.dev.han.nl:8080/aselectserver/server',
- 'serverid' => 'hanaselect',
- 'type' => 'app', # type =
app/cross
- 'app_id' => 'app1', # only if type
= app
+ 'app_id' => 'simplesamlphp',
+ 'server_id' => 'sso.example.com',
+ 'server_url' => 'https://test.sso.example.com/server',
+ 'private_key' => 'file:///etc/ssl/private/aselect.key'
),
-2. The module can act to the A-Select server as cross A-Select.
-
-Configuration in A-Select:
-
- <cross_aselect>
- <local_servers require_signing="false">
- <organization id="simpleSAMLphp" server="sso.testorg.com"
attribute_policy="policyA">
- </organization>
- </local_servers>
- </cross_aselect>
-
-
-Configuration in authsources.php
-
- 'example-aselect' => array(
- 'aselect:aselect',
- 'serverurl'
=> 'http://a-select.dev.han.nl:8080/aselectserver/server',
- 'serverid' => 'hanaselect',
- 'type' => 'cross', # type =
app/cross
- 'local_organization' => 'simpleSAMLphp', # only if type
= cross
- 'required_level' => 10, # only if type
= cross, defaults to 10
- ),
-
-
+The parameters:
+- app_id: the application I for simpleSAMLphp as configured in
+ your A-Select server;
+- server_id: the A-Select server ID as configured in your
+ A-Select server;
+- server_url: the URL for your A-Selectserver, usually ends in
+ '/server/.
+- private_key: the key you want to use for signing requests.
+ If you're really sure you do not want request signing, you
+ can set this option to a null value.
+Options 'serverurl' and 'serverid' (without underscore) are
+supported for backwards compatibility.
+
+Author: Wessel Dankers <w...@uvt.nl>
+
+Copyright: © 2011,2012 Tilburg University
(http://www.tilburguniversity.edu)
+
+License: GPL version 3 or any later version.
=======================================
--- /trunk/modules/aselect/lib/Auth/Source/aselect.php Mon Jul 12 05:43:59
2010
+++ /trunk/modules/aselect/lib/Auth/Source/aselect.php Mon May 14 04:34:55
2012
@@ -1,30 +1,15 @@
<?php
/**
- * A-Select authentication source.
+ * Authentication module which acts as an A-Select client
*
- * Based on www/aselect/handler.php by Hans Zandbelt, SURFnet BV.
<hans.zandb...@surfnet.nl>
- *
- * @author Patrick Honing, Hogeschool van Arnhem en Nijmegen.
<patrick.hon...@han.nl>
- * @package simpleSAMLphp
- * @version $Id$
+ * @author Wessel Dankers, Tilburg University
*/
class sspmod_aselect_Auth_Source_aselect extends SimpleSAML_Auth_Source {
-
- /**
- * The string used to identify our states.
- */
- const STAGE_INIT = 'aselect:init';
-
- /**
- * The key of the AuthId field in the state.
- */
- const AUTHID = 'aselect:AuthId';
-
- /**
- * @var array with aselect configuration
- */
- private $asconfig;
+ private $app_id = 'simplesamlphp';
+ private $server_id;
+ private $server_url;
+ private $private_key;
/**
* Constructor for this authentication source.
@@ -33,142 +18,186 @@
* @param array $config Configuration.
*/
public function __construct($info, $config) {
- assert('is_array($info)');
- assert('is_array($config)');
-
/* Call the parent constructor first, as required by the
interface. */
parent::__construct($info, $config);
- if (!array_key_exists('serverurl', $config)) throw new
Exception('aselect serverurl not specified');
- $this->asconfig['serverurl'] = $config['serverurl'];
-
- if (!array_key_exists('serverid', $config)) throw new Exception('aselect
serverid not specified');
- $this->asconfig['serverid'] = $config['serverid'];
-
- if (!array_key_exists('type', $config)) throw new Exception('aselect
type not specified');
- $this->asconfig['type'] = $config['type'];
-
- if ($this->asconfig['type'] == 'app') {
- if (!array_key_exists('app_id', $config)) throw new Exception('aselect
app_id not specified');
- $this->asconfig['app_id'] = $config['app_id'];
- } elseif($this->asconfig['type'] == 'cross') {
- if (!array_key_exists('local_organization', $config)) throw new
Exception('aselect local_organization not specified');
- $this->asconfig['local_organization'] =
$config['local_organization'];
-
- $this->asconfig['required_level'] = (array_key_exists('required_level',
$config)) ? $config['required_level'] : 10;
- } else {
- throw new Exception('aselect type need to be either app
or cross');
- }
-
+ $cfg = SimpleSAML_Configuration::loadFromArray($config,
+ 'Authentication source ' . var_export($this->authId,
true));
+
+ $cfg->getValueValidate('type', array('app'), 'app');
+ $this->app_id = $cfg->getString('app_id');
+ $this->private_key = $cfg->getString('private_key', null);
+
+ // accept these arguments with '_' for consistency
+ // accept these arguments without '_' for backwards
compatibility
+ $this->server_id = $cfg->getString('serverid', null);
+ if($this->server_id === null)
+ $this->server_id = $cfg->getString('server_id');
+
+ $this->server_url = $cfg->getString('serverurl', null);
+ if($this->server_url === null)
+ $this->server_url = $cfg->getString('server_url');
+ }
+
+ /**
+ * Initiate authentication.
+ *
+ * @param array &$state Information about the current authentication.
+ */
+ public function authenticate(&$state) {
+ $state['aselect::authid'] = $this->authId;
+ $id = SimpleSAML_Auth_State::saveState($state, 'aselect:login',
true);
+
+ try {
+ $app_url = SimpleSAML_Module::getModuleURL('aselect/credentials.php',
array('ssp_state' => $id));
+ $as_url = $this->request_authentication($app_url);
+
+ SimpleSAML_Utilities::redirect($as_url);
+ } catch(Exception $e) {
+ // attach the exception to the state
+ SimpleSAML_Auth_State::throwException($state, $e);
+ }
+ }
+
+ /**
+ * Sign a string using the configured private key
+ *
+ * @param string $str The string to calculate a signature for
+ */
+ private function base64_signature($str) {
+ $key = openssl_pkey_get_private($this->private_key);
+ if($key === false)
+ throw new SimpleSAML_Error_Exception("Unable to load private
key: ".openssl_error_string());
+ if(!openssl_sign($str, $sig, $key))
+ throw new SimpleSAML_Error_Exception("Unable to create
signature: ".openssl_error_string());
+ openssl_pkey_free($key);
+ return base64_encode($sig);
}
-
- // helper function for sending a non-browser request to a remote server
- function as_call($url) {
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($ch, CURLOPT_URL, $url);
- $result = curl_exec($ch);
- $error = curl_error($ch);
- curl_close($ch);
- if ($result == FALSE) {
- throw new Exception('Request on remote server failed: '
. $error);
- }
- $parms = array();
- foreach (explode('&', $result) as $parm) {
- $tuple = explode('=', $parm);
- $parms[urldecode($tuple[0])] = urldecode($tuple[1]);
- }
- if ($parms['result_code'] != '0000') {
- throw new Exception('Request on remote server returned error: ' .
$result);
- }
- return $parms;
+ /**
+ * Parse a base64 encoded attribute blob. Can't use parse_str() because
it
+ * may contain multi-valued attributes.
+ *
+ * @param string $base64 The base64 string to decode.
+ */
+ private static function decode_attributes($base64) {
+ $blob = base64_decode($base64, true);
+ if($blob === false)
+ throw new SimpleSAML_Error_Exception("Attributes parameter base64
malformed");
+ $pairs = explode('&', $blob);
+ $ret = array();
+ foreach($pairs as $pair) {
+ $keyval = explode('=', $pair, 2);
+ if(count($keyval) < 2)
+ throw new SimpleSAML_Error_Exception("Missing value in attributes
parameter");
+ $key = urldecode($keyval[0]);
+ $val = urldecode($keyval[1]);
+ $ret[$key][] = $val;
+ }
+ return $ret;
}
+ /**
+ * Default options for curl invocations.
+ */
+ private static $curl_options = array(
+ CURLOPT_BINARYTRANSFER => true,
+ CURLOPT_FAILONERROR => true,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_CONNECTTIMEOUT => 1,
+ CURLOPT_TIMEOUT => 5,
+ CURLOPT_USERAGENT => "simpleSAMLphp",
+ );
/**
- * Log-in using A-Select
+ * Create a (possibly signed) URL to contact the A-Select server.
*
- * @param array &$state Information about the current authentication.
+ * @param string $request The name of the request (authenticate /
verify_credentials).
+ * @param array $parameters The parameters to pass for this request.
*/
- public function authenticate(&$state) {
- assert('is_array($state)');
-
- /* We are going to need the authId in order to retrieve this
authentication source later. */
- $state[self::AUTHID] = $this->authId;
-
- $stateID = SimpleSAML_Auth_State::saveState($state,
self::STAGE_INIT);
-
- $serviceUrl = SimpleSAML_Module::getModuleURL('aselect/linkback.php',
array('stateID' => $stateID));
-
- if ($this->asconfig['type'] == 'app') {
- $params = array(
- 'request' => 'authenticate',
- 'a-select-server' =>
$this->asconfig['serverid'],
- 'app_id' =>
$this->asconfig['app_id'],
- 'app_url' => $serviceUrl,
- );
- } else { // type = cross
- $params = array(
- 'request' => 'authenticate',
- 'a-select-server' =>
$this->asconfig['serverid'],
- 'local_organization' =>
$this->asconfig['local_organization'],
- 'required_level' =>
$this->asconfig['required_level'],
- 'local_as_url' => $serviceUrl,
-
- );
- }
- $url =
SimpleSAML_Utilities::addURLparameter($this->asconfig['serverurl'],$params);
-
- $parm = $this->as_call($url);
-
- SimpleSAML_Utilities::redirect(
- $parm['as_url'],
- array(
- 'rid' => $parm['rid'],
- 'a-select-server' =>
$this->asconfig['serverid'],
- )
- );
+ private function create_aselect_url($request, $parameters) {
+ $parameters['request'] = $request;
+ $parameters['a-select-server'] = $this->server_id;
+ if(!is_null($this->private_key)) {
+ $signable = '';
+
foreach(array('a-select-server', 'app_id', 'app_url', 'aselect_credentials', 'rid')
as $p)
+ if(array_key_exists($p, $parameters))
+ $signable .= $parameters[$p];
+ $parameters['signature'] =
$this->base64_signature($signable);
+ }
+ return SimpleSAML_Utilities::addURLparameter($this->server_url,
$parameters);
+ }
+
+ /**
+ * Contact the A-Select server and return the result as an associative
array.
+ *
+ * @param string $request The name of the request (authenticate /
verify_credentials).
+ * @param array $parameters The parameters to pass for this request.
+ */
+ private function call_aselect($request, $parameters) {
+ $url = $this->create_aselect_url($request, $parameters);
+
+ $curl = curl_init($url);
+ if($curl === false)
+ throw new SimpleSAML_Error_Exception("Unable to create CURL
handle");
+
+ if(!curl_setopt_array($curl, self::$curl_options))
+ throw new SimpleSAML_Error_Exception("Unable to set CURL
options: ".curl_error($curl));
+
+ $str = curl_exec($curl);
+ $err = curl_error($curl);
+
+ curl_close($curl);
+
+ if($str === false)
+ throw new SimpleSAML_Error_Exception("Unable to retrieve
URL: $error");
+
+ parse_str($str, $res);
+
+ // message is only available with some A-Select server
implementations
+ if($res['result_code'] != '0000')
+ if(array_key_exists('message', $res))
+ throw new SimpleSAML_Error_Exception("Unable to contact SSO service:
result_code=".$res['result_code']." message=".$res['message']);
+ else
+ throw new SimpleSAML_Error_Exception("Unable to contact SSO service:
result_code=".$res['result_code']);
+ unset($res['result_code']);
+
+ return $res;
}
- public function finalStep(&$state) {
- $credentials = $state['aselect:credentials'];
- $rid = $state['aselect:rid'];
- assert('isset($credentials)');
- assert('isset($rid)');
-
- $params = array(
- 'request' => 'verify_credentials',
- 'rid' => $rid,
- 'a-select-server' => $this->asconfig['serverid'],
- 'aselect_credentials' => $credentials,
- );
- if ($this->asconfig['type'] == 'cross') {
- $params['local_organization'] =
$this->asconfig['local_organization'];
- }
-
- $url =
SimpleSAML_Utilities::addURLparameter($this->asconfig['serverurl'],
$params);
-
- $parms = $this->as_call($url);
- $attributes = array('uid' => array($parms['uid']));
-
- if (array_key_exists('attributes', $parms)) {
- $decoded = base64_decode($parms['attributes']);
- foreach (explode('&', $decoded) as $parm) {
- $tuple = explode('=', $parm);
- $name = urldecode($tuple[0]);
- if (preg_match('/\[\]$/',$name)) {
- $name = substr($name, 0 ,-2);
- }
- if (!array_key_exists($name, $attributes)) {
- $attributes[$name] = array();
- }
- $attributes[$name][] = urldecode($tuple[1]);
- }
- }
- $state['Attributes'] = $attributes;
-
- SimpleSAML_Auth_Source::completeAuth($state);
+ /**
+ * Initiate authentication. Returns a URL to redirect the user to.
+ *
+ * @param string $app_url The SSP URL to return to after authenticating
(similar to an ACS).
+ */
+ public function request_authentication($app_url) {
+ $res = $this->call_aselect('authenticate',
+ array('app_id' => $this->app_id, 'app_url' =>
$app_url));
+
+ $as_url = $res['as_url'];
+ unset($res['as_url']);
+
+ return SimpleSAML_Utilities::addURLparameter($as_url, $res);
+ }
+
+ /**
+ * Verify the credentials upon return from the A-Select server. Returns
an associative array
+ * with the information given by the A-Select server. Any attributes are
pre-parsed.
+ *
+ * @param string $server_id The A-Select server ID as passed by the
client
+ * @param string $credentials The credentials as passed by the client
+ * @param string $rid The request ID as passed by the client
+ */
+ public function verify_credentials($server_id, $credentials, $rid) {
+ if($server_id != $this->server_id)
+ throw new SimpleSAML_Error_Exception("Acquired server ID ($server_id)
does not match configured server ID ($this->server_id)");
+
+ $res = $this->call_aselect('verify_credentials',
+ array('aselect_credentials' => $credentials, 'rid' =>
$rid));
+
+ if(array_key_exists('attributes', $res))
+ $res['attributes'] =
self::decode_attributes($res['attributes']);
+
+ return $res;
}
}
=======================================
--- /trunk/modules/aselect/www/linkback.php Tue Jun 15 05:47:55 2010
+++ /trunk/modules/aselect/www/linkback.php Mon May 14 04:34:55 2012
@@ -1,36 +0,0 @@
-<?php
-
-/**
- * Handle linkback() response from A-Select.
- */
-
-
-if (!isset($_GET['stateID'])) {
- throw new SimpleSAML_Error_BadRequest('Missing stateID parameter.');
-}
-$stateId = (string)$_GET['stateID'];
-
-if (!isset($_GET['aselect_credentials'])) {
- throw new SimpleSAML_Error_BadRequest('Missing aselect_credentials
parameter.');
-}
-if (!isset($_GET['rid'])) {
- throw new SimpleSAML_Error_BadRequest('Missing ridparameter.');
-}
-
-
-$state = SimpleSAML_Auth_State::loadState($stateId,
sspmod_aselect_Auth_Source_aselect::STAGE_INIT);
-$state['aselect:credentials'] = $_GET['aselect_credentials'];
-$state['aselect:rid'] = $_GET['rid'];
-
-
-/* Find authentication source. */
-assert('array_key_exists(sspmod_aselect_Auth_Source_aselect::AUTHID,
$state)');
-$sourceId = $state[sspmod_aselect_Auth_Source_aselect::AUTHID];
-
-$source = SimpleSAML_Auth_Source::getById($sourceId);
-if ($source === NULL) {
- throw new Exception('Could not find authentication source with id ' .
$sourceId);
-}
-
-$source->finalStep($state);
-
--
You received this message because you are subscribed to the Google Groups
"simpleSAMLphp commits" group.
To post to this group, send email to simplesamlphp-commits@googlegroups.com.
To unsubscribe from this group, send email to
simplesamlphp-commits+unsubscr...@googlegroups.com.
For more options, visit this group at
http://groups.google.com/group/simplesamlphp-commits?hl=en.