Skip to content
Snippets Groups Projects
Commit d74b7715 authored by Olav Morken's avatar Olav Morken
Browse files

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.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@3091 44740490-163a-0410-bde0-09ae8108e29a
parent 9fa888c2
No related branches found
No related tags found
No related merge requests found
Using the A-Select authentication source with simpleSAMLphp
===========================================================
A-Select module for 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.
This module allows one to use an A-Select server as authentication
source for simpleSAMLphp.
The structure applied follows the structure of the CAS authentication source.
The module supports the A-Select protocol, including signing of
requests. Not supported is A-Select Cross.
Usage:
Setting up the A-Select authentication module
----------------------------------------------
Enable the module if not already enabled:
$ touch modules/aselect/enabled
The first thing you need to do is to enable the aselect module:
In config/authsources.php, configure your A-Selectserver as an
authentication source. The following is an example for a source
named 'aselect':
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(
'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.
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.
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
),
Author: Wessel Dankers <wsl@uvt.nl>
Copyright: © 2011,2012 Tilburg University (http://www.tilburguniversity.edu)
License: GPL version 3 or any later version.
<?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.zandbelt@surfnet.nl>
*
* @author Patrick Honing, Hogeschool van Arnhem en Nijmegen. <Patrick.Honing@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 @@ class sspmod_aselect_Auth_Source_aselect extends SimpleSAML_Auth_Source {
* @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'];
$cfg = SimpleSAML_Configuration::loadFromArray($config,
'Authentication source ' . var_export($this->authId, true));
if (!array_key_exists('serverid', $config)) throw new Exception('aselect serverid not specified');
$this->asconfig['serverid'] = $config['serverid'];
$cfg->getValueValidate('type', array('app'), 'app');
$this->app_id = $cfg->getString('app_id');
$this->private_key = $cfg->getString('private_key', null);
if (!array_key_exists('type', $config)) throw new Exception('aselect type not specified');
$this->asconfig['type'] = $config['type'];
// 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');
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->server_url = $cfg->getString('serverurl', null);
if($this->server_url === null)
$this->server_url = $cfg->getString('server_url');
}
$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');
}
/**
* 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);
/**
* 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 $parms;
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,
);
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);
}
$url = SimpleSAML_Utilities::addURLparameter($this->asconfig['serverurl'],$params);
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");
$parm = $this->as_call($url);
parse_str($str, $res);
SimpleSAML_Utilities::redirect(
$parm['as_url'],
array(
'rid' => $parm['rid'],
'a-select-server' => $this->asconfig['serverid'],
)
);
// 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'];
}
/**
* 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));
$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;
$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']);
SimpleSAML_Auth_Source::completeAuth($state);
return $res;
}
}
<?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();
<?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);
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment