/lib/SimpleSAML/Auth/State.php
PHP | 349 lines | 131 code | 77 blank | 141 comment | 18 complexity | b74b64784a5742cae3498d7918826bd8 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause, MIT
- <?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 cloned state array which contains the identifier of the
- * original state.
- */
- const CLONE_ORIGINAL_ID = 'SimpleSAML_Auth_State.cloneOriginalId';
- /**
- * 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';
- /**
- * State timeout.
- */
- private static $stateTimeout = NULL;
- /**
- * 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];
- }
- /**
- * Retrieve state timeout.
- *
- * @return integer State timeout.
- */
- private static function getStateTimeout() {
- if (self::$stateTimeout === NULL) {
- $globalConfig = SimpleSAML_Configuration::getInstance();
- self::$stateTimeout = $globalConfig->getInteger('session.state.timeout', 60*60);
- }
- return self::$stateTimeout;
- }
- /**
- * 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, self::getStateTimeout());
- SimpleSAML_Logger::debug('Saved state: ' . var_export($return, TRUE));
- return $return;
- }
- /**
- * Clone the state.
- *
- * This function clones and returns the new cloned state.
- *
- * @param array $state The original request state.
- * @return array Cloned state data.
- */
- public static function cloneState(array $state) {
- $clonedState = $state;
- if (array_key_exists(self::ID, $state)) {
- $clonedState[self::CLONE_ORIGINAL_ID] = $state[self::ID];
- unset($clonedState[self::ID]);
- SimpleSAML_Logger::debug('Cloned state: ' . var_export($state[self::ID], TRUE));
- } else {
- SimpleSAML_Logger::debug('Cloned state with undefined id.');
- }
- return $clonedState;
- }
- /**
- * 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.
- * @param bool $allowMissing Whether to allow the state to be missing.
- * @return array|NULL State information, or NULL if the state is missing and $allowMissing is TRUE.
- */
- public static function loadState($id, $stage, $allowMissing = FALSE) {
- assert('is_string($id)');
- assert('is_string($stage)');
- assert('is_bool($allowMissing)');
- 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. */
- if ($allowMissing) {
- return NULL;
- }
- 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;
- }
- }
- ?>