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

Authentication processing filters.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@811 44740490-163a-0410-bde0-09ae8108e29a
parent c1c2c004
No related branches found
No related tags found
No related merge requests found
<?php
/**
* Class for implementing authentication processing chains for IdPs.
*
* This class implements a system for additional steps which should be taken by an IdP before
* submitting a response to a SP. Examples of additional steps can be additional authentication
* checks, or attribute consent requirements.
*
* @author Olav Morken, UNINETT AS.
* @package simpleSAMLphp
* @version $Id$
*/
class SimpleSAML_Auth_ProcessingChain {
/**
* The list of remaining filters which should be applied to the state.
*/
const FILTERS_INDEX = 'SimpleSAML_Auth_ProcessingChain.filters';
/**
* The stage we use for completed requests.
*/
const COMPLETED_STAGE = 'SimpleSAML_Auth_ProcessingChain.completed';
/**
* The request parameter we will use to pass the state identifier when we redirect after
* having completed processing of the state.
*/
const AUTHPARAM = 'AuthProcId';
/**
* All authentication processing filters, in the order they should be applied.
*/
private $filters;
/**
* Initialize an authentication processing chain for the given service provider
* and identity provider.
*
* @param array $idpMetadata The metadata for the IdP.
* @param array $spMetadata The metadata for the SP.
*/
public function __construct($idpMetadata, $spMetadata) {
assert('is_array($idpMetadata)');
assert('is_array($spMetadata)');
$this->filters = array();
if (array_key_exists('authproc', $idpMetadata)) {
$idpFilters = self::parseFilterList($idpMetadata['authproc']);
self::addFilters($this->filters, $idpFilters);
}
if (array_key_exists('authproc', $spMetadata)) {
$spFilters = self::parseFilterList($spMetadata['authproc']);
self::addFilters($this->filters, $spFilters);
}
SimpleSAML_Logger::debug('Filter config for ' . $idpMetadata['entityid'] . '->' .
$spMetadata['entityid'] . ': ' . str_replace("\n", '', var_export($this->filters, TRUE)));
}
/**
* Sort & merge filter configuration
*
* Inserts unsorted filters into sorted filter list. This sort operation is stable.
*
* @param array &$target Target filter list. This list must be sorted.
* @param array $src Source filters. May be unsorted.
*/
private static function addFilters(&$target, $src) {
assert('is_array($target)');
assert('is_array($src)');
foreach ($src as $filter) {
$fp = $filter->priority;
/* Find insertion position for filter. */
for($i = count($target)-1; $i >= 0; $i--) {
if ($target[$i]->priority <= $fp) {
/* The new filter should be inserted after this one. */
break;
}
}
/* $i now points to the filter which should preceede the current filter. */
array_splice($target, $i+1, 0, array($filter));
}
}
/**
* Parse an array of authentication processing filters.
*
* @param array $filterSrc Array with filter configuration.
* @return array Array of SimpleSAML_Auth_ProcessingFilter objects.
*/
private static function parseFilterList($filterSrc) {
assert('is_array($filterSrc)');
$parsedFilters = array();
foreach ($filterSrc as $filter) {
if (is_string($filter)) {
$filter = array($filter);
}
if (!is_array($filter)) {
throw new Exception('Invalid authentication processing filter configuration: ' .
'One of the filters wasn\'t a string or an array.');
}
$parsedFilters[] = self::parseFilter($filter);
}
return $parsedFilters;
}
/**
* Parse an authentication processing filter.
*
* @param array $config Array with the authentication processing filter configuration.
* @return SimpleSAML_Auth_ProcessingFilter The parsed filter.
*/
private static function parseFilter($config) {
assert('is_array($config)');
if (!array_key_exists(0, $config)) {
throw new Exception('Authentication processing filter without name given.');
}
$className = SimpleSAML_Module::resolveClass($config[0], 'Auth_Process',
'SimpleSAML_Auth_ProcessingFilter');
unset($config[0]);
return new $className($config, NULL);
}
/**
* Process the given state.
*
* This function will only return if processing completes. If processing requires showing
* a page to the user, we will redirect to the URL set in $state['ReturnURL'] after processing is
* completed.
*
* @param array &$state The state we are processing.
*/
public function processState(&$state) {
assert('is_array($state)');
assert('array_key_exists("ReturnURL", $state)');
$state[self::FILTERS_INDEX] = $this->filters;
while (count($state[self::FILTERS_INDEX]) > 0) {
$filter = array_shift($state[self::FILTERS_INDEX]);
$filter->process($state);
}
/* Completed. */
}
/**
* Continues processing of the state.
*
* This function is used to resume processing by filters which for example needed to show
* a page to the user.
*
* This function will never return. In the case of an exception, exception handling should
* be left to the main simpleSAMLphp exception handler.
*
* @param array $state The state we are processing.
*/
public static function resumeProcessing($state) {
assert('is_array($state)');
while (count($state[self::FILTERS_INDEX]) > 0) {
$filter = array_shift($state[self::FILTERS_INDEX]);
$filter->process($state);
}
assert('array_key_exists("ReturnURL", $state)');
/* Completed. Save state information, and redirect to the URL specified
* in $state['ReturnURL'].
*/
$id = SimpleSAML_Auth_State::saveState($state, self::COMPLETED_STAGE);
SimpleSAML_Utilities::redirect($state['ReturnURL'], array(self::AUTHPARAM => $id));
}
/**
* Retrieve a state which has finished processing.
*
* @param string $id The identifier of the state. This can be found in the request parameter
* with index from SimpleSAML_Auth_ProcessingChain::AUTHPARAM.
*/
public static function fetchProcessedState($id) {
assert('is_string($id)');
return SimpleSAML_Auth_State::loadState($id, self::COMPLETED_STAGE);
}
}
?>
\ No newline at end of file
<?php
/**
* Base class for authentication processing filters.
*
* All authentication processing filters must support serialization.
*
* The current request is stored in an associative array. It has the following defined attributes:
* - 'Attributes' The attributes of the user.
* - 'Destination' Metadata of the destination (SP).
* - 'Source' Metadata of the source (IdP).
*
* It may also contain other attributes. If an authentication processing filter wishes to store other
* information in it, it should have a name on the form 'module:filter:attributename', to avoid name
* collisions.
*
* @author Olav Morken, UNINETT AS.
* @package simpleSAMLphp
* @version $Id$
*/
abstract class SimpleSAML_Auth_ProcessingFilter {
/**
* Priority of this filter.
*
* Used when merging IdP and SP processing chains.
* The priority can be any integer. The default for most filters is 50. Filters may however
* specify their own default, if they typically should be amongst the first or the last filters.
*
* The prioroty can also be overridden by the user by specifying the '%priority' option.
*/
public $priority = 50;
/**
* Constructor for a processing filter.
*
* Any processing filter which implements its own constructor must call this
* constructor first.
*
* @param array &$config Configuration for this filter.
* @param mixed $reserved For future use.
*/
public function __construct(&$config, $reserved) {
assert('is_array($config)');
if(array_key_exists('%priority', $config)) {
$this->priority = $config['%priority'];
if(!is_int($this->priority)) {
throw new Exception('Invalid priority: ' . var_export($this->priority, TRUE));
}
unset($config['%priority']);
}
}
/**
* Process a request.
*
* When a filter returns from this function, it is assumed to have completed its task.
*
* @param array &$request The request we are currently processing.
*/
abstract public function process(&$request);
}
?>
\ No newline at end of file
This file indicates that the default state of this module
is enabled. To disable, create a file named disable in the
same directory as this file.
<?php
/**
* Filter to add attributes.
*
* This filter allows you to add attributes to the attribute set being processed.
*
* Example - add attribute, single value:
* <code>
* 'authproc' => array(
* array('core:AttributeAdd', 'source' => 'myidp'),
* ),
* </code>
*
* Examle - add attribute, multiple values:
* <code>
* 'authproc' => array(
* array('core:AttributeAdd', 'groups' => array('users', 'members')),
* ),
* </code>
*
* Examle - replace attribute, single value:
* <code>
* 'authproc' => array(
* array('core:AttributeAdd', '%replace', 'uid' => array('guest')),
* ),
* </code>
*
* @author Olav Morken, UNINETT AS.
* @package simpleSAMLphp
* @version $Id$
*/
class sspmod_core_Auth_Process_AttributeAdd extends SimpleSAML_Auth_ProcessingFilter {
/**
* Flag which indicates wheter this filter should append new values or replace old values.
*/
private $replace = FALSE;
/**
* Attributes which should be added/appended.
*
* Assiciative array of arrays.
*/
private $attributes = array();
/**
* Initialize this filter.
*
* @param array $config Configuration information about this filter.
* @param mixed $reserved For future use.
*/
public function __construct($config, $reserved) {
parent::__construct($config, $reserved);
assert('is_array($config)');
foreach($config as $name => $values) {
if(is_int($name)) {
if($values === '%replace') {
$this->replace = TRUE;
} else {
throw new Exception('Unknown flag: ' . var_export($values, TRUE));
}
continue;
}
if(!is_string($name)) {
throw new Exception('Invalid attribute name: ' . var_export($name, TRUE));
}
if(!is_array($values)) {
$values = array($values);
}
foreach($values as $value) {
if(!is_string($value)) {
throw new Exception('Invalid value for attribute ' . $name . ': ' .
var_export($values, TRUE));
}
}
$this->attributes[$name] = $values;
}
}
/**
* Apply filter to add or replace attributes.
*
* Add or replace existing attributes with the configured values.
*
* @param array &$request The current request
*/
public function process(&$request) {
assert('is_array($request)');
assert('array_key_exists("Attributes", $request)');
$attributes =& $request['Attributes'];
foreach($this->attributes as $name => $values) {
if($this->replace === TRUE || !array_key_exists($name, $attributes)) {
$attributes[$name] = $values;
} else {
$attributes[$name] = array_merge($attributes[$name], $values);
}
}
}
}
?>
\ No newline at end of file
<?php
/**
* A filter for limiting which attributes are passed on.
*
* Example - remove all attributes except 'cn' and 'mail':
* <code>
* 'authproc' => array(
* array('core:AttributeLimit', 'cn', 'mail'),
* ),
* </code>
*
* @author Olav Morken, UNINETT AS.
* @package simpleSAMLphp
* @version $Id$
*/
class sspmod_core_Auth_Process_AttributeLimit extends SimpleSAML_Auth_ProcessingFilter {
/**
* List of attributes which this filter will allow through.
*/
private $allowedAttributes = array();
/**
* Initialize this filter.
*
* @param array $config Configuration information about this filter.
* @param mixed $reserved For future use
*/
public function __construct($config, $reserved) {
parent::__construct($config, $reserved);
assert('is_array($config)');
foreach($config as $name) {
if(!is_string($name)) {
throw new Exception('Invalid attribute name: ' . var_export($name, TRUE));
}
$this->allowedAttributes[] = $name;
}
}
/**
* Apply filter to remove attributes.
*
* Removes all attributes which aren't one of the allowed attributes.
*
* @param array &$request The current request
*/
public function process(&$request) {
assert('is_array($request)');
assert('array_key_exists("Attributes", $request)');
$attributes =& $request['Attributes'];
foreach($attributes as $name => $values) {
if(!in_array($name, $this->allowedAttributes, TRUE)) {
unset($attributes[$name]);
}
}
}
}
?>
\ No newline at end of file
<?php
/**
* Attribute filter for renaming attributes.
*
* Example 1 - apply map stored in attributemap/defaultmap.php:
* <code>
* 'authproc' => array(
* array('core:AttributeMap', 'defaultmaps'),
* ),
* </code>
*
* Example 2 - rename attributes 'mail' and 'uid' to 'email' and 'user':
* <code>
* 'authproc' => array(
* array('core:AttributeMap', 'mail' => 'email', 'uid' => 'user'),
* ),
* </code>
*
* @author Olav Morken, UNINETT AS.
* @package simpleSAMLphp
* @version $Id$
*/
class sspmod_core_Auth_Process_AttributeMap extends SimpleSAML_Auth_ProcessingFilter {
/**
* Assosiative array with the mappings of attribute names.
*/
private $map = array();
/**
* Initialize this filter, parse configuration
*
* @param array $config Configuration information about this filter.
* @param mixed $reserved For future use.
*/
public function __construct($config, $reserved) {
parent::__construct($config, $reserved);
assert('is_array($config)');
foreach($config as $origName => $newName) {
if(is_int($origName)) {
/* No index given - this is a map file. */
$this->loadMapFile($newName);
continue;
}
if(!is_string($origName)) {
throw new Exception('Invalid attribute name: ' . var_export($origName, TRUE));
}
if(!is_string($newName)) {
throw new Exception('Invalid attribute name: ' . var_export($newName, TRUE));
}
$this->map[$origName] = $newName;
}
}
/**
* Loads and merges in a file with a attribute map.
*
* @param string $fileName Name of attribute map file. Expected to be in the attributenamemapdir.
*/
private function loadMapFile($fileName) {
$config = SimpleSAML_Configuration::getInstance();
$filePath = $config->getPathValue('attributenamemapdir') . $fileName . '.php';
if(!file_exists($filePath)) {
throw new Exception('Could not find attributemap file: ' . $filePath);
}
$attributemap = NULL;
include($filePath);
if(!is_array($attributemap)) {
throw new Exception('Attribute map file "' . $filePath . '" didn\'t define an attribute map.');
}
$this->map = array_merge($this->map, $attributemap);
}
/**
* Apply filter to rename attributes.
*
* @param array &$request The current request
*/
public function process(&$request) {
assert('is_array($request');
assert('array_key_exists("Attributes", $request)');
$attributes =& $request['Attributes'];
foreach($attributes as $name => $values) {
if(array_key_exists($name, $this->map)) {
$attributes[$this->map[$name]] = $values;
unset($attributes[$name]);
}
}
}
}
?>
\ No newline at end of file
<?php
/**
* A simple processing filter for testing that redirection works as it should.
*
*/
class sspmod_exampleauth_Auth_Process_RedirectTest extends SimpleSAML_Auth_ProcessingFilter {
/**
* Initialize processing of the redirect test.
*
* @param array &$state The state we should update.
*/
public function process(&$state) {
assert('is_array($state)');
assert('array_key_exists("Attributes", $state)');
/* To check whether the state is saved correctly. */
$state['Attributes']['RedirectTest1'] = array('OK');
/* Save state and redirect. */
$id = SimpleSAML_Auth_State::saveState($state, 'exampleauth:redirectfilter-test');
$url = SimpleSAML_Module::getModuleURL('exampleauth/redirecttest.php');
SimpleSAML_Utilities::redirect($url, array('StateId' => $id));
}
}
?>
\ No newline at end of file
<?php
/**
* Request handler for redirect filter test.
*
* @author Olav Morken, UNINETT AS.
* @package simpleSAMLphp
* @version $Id$
*/
if (!array_key_exists('StateId', $_REQUEST)) {
throw new SimpleSAML_Error_BadRequest('Missing required StateId query parameter.');
}
$id = $_REQUEST['StateId'];
$state = SimpleSAML_Auth_State::loadState($id, 'exampleauth:redirectfilter-test');
$state['Attributes']['RedirectTest2'] = array('OK');
SimpleSAML_Auth_ProcessingChain::resumeProcessing($state);
?>
\ No newline at end of file
......@@ -139,6 +139,13 @@ if (isset($_GET['SAMLRequest'])) {
SimpleSAML_Utilities::fatalError($session->getTrackID(), 'CACHEAUTHNREQUEST', $exception);
}
} elseif(isset($_REQUEST[SimpleSAML_Auth_ProcessingChain::AUTHPARAM])) {
/* Resume from authentication processing chain. */
$authProcId = $_REQUEST[SimpleSAML_Auth_ProcessingChain::AUTHPARAM];
$authProcState = SimpleSAML_Auth_ProcessingChain::fetchProcessedState($authProcId);
$requestcache = $authProcState['core:saml20-idp:requestcache'];
} else {
SimpleSAML_Utilities::fatalError($session->getTrackID(), 'SSOSERVICEPARAMS');
}
......@@ -270,6 +277,34 @@ if($needAuth && !$isPassive) {
$filteredattributes = $afilter->getAttributes();
/* Authentication processing operations. */
if (array_key_exists('AuthProcState', $requestcache)) {
/* Processed earlier, saved in requestcache. */
$authProcState = $requestcache['AuthProcState'];
} elseif (isset($authProcState)) {
/* Returned from redirect during processing. */
$requestcache['AuthProcState'] = $authProcState;
} else {
/* Not processed. */
$pc = new SimpleSAML_Auth_ProcessingChain($idpmetadata, $spmetadata);
$authProcState = array(
'core:saml20-idp:requestcache' => $requestcache,
'ReturnURL' => SimpleSAML_Utilities::selfURLNoQuery(),
'Attributes' => $filteredattributes,
'Destination' => $spmetadata,
'Source' => $idpmetadata,
);
$pc->processState($authProcState);
$requestcache['AuthProcState'] = $authProcState;
}
$filteredattributes = $authProcState['Attributes'];
......
......@@ -91,6 +91,12 @@ if (isset($_GET['shire'])) {
SimpleSAML_Utilities::fatalError($session->getTrackID(), 'CACHEAUTHNREQUEST', $exception);
}
} elseif(isset($_REQUEST[SimpleSAML_Auth_ProcessingChain::AUTHPARAM])) {
/* Resume from authentication processing chain. */
$authProcId = $_REQUEST[SimpleSAML_Auth_ProcessingChain::AUTHPARAM];
$authProcState = SimpleSAML_Auth_ProcessingChain::fetchProcessedState($authProcId);
$requestcache = $authProcState['core:shib13-idp:requestcache'];
} else {
SimpleSAML_Utilities::fatalError($session->getTrackID(), 'SSOSERVICEPARAMS');
......@@ -184,6 +190,35 @@ if (!$session->isAuthenticated($authority) ) {
$filteredattributes = $afilter->getAttributes();
/* Authentication processing operations. */
if (array_key_exists('AuthProcState', $requestcache)) {
/* Processed earlier, saved in requestcache. */
$authProcState = $requestcache['AuthProcState'];
} elseif (isset($authProcState)) {
/* Returned from redirect during processing. */
$requestcache['AuthProcState'] = $authProcState;
} else {
/* Not processed. */
$pc = new SimpleSAML_Auth_ProcessingChain($idpmetadata, $spmetadata);
$authProcState = array(
'core:shib13-idp:requestcache' => $requestcache,
'ReturnURL' => SimpleSAML_Utilities::selfURLNoQuery(),
'Attributes' => $filteredattributes,
'Destination' => $spmetadata,
'Source' => $idpmetadata,
);
$pc->processState($authProcState);
$requestcache['AuthProcState'] = $authProcState;
}
$filteredattributes = $authProcState['Attributes'];
/*
* Dealing with attribute release consent.
......
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