-
Olav Morken authored
git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@2732 44740490-163a-0410-bde0-09ae8108e29a
cee33e80
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
<?php
/**
* This is a helper class for saving and loading state information.
*
* The state must be an associative array. This class will add additional keys to this
* array. These keys will always start with 'SimpleSAML_Auth_State.'.
*
* It is also possible to add a restart URL to the state. If state information is lost, for
* example because it timed out, or the user loaded a bookmarked page, the loadState function
* will redirect to this URL. To use this, set $state[SimpleSAML_Auth_State::RESTART] to this
* URL.
*
* Both the saveState and the loadState function takes in a $stage parameter. This parameter is
* a security feature, and is used to prevent the user from taking a state saved one place and
* using it as input a different place.
*
* The $stage parameter must be a unique string. To maintain uniqueness, it must be on the form
* "<classname>.<identifier>" or "<module>:<identifier>".
*
* There is also support for passing exceptions through the state.
* By defining an exception handler when creating the state array, users of the state
* array can call throwException with the state and the exception. This exception will
* be passed to the handler defined by the EXCEPTION_HANDLER_URL or EXCEPTION_HANDLER_FUNC
* elements of the state array.
*
* @author Olav Morken, UNINETT AS.
* @package simpleSAMLphp
* @version $Id$
*/
class SimpleSAML_Auth_State {
/**
* The index in the state array which contains the identifier.
*/
const ID = 'SimpleSAML_Auth_State.id';
/**
* The index in the state array which contains the current stage.
*/
const STAGE = 'SimpleSAML_Auth_State.stage';
/**
* The index in the state array which contains the restart URL.
*/
const RESTART = 'SimpleSAML_Auth_State.restartURL';
/**
* The index in the state array which contains the exception handler URL.
*/
const EXCEPTION_HANDLER_URL = 'SimpleSAML_Auth_State.exceptionURL';
/**
* The index in the state array which contains the exception handler function.
*/
const EXCEPTION_HANDLER_FUNC = 'SimpleSAML_Auth_State.exceptionFunc';
/**
* The index in the state array which contains the exception data.
*/
const EXCEPTION_DATA = 'SimpleSAML_Auth_State.exceptionData';
/**
* The stage of a state with an exception.
*/
const EXCEPTION_STAGE = 'SimpleSAML_Auth_State.exceptionStage';
/**
* The URL parameter which contains the exception state id.
*/
const EXCEPTION_PARAM = 'SimpleSAML_Auth_State_exceptionId';
/**
* Retrieve the ID of a state array.
*
* Note that this function will not save the state.
*
* @param array &$state The state array.
* @param bool $rawId Return a raw ID, without a restart URL. Defaults to FALSE.
* @return string Identifier which can be used to retrieve the state later.
*/
public static function getStateId(&$state, $rawId = FALSE) {
assert('is_array($state)');
assert('is_bool($rawId)');
if (!array_key_exists(self::ID, $state)) {
$state[self::ID] = SimpleSAML_Utilities::generateID();
}
$id = $state[self::ID];
if ($rawId || !array_key_exists(self::RESTART, $state)) {
/* Either raw ID or no restart URL. In any case, return the raw ID. */
return $id;
}
/* We have a restart URL. Return the ID with that URL. */
return $id . ':' . $state[self::RESTART];
}
/**
* Save the state.
*
* This function saves the state, and returns an id which can be used to
* retrieve it later. It will also update the $state array with the identifier.
*
* @param array &$state The login request state.
* @param string $stage The current stage in the login process.
* @param bool $rawId Return a raw ID, without a restart URL.
* @return string Identifier which can be used to retrieve the state later.
*/
public static function saveState(&$state, $stage, $rawId = FALSE) {
assert('is_array($state)');
assert('is_string($stage)');
assert('is_bool($rawId)');
$return = self::getStateId($state, $rawId);
$id = $state[self::ID];
/* Save stage. */
$state[self::STAGE] = $stage;
/* Save state. */
$serializedState = serialize($state);
$session = SimpleSAML_Session::getInstance();
$session->setData('SimpleSAML_Auth_State', $id, $serializedState, 60*60);
SimpleSAML_Logger::debug('Saved state: ' . var_export($return, TRUE));
return $return;
}
/**
* Retrieve saved state.
*
* This function retrieves saved state information. If the state information has been lost,
* it will attempt to restart the request by calling the restart URL which is embedded in the
* state information. If there is no restart information available, an exception will be thrown.
*
* @param string $id State identifier (with embedded restart information).
* @param string $stage The stage the state should have been saved in.
* @return array State information.
*/
public static function loadState($id, $stage) {
assert('is_string($id)');
assert('is_string($stage)');
SimpleSAML_Logger::debug('Loading state: ' . var_export($id, TRUE));
$tmp = explode(':', $id, 2);
$id = $tmp[0];
if (count($tmp) === 2) {
$restartURL = $tmp[1];
} else {
$restartURL = NULL;
}
$session = SimpleSAML_Session::getInstance();
$state = $session->getData('SimpleSAML_Auth_State', $id);
if ($state === NULL) {
/* Could not find saved data. Attempt to restart. */
if ($restartURL === NULL) {
throw new SimpleSAML_Error_NoState();
}
SimpleSAML_Utilities::redirect($restartURL);
}
$state = unserialize($state);
assert('is_array($state)');
assert('array_key_exists(self::ID, $state)');
assert('array_key_exists(self::STAGE, $state)');
/* Verify stage. */
if ($state[self::STAGE] !== $stage) {
/* This could be a user trying to bypass security, but most likely it is just
* someone using the back-button in the browser. We try to restart the
* request if that is possible. If not, show an error.
*/
$msg = 'Wrong stage in state. Was \'' . $state[self::STAGE] .
'\', shoud be \'' . $stage . '\'.';
SimpleSAML_Logger::warning($msg);
if ($restartURL === NULL) {
throw new Exception($msg);
}
SimpleSAML_Utilities::redirect($restartURL);
}
return $state;
}
/**
* Delete state.
*
* This function deletes the given state to prevent the user from reusing it later.
*
* @param array &$state The state which should be deleted.
*/
public static function deleteState(&$state) {
assert('is_array($state)');
if (!array_key_exists(self::ID, $state)) {
/* This state hasn't been saved. */
return;
}
SimpleSAML_Logger::debug('Deleting state: ' . var_export($state[self::ID], TRUE));
$session = SimpleSAML_Session::getInstance();
$session->deleteData('SimpleSAML_Auth_State', $state[self::ID]);
}
/**
* Throw exception to the state exception handler.
*
* @param array $state The state array.
* @param SimpleSAML_Error_Exception $exception The exception.
*/
public static function throwException($state, SimpleSAML_Error_Exception $exception) {
assert('is_array($state)');
if (array_key_exists(self::EXCEPTION_HANDLER_URL, $state)) {
/* Save the exception. */
$state[self::EXCEPTION_DATA] = $exception;
$id = self::saveState($state, self::EXCEPTION_STAGE);
/* Redirect to the exception handler. */
SimpleSAML_Utilities::redirect($state[self::EXCEPTION_HANDLER_URL], array(self::EXCEPTION_PARAM => $id));
} elseif (array_key_exists(self::EXCEPTION_HANDLER_FUNC, $state)) {
/* Call the exception handler. */
$func = $state[self::EXCEPTION_HANDLER_FUNC];
assert('is_callable($func)');
call_user_func($func, $exception, $state);
assert(FALSE);
} else {
/*
* No exception handler is defined for the current state.
*/
throw $exception;
}
}
/**
* Retrieve an exception state.
*
* @param string|NULL $id The exception id. Can be NULL, in which case it will be retrieved from the request.
* @return array|NULL The state array with the exception, or NULL if no exception was thrown.
*/
public static function loadExceptionState($id = NULL) {
assert('is_string($id) || is_null($id)');
if ($id === NULL) {
if (!array_key_exists(self::EXCEPTION_PARAM, $_REQUEST)) {
/* No exception. */
return NULL;
}
$id = $_REQUEST[self::EXCEPTION_PARAM];
}
$state = self::loadState($id, self::EXCEPTION_STAGE);
assert('array_key_exists(self::EXCEPTION_DATA, $state)');
return $state;
}
}
?>