514 lines
17 KiB
PHP
Executable File
514 lines
17 KiB
PHP
Executable File
<?php
|
|
|
|
use SimpleSAML\Auth\SourceFactory;
|
|
|
|
/**
|
|
* This class defines a base class for authentication source.
|
|
*
|
|
* An authentication source is any system which somehow authenticate the user.
|
|
*
|
|
* @author Olav Morken, UNINETT AS.
|
|
* @package SimpleSAMLphp
|
|
*/
|
|
abstract class SimpleSAML_Auth_Source
|
|
{
|
|
|
|
|
|
/**
|
|
* The authentication source identifier. This identifier can be used to look up this object, for example when
|
|
* returning from a login form.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $authId;
|
|
|
|
|
|
/**
|
|
* Constructor for an authentication source.
|
|
*
|
|
* Any authentication source which implements its own constructor must call this
|
|
* constructor first.
|
|
*
|
|
* @param array $info Information about this authentication source.
|
|
* @param array &$config Configuration for this authentication source.
|
|
*/
|
|
public function __construct($info, &$config)
|
|
{
|
|
assert(is_array($info));
|
|
assert(is_array($config));
|
|
|
|
assert(array_key_exists('AuthId', $info));
|
|
$this->authId = $info['AuthId'];
|
|
}
|
|
|
|
|
|
/**
|
|
* Get sources of a specific type.
|
|
*
|
|
* @param string $type The type of the authentication source.
|
|
*
|
|
* @return SimpleSAML_Auth_Source[] Array of SimpleSAML_Auth_Source objects of the specified type.
|
|
* @throws Exception If the authentication source is invalid.
|
|
*/
|
|
public static function getSourcesOfType($type)
|
|
{
|
|
assert(is_string($type));
|
|
|
|
$config = SimpleSAML_Configuration::getConfig('authsources.php');
|
|
|
|
$ret = array();
|
|
|
|
$sources = $config->getOptions();
|
|
foreach ($sources as $id) {
|
|
$source = $config->getArray($id);
|
|
|
|
self::validateSource($source, $id);
|
|
|
|
if ($source[0] !== $type) {
|
|
continue;
|
|
}
|
|
|
|
$ret[] = self::parseAuthSource($id, $source);
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Retrieve the ID of this authentication source.
|
|
*
|
|
* @return string The ID of this authentication source.
|
|
*/
|
|
public function getAuthId()
|
|
{
|
|
return $this->authId;
|
|
}
|
|
|
|
|
|
/**
|
|
* Process a request.
|
|
*
|
|
* If an authentication source returns from this function, it is assumed to have
|
|
* authenticated the user, and should have set elements in $state with the attributes
|
|
* of the user.
|
|
*
|
|
* If the authentication process requires additional steps which make it impossible to
|
|
* complete before returning from this function, the authentication source should
|
|
* save the state, and at a later stage, load the state, update it with the authentication
|
|
* information about the user, and call completeAuth with the state array.
|
|
*
|
|
* @param array &$state Information about the current authentication.
|
|
*/
|
|
abstract public function authenticate(&$state);
|
|
|
|
|
|
/**
|
|
* Reauthenticate an user.
|
|
*
|
|
* This function is called by the IdP to give the authentication source a chance to
|
|
* interact with the user even in the case when the user is already authenticated.
|
|
*
|
|
* @param array &$state Information about the current authentication.
|
|
*/
|
|
public function reauthenticate(array &$state)
|
|
{
|
|
assert(isset($state['ReturnCallback']));
|
|
|
|
// the default implementation just copies over the previous authentication data
|
|
$session = SimpleSAML_Session::getSessionFromRequest();
|
|
$data = $session->getAuthState($this->authId);
|
|
foreach ($data as $k => $v) {
|
|
$state[$k] = $v;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Complete authentication.
|
|
*
|
|
* This function should be called if authentication has completed. It will never return,
|
|
* except in the case of exceptions. Exceptions thrown from this page should not be caught,
|
|
* but should instead be passed to the top-level exception handler.
|
|
*
|
|
* @param array &$state Information about the current authentication.
|
|
*/
|
|
public static function completeAuth(&$state)
|
|
{
|
|
assert(is_array($state));
|
|
assert(array_key_exists('LoginCompletedHandler', $state));
|
|
|
|
SimpleSAML_Auth_State::deleteState($state);
|
|
|
|
$func = $state['LoginCompletedHandler'];
|
|
assert(is_callable($func));
|
|
|
|
call_user_func($func, $state);
|
|
assert(false);
|
|
}
|
|
|
|
|
|
/**
|
|
* Start authentication.
|
|
*
|
|
* This method never returns.
|
|
*
|
|
* @param string|array $return The URL or function we should direct the user to after authentication. If using a
|
|
* URL obtained from user input, please make sure to check it by calling \SimpleSAML\Utils\HTTP::checkURLAllowed().
|
|
* @param string|null $errorURL The URL we should direct the user to after failed authentication. Can be null, in
|
|
* which case a standard error page will be shown. If using a URL obtained from user input, please make sure to
|
|
* check it by calling \SimpleSAML\Utils\HTTP::checkURLAllowed().
|
|
* @param array $params Extra information about the login. Different authentication requestors may provide different
|
|
* information. Optional, will default to an empty array.
|
|
*/
|
|
public function initLogin($return, $errorURL = null, array $params = array())
|
|
{
|
|
assert(is_string($return) || is_array($return));
|
|
assert(is_string($errorURL) || $errorURL === null);
|
|
|
|
$state = array_merge($params, array(
|
|
'SimpleSAML_Auth_Default.id' => $this->authId, // TODO: remove in 2.0
|
|
'SimpleSAML_Auth_Source.id' => $this->authId,
|
|
'SimpleSAML_Auth_Default.Return' => $return, // TODO: remove in 2.0
|
|
'SimpleSAML_Auth_Source.Return' => $return,
|
|
'SimpleSAML_Auth_Default.ErrorURL' => $errorURL, // TODO: remove in 2.0
|
|
'SimpleSAML_Auth_Source.ErrorURL' => $errorURL,
|
|
'LoginCompletedHandler' => array(get_class(), 'loginCompleted'),
|
|
'LogoutCallback' => array(get_class(), 'logoutCallback'),
|
|
'LogoutCallbackState' => array(
|
|
'SimpleSAML_Auth_Default.logoutSource' => $this->authId, // TODO: remove in 2.0
|
|
'SimpleSAML_Auth_Source.logoutSource' => $this->authId,
|
|
),
|
|
));
|
|
|
|
if (is_string($return)) {
|
|
$state['SimpleSAML_Auth_Default.ReturnURL'] = $return; // TODO: remove in 2.0
|
|
$state['SimpleSAML_Auth_Source.ReturnURL'] = $return;
|
|
}
|
|
|
|
if ($errorURL !== null) {
|
|
$state[SimpleSAML_Auth_State::EXCEPTION_HANDLER_URL] = $errorURL;
|
|
}
|
|
|
|
try {
|
|
$this->authenticate($state);
|
|
} catch (SimpleSAML_Error_Exception $e) {
|
|
SimpleSAML_Auth_State::throwException($state, $e);
|
|
} catch (Exception $e) {
|
|
$e = new SimpleSAML_Error_UnserializableException($e);
|
|
SimpleSAML_Auth_State::throwException($state, $e);
|
|
}
|
|
self::loginCompleted($state);
|
|
}
|
|
|
|
|
|
/**
|
|
* Called when a login operation has finished.
|
|
*
|
|
* This method never returns.
|
|
*
|
|
* @param array $state The state after the login has completed.
|
|
*/
|
|
public static function loginCompleted($state)
|
|
{
|
|
assert(is_array($state));
|
|
assert(array_key_exists('SimpleSAML_Auth_Source.Return', $state));
|
|
assert(array_key_exists('SimpleSAML_Auth_Source.id', $state));
|
|
assert(array_key_exists('Attributes', $state));
|
|
assert(!array_key_exists('LogoutState', $state) || is_array($state['LogoutState']));
|
|
|
|
$return = $state['SimpleSAML_Auth_Source.Return'];
|
|
|
|
// save session state
|
|
$session = SimpleSAML_Session::getSessionFromRequest();
|
|
$authId = $state['SimpleSAML_Auth_Source.id'];
|
|
$session->doLogin($authId, SimpleSAML_Auth_State::getPersistentAuthData($state));
|
|
|
|
if (is_string($return)) { // redirect...
|
|
\SimpleSAML\Utils\HTTP::redirectTrustedURL($return);
|
|
} else {
|
|
call_user_func($return, $state);
|
|
}
|
|
assert(false);
|
|
}
|
|
|
|
|
|
/**
|
|
* Log out from this authentication source.
|
|
*
|
|
* This function should be overridden if the authentication source requires special
|
|
* steps to complete a logout operation.
|
|
*
|
|
* If the logout process requires a redirect, the state should be saved. Once the
|
|
* logout operation is completed, the state should be restored, and completeLogout
|
|
* should be called with the state. If this operation can be completed without
|
|
* showing the user a page, or redirecting, this function should return.
|
|
*
|
|
* @param array &$state Information about the current logout operation.
|
|
*/
|
|
public function logout(&$state)
|
|
{
|
|
assert(is_array($state));
|
|
// default logout handler which doesn't do anything
|
|
}
|
|
|
|
|
|
/**
|
|
* Complete logout.
|
|
*
|
|
* This function should be called after logout has completed. It will never return,
|
|
* except in the case of exceptions. Exceptions thrown from this page should not be caught,
|
|
* but should instead be passed to the top-level exception handler.
|
|
*
|
|
* @param array &$state Information about the current authentication.
|
|
*/
|
|
public static function completeLogout(&$state)
|
|
{
|
|
assert(is_array($state));
|
|
assert(array_key_exists('LogoutCompletedHandler', $state));
|
|
|
|
SimpleSAML_Auth_State::deleteState($state);
|
|
|
|
$func = $state['LogoutCompletedHandler'];
|
|
assert(is_callable($func));
|
|
|
|
call_user_func($func, $state);
|
|
assert(false);
|
|
}
|
|
|
|
|
|
/**
|
|
* Create authentication source object from configuration array.
|
|
*
|
|
* This function takes an array with the configuration for an authentication source object,
|
|
* and returns the object.
|
|
*
|
|
* @param string $authId The authentication source identifier.
|
|
* @param array $config The configuration.
|
|
*
|
|
* @return SimpleSAML_Auth_Source The parsed authentication source.
|
|
* @throws Exception If the authentication source is invalid.
|
|
*/
|
|
private static function parseAuthSource($authId, $config)
|
|
{
|
|
assert(is_string($authId));
|
|
assert(is_array($config));
|
|
|
|
self::validateSource($config, $authId);
|
|
|
|
$id = $config[0];
|
|
$info = array('AuthId' => $authId);
|
|
$authSource = null;
|
|
|
|
unset($config[0]);
|
|
|
|
try {
|
|
// Check whether or not there's a factory responsible for instantiating our Auth Source instance
|
|
$factoryClass = SimpleSAML\Module::resolveClass($id, 'Auth_Source_Factory', 'SimpleSAML\Auth\SourceFactory');
|
|
|
|
/** @var SourceFactory $factory */
|
|
$factory = new $factoryClass;
|
|
$authSource = $factory->create($info, $config);
|
|
} catch (Exception $e) {
|
|
// If not, instantiate the Auth Source here
|
|
$className = SimpleSAML\Module::resolveClass($id, 'Auth_Source', 'SimpleSAML_Auth_Source');
|
|
$authSource = new $className($info, $config);
|
|
}
|
|
|
|
return $authSource;
|
|
}
|
|
|
|
|
|
/**
|
|
* Retrieve authentication source.
|
|
*
|
|
* This function takes an id of an authentication source, and returns the
|
|
* AuthSource object. If no authentication source with the given id can be found,
|
|
* NULL will be returned.
|
|
*
|
|
* If the $type parameter is specified, this function will return an
|
|
* authentication source of the given type. If no authentication source or if an
|
|
* authentication source of a different type is found, an exception will be thrown.
|
|
*
|
|
* @param string $authId The authentication source identifier.
|
|
* @param string|NULL $type The type of authentication source. If NULL, any type will be accepted.
|
|
*
|
|
* @return SimpleSAML_Auth_Source|NULL The AuthSource object, or NULL if no authentication
|
|
* source with the given identifier is found.
|
|
* @throws SimpleSAML_Error_Exception If no such authentication source is found or it is invalid.
|
|
*/
|
|
public static function getById($authId, $type = null)
|
|
{
|
|
assert(is_string($authId));
|
|
assert($type === null || is_string($type));
|
|
|
|
// for now - load and parse config file
|
|
$config = SimpleSAML_Configuration::getConfig('authsources.php');
|
|
|
|
$authConfig = $config->getArray($authId, null);
|
|
if ($authConfig === null) {
|
|
if ($type !== null) {
|
|
throw new SimpleSAML_Error_Exception(
|
|
'No authentication source with id '.
|
|
var_export($authId, true).' found.'
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
$ret = self::parseAuthSource($authId, $authConfig);
|
|
|
|
if ($type === null || $ret instanceof $type) {
|
|
return $ret;
|
|
}
|
|
|
|
// the authentication source doesn't have the correct type
|
|
throw new SimpleSAML_Error_Exception(
|
|
'Invalid type of authentication source '.
|
|
var_export($authId, true).'. Was '.var_export(get_class($ret), true).
|
|
', should be '.var_export($type, true).'.'
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Called when the authentication source receives an external logout request.
|
|
*
|
|
* @param array $state State array for the logout operation.
|
|
*/
|
|
public static function logoutCallback($state)
|
|
{
|
|
assert(is_array($state));
|
|
assert(array_key_exists('SimpleSAML_Auth_Source.logoutSource', $state));
|
|
|
|
$source = $state['SimpleSAML_Auth_Source.logoutSource'];
|
|
|
|
$session = SimpleSAML_Session::getSessionFromRequest();
|
|
if (!$session->isValid($source)) {
|
|
SimpleSAML\Logger::warning(
|
|
'Received logout from an invalid authentication source '.
|
|
var_export($source, true)
|
|
);
|
|
|
|
return;
|
|
}
|
|
$session->doLogout($source);
|
|
}
|
|
|
|
|
|
/**
|
|
* Add a logout callback association.
|
|
*
|
|
* This function adds a logout callback association, which allows us to initiate
|
|
* a logout later based on the $assoc-value.
|
|
*
|
|
* Note that logout-associations exists per authentication source. A logout association
|
|
* from one authentication source cannot be called from a different authentication source.
|
|
*
|
|
* @param string $assoc The identifier for this logout association.
|
|
* @param array $state The state array passed to the authenticate-function.
|
|
*/
|
|
protected function addLogoutCallback($assoc, $state)
|
|
{
|
|
assert(is_string($assoc));
|
|
assert(is_array($state));
|
|
|
|
if (!array_key_exists('LogoutCallback', $state)) {
|
|
// the authentication requester doesn't have a logout callback
|
|
return;
|
|
}
|
|
$callback = $state['LogoutCallback'];
|
|
|
|
if (array_key_exists('LogoutCallbackState', $state)) {
|
|
$callbackState = $state['LogoutCallbackState'];
|
|
} else {
|
|
$callbackState = array();
|
|
}
|
|
|
|
$id = strlen($this->authId).':'.$this->authId.$assoc;
|
|
|
|
$data = array(
|
|
'callback' => $callback,
|
|
'state' => $callbackState,
|
|
);
|
|
|
|
$session = SimpleSAML_Session::getSessionFromRequest();
|
|
$session->setData(
|
|
'SimpleSAML_Auth_Source.LogoutCallbacks',
|
|
$id,
|
|
$data,
|
|
SimpleSAML_Session::DATA_TIMEOUT_SESSION_END
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Call a logout callback based on association.
|
|
*
|
|
* This function calls a logout callback based on an association saved with
|
|
* addLogoutCallback(...).
|
|
*
|
|
* This function always returns.
|
|
*
|
|
* @param string $assoc The logout association which should be called.
|
|
*/
|
|
protected function callLogoutCallback($assoc)
|
|
{
|
|
assert(is_string($assoc));
|
|
|
|
$id = strlen($this->authId).':'.$this->authId.$assoc;
|
|
|
|
$session = SimpleSAML_Session::getSessionFromRequest();
|
|
|
|
$data = $session->getData('SimpleSAML_Auth_Source.LogoutCallbacks', $id);
|
|
if ($data === null) {
|
|
// FIXME: fix for IdP-first flow (issue 397) -> reevaluate logout callback infrastructure
|
|
$session->doLogout($this->authId);
|
|
|
|
return;
|
|
}
|
|
|
|
assert(is_array($data));
|
|
assert(array_key_exists('callback', $data));
|
|
assert(array_key_exists('state', $data));
|
|
|
|
$callback = $data['callback'];
|
|
$callbackState = $data['state'];
|
|
|
|
$session->deleteData('SimpleSAML_Auth_Source.LogoutCallbacks', $id);
|
|
call_user_func($callback, $callbackState);
|
|
}
|
|
|
|
|
|
/**
|
|
* Retrieve list of authentication sources.
|
|
*
|
|
* @return array The id of all authentication sources.
|
|
*/
|
|
public static function getSources()
|
|
{
|
|
$config = SimpleSAML_Configuration::getOptionalConfig('authsources.php');
|
|
|
|
return $config->getOptions();
|
|
}
|
|
|
|
|
|
/**
|
|
* Make sure that the first element of an auth source is its identifier.
|
|
*
|
|
* @param array $source An array with the auth source configuration.
|
|
* @param string $id The auth source identifier.
|
|
*
|
|
* @throws Exception If the first element of $source is not an identifier for the auth source.
|
|
*/
|
|
protected static function validateSource($source, $id)
|
|
{
|
|
if (!array_key_exists(0, $source) || !is_string($source[0])) {
|
|
throw new Exception(
|
|
'Invalid authentication source \''.$id.
|
|
'\': First element must be a string which identifies the authentication source.'
|
|
);
|
|
}
|
|
}
|
|
}
|