Skip to content
Snippets Groups Projects
Session.php 35.6 KiB
Newer Older
 * The Session class holds information about a user session, and everything attached to it.
 * The session will have a duration, and validity, and also cache information about the different
 * federation protocols, as Shibboleth and SAML 2.0. On the IdP side the Session class holds 
 * information about all the currently logged in SPs. This is used when the user initiate a 
 * Single-Log-Out.
 * @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
	/**
	 * This is a timeout value for setData, which indicates that the data should be deleted
	 * on logout.
	 */
	const DATA_TIMEOUT_LOGOUT = 'logoutTimeout';


	/**
	 * This is a timeout value for setData, which indicates that the data
	 * should never be deleted, i.e. lasts the whole session lifetime.
	 */
	const DATA_TIMEOUT_SESSION_END = 'sessionEndTimeout';


	/**
	 * The list of loaded session objects.
	 *
	 * This is an associative array indexed with the session id.
	 *
	 * @var array
	 */
	private static $sessions = array();


	/**
	 * This variable holds the instance of the session - Singleton approach.
	 */
	private static $instance = null;

	/**
	 * The session ID of this session.
	 *
	 * @var string|NULL
	 */
	private $sessionId;


	/**
	 * Transient session flag.
	 *
	 * @var boolean|FALSE
	 */
	private $transient = FALSE;


	/**
	 * The track id is a new random unique identifier that is generate for each session.
	 * This is used in the debug logs and error messages to easily track more information
	 * about what went wrong.
	 */
	private $trackid = 0;
	
	private $authenticated = null;
	private $attributes = null;
	
	private $sessionindex = null;
	private $nameid = null;
	
	// Session duration parameters
	private $sessionstarted = null;
	private $sessionduration = null;

	private $rememberMeExpire = null;

	// Track whether the session object is modified or not.
	/**
	 * This is an array of registered logout handlers.
	 * All registered logout handlers will be called on logout.
	 */
	private $logout_handlers = array();


	 * This is an array of objects which will autoexpire after a set time. It is used
	 * where one needs to store some information - for example a logout request, but doesn't
	 * want it to be stored forever.
	 *
	 * The data store contains three levels of nested associative arrays. The first is the data type, the
	 * second is the identifier, and the third contains the expire time of the data and the data itself.
	 */
	private $dataStore = null;


	/**
	 * Current NameIDs for sessions.
	 *
	 * Stored as a two-level associative array: $sessionNameId[<entityType>][<entityId>]
	 */
	private $sessionNameId;


	/**
	 * Logout state when authenticated with authentication sources.
	 */
	private $logoutState;


	/**
	 * Persistent authentication state.
	 *
	 * @array
	 */
	private $authState;


	/**
	 * The list of IdP-SP associations.
	 *
	 * This is an associative array with the IdP id as the key, and the list of
	 * associations as the value.
	 *
	 * @var array
	 */
	private $associations = array();


	/**
	 * The authentication token.
	 *
	 * This token is used to prevent session fixation attacks.
	 *
	 * @var string|NULL
	 */
	private $authToken;


	/**
	 * Authentication data.
	 *
	 * This is an array with authentication data for the various authsources.
	 *
	 * @var array|NULL  Associative array of associative arrays.
	 */
	private $authData;


	 * private constructor restricts instantiaton to getInstance()
	private function __construct($transient = FALSE) {

		$this->authData = array();

		if ($transient) {
			$this->trackid = 'XXXXXXXXXX';
			$this->transient = TRUE;
		$sh = SimpleSAML_SessionHandler::getSessionHandler();
		$this->sessionId = $sh->newSessionId();
		$this->trackid = substr(md5(uniqid(rand(), true)), 0, 10);

		/* Initialize data for session check function if defined */
		$globalConfig = SimpleSAML_Configuration::getInstance();
		$checkFunction = $globalConfig->getArray('session.check_function', NULL);
		if (isset($checkFunction)) {
			assert('is_callable($checkFunction)');
			call_user_func($checkFunction, $this, TRUE);
		}
    /**
     * Destructor for this class. It will save the session to the session handler
     * in case the session has been marked as dirty. Do nothing otherwise.
     */
    public function __destruct() {
        if(!$this->dirty) {
            /* Session hasn't changed - don't bother saving it. */
            return;
        }

        $this->dirty = FALSE;

        $sh = SimpleSAML_SessionHandler::getSessionHandler();

        try {
            $sh->saveSession($this);
        } catch (Exception $e) {
            if (!($e instanceof SimpleSAML_Error_Exception)) {
                $e = new SimpleSAML_Error_UnserializableException($e);
            }
            SimpleSAML_Logger::error('Unable to save session.');
            $e->logError();
        }
    }


	/**
	 * Upgrade this session object to use the $authData property.
	 *
	 * TODO: Remove in version 1.8.
	 */
	private function upgradeAuthData() {
		$this->authData = array();

		if ($this->authority === NULL || !$this->authenticated) {
			return;
		}

		if ($this->authState !== NULL) {
			$data = $this->authState;
		} else {
			$data = array();
		}

		if ($this->attributes !== NULL) {
			$data['Attributes'] = $this->attributes;
		} else {
			$data['Attributes'] = array();
		}

		if ($this->idp !== NULL) {
			$data['saml:sp:IdP'] = $this->idp;
		}

		if ($this->sessionindex !== NULL) {
			$data['saml:sp:SessionIndex'] = $this->sessionindex;
		}

		if ($this->nameid !== NULL) {
			$data['saml:sp:NameID'] = $this->nameid;
		}

		$data['AuthnInstant'] = $this->sessionstarted;
		$data['Expire'] = $this->sessionstarted + $this->sessionduration;
		$this->sessionstarted = NULL;
		$this->sessionduration = NULL;

		if ($this->logoutState !== NULL) {
			$data['LogoutState'] = $this->logoutState;
		}


		if (!empty($this->logout_handlers)) {
			$data['LogoutHandlers'] = $this->logout_handlers;
		}

		$this->authData[$this->authority] = $data;
	}


	 * This function is called after this class has been deserialized.
	 */
	public function __wakeup() {

		/* TODO: Remove for version 1.8. */
		if ($this->authData === NULL) {
			$this->upgradeAuthData();
		}
	}


	/**
	 * Retrieves the current session. Will create a new session if there isn't a session.
	public static function getInstance() {

		/* Check if we already have initialized the session. */
		if (isset(self::$instance)) {
			return self::$instance;
		}


		/* Check if we have stored a session stored with the session
		 * handler.
		 */
			self::$instance = self::getSession();
			/* For some reason, we were unable to initialize this session. Use a transient session instead. */
			self::useTransientSession();

			$globalConfig = SimpleSAML_Configuration::getInstance();
			if ($globalConfig->getBoolean('session.disable_fallback', FALSE) === TRUE) {
				throw $e;
			}

			if ($e instanceof SimpleSAML_Error_Exception) {
				SimpleSAML_Logger::error('Error loading session:');
				$e->logError();
			} else {
				SimpleSAML_Logger::error('Error loading session: ' . $e->getMessage());
			}
		if(self::$instance !== NULL) {
			return self::$instance;
		}

		/* Create a new session. */
		self::$instance = new SimpleSAML_Session();
		return self::$instance;
	/**
	 * Use a transient session.
	 *
	 * Create a session that should not be saved at the end of the request.
	 * Subsequent calls to getInstance() will return this transient session.
	 */
	public static function useTransientSession() {

		if (isset(self::$instance)) {
			/* We already have a session. Don't bother with a transient session. */
			return;
		}

		self::$instance = new SimpleSAML_Session(TRUE);
	}


	/**
	 * Retrieve the session ID of this session.
	 *
	 * @return string|NULL  The session ID, or NULL if this is a transient session.
	 */
	public function getSessionId() {

		return $this->sessionId;
	}


	/**
	 * Retrieve if session is transient.
	 *
	 * @return boolean  The session transient flag.
	 */
	public function isTransient() {
		return $this->transient;
	}


	/**
	 * Get a unique ID that will be permanent for this session.
	 * Used for debugging and tracing log files related to a session.
	 */
	public function getTrackID() {
		return $this->trackid;
	 * Who authorized this session. could be in example saml2, shib13, login,login-admin etc.
	 */
	public function getAuthority() {
		return $this->authority;
	}
	/**
	 * This method retrieves from session a cache of a specific Authentication Request
	 * The complete request is not stored, instead the values that will be needed later
	 * are stored in an assoc array.
	 *
	 * @param $protocol 		saml2 or shib13
	 * @param $requestid 		The request id used as a key to lookup the cache.
	 * @return Returns an assoc array of cached variables associated with the
	 * authentication request.
	 */
	public function getAuthnRequest($protocol, $requestid) {
		SimpleSAML_Logger::debug('Library - Session: Get authnrequest from cache ' . $protocol . ' time:' . time() . '  id: '. $requestid );
		$type = 'AuthnRequest-' . $protocol;
		$authnRequest = $this->getData($type, $requestid);

		if($authnRequest === NULL) {
			 * Could not find requested ID. Throw an error. Could be that it is never set, or that it is deleted due to age.
			throw new Exception('Could not find cached version of authentication request with ID ' . $requestid . ' (' . $protocol . ')');
	/**
	 * This method sets a cached assoc array to the authentication request cache storage.
	 *
	 * @param $protocol 		saml2 or shib13
	 * @param $requestid 		The request id used as a key to lookup the cache.
	 * @param $cache			The assoc array that will be stored.
	 */
	public function setAuthnRequest($protocol, $requestid, array $cache) {
		SimpleSAML_Logger::debug('Library - Session: Set authnrequest ' . $protocol . ' time:' . time() . ' size:' . count($cache) . '  id: '. $requestid );
		$type = 'AuthnRequest-' . $protocol;
		$this->setData($type, $requestid, $cache);
Olav Morken's avatar
Olav Morken committed
	/**
	 * Set the IdP we are authenticated against.
	 *
	 * @param string|NULL $idp  Our current IdP, or NULL if we aren't authenticated with an IdP.
	public function setIdP($idp) {
Olav Morken's avatar
Olav Morken committed
		assert('is_string($idp) || is_null($idp)');
		assert('isset($this->authData[$this->authority])');
		SimpleSAML_Logger::debug('Library - Session: Set IdP to : ' . $idp);
		if ($idp !== NULL) {
			$this->authData[$this->authority]['saml:sp:IdP'] = $idp;
		} else {
			unset($this->authData[$this->authority]['saml:sp:IdP']);
		}

Olav Morken's avatar
Olav Morken committed


	/**
	 * Retrieve the IdP we are currently authenticated against.
	 *
	 * @return string|NULL  Our current IdP, or NULL if we aren't authenticated with an IdP.
		if (!isset($this->authData[$this->authority]['saml:sp:IdP'])) {
			return NULL;
		}
		return $this->authData[$this->authority]['saml:sp:IdP'];
Olav Morken's avatar
Olav Morken committed

	/**
	 * Set the SessionIndex we received from our IdP.
	 *
	 * @param string|NULL $sessionindex  Our SessionIndex.
	public function setSessionIndex($sessionindex) {
Olav Morken's avatar
Olav Morken committed
		assert('is_string($sessionindex) || is_null($sessionindex)');
		assert('isset($this->authData[$this->authority])');
		SimpleSAML_Logger::debug('Library - Session: Set sessionindex: ' . $sessionindex);
		if ($sessionindex !== NULL) {
			$this->authData[$this->authority]['saml:sp:SessionIndex'] = $sessionindex;
		} else {
			unset($this->authData[$this->authority]['saml:sp:SessionIndex']);
		}
Olav Morken's avatar
Olav Morken committed


	/**
	 * Retrieve our SessionIndex.
	 *
	 * @return string|NULL  Our SessionIndex.
	public function getSessionIndex() {
		if (!isset($this->authData[$this->authority]['saml:sp:SessionIndex'])) {
			return NULL;
		}
		return $this->authData[$this->authority]['saml:sp:SessionIndex'];
Olav Morken's avatar
Olav Morken committed


	/**
	 * Set our current NameID.
	 *
	 * @param array|NULL $nameid  The NameID we received from the IdP
	public function setNameID($nameid) {
Olav Morken's avatar
Olav Morken committed
		assert('is_array($nameid) || is_null($nameid)');
		assert('isset($this->authData[$this->authority])');
		SimpleSAML_Logger::debug('Library - Session: Set nameID: ');
		if ($nameid !== NULL) {
			$this->authData[$this->authority]['saml:sp:NameID'] = $nameid;
		} else {
			unset($this->authData[$this->authority]['saml:sp:NameID']);
		}
Olav Morken's avatar
Olav Morken committed


	/**
	 * Get our NameID.
	 *
	 * @return array|NULL The NameID we received from the IdP.
	 */
	public function getNameID() {
		if (!isset($this->authData[$this->authority]['saml:sp:NameID'])) {
			return NULL;
		}
		return $this->authData[$this->authority]['saml:sp:NameID'];
	/**
	 * Set remember me expire time.
	 *
	 * @param int $expire  Unix timestamp when remember me session cookies expire.
	 */
	public function setRememberMeExpire($expire = NULL) {
		assert('is_int($expire) || is_null($expire)');

		if ($expire === NULL) {
			$globalConfig = SimpleSAML_Configuration::getInstance();
			$expire = time() + $globalConfig->getInteger('session.rememberme.lifetime', 14*86400);
		}
		$this->rememberMeExpire = $expire;

		$cookieParams = array('expire' => $this->rememberMeExpire);
		$this->updateSessionCookies($cookieParams);
	}


	/**
	 * Get remember me expire time.
	 *
	 * @return integer|NULL The remember me expire time.
	 */
	public function getRememberMeExpire() {
		return $this->rememberMeExpire;
	}


	/**
	 * Update session cookies.
	 */
	public function updateSessionCookies($params = NULL) {
		$sessionHandler = SimpleSAML_SessionHandler::getSessionHandler();

		if ($this->sessionId !== NULL) {
			$sessionHandler->setCookie($sessionHandler->getSessionCookieName(), $this->sessionId, $params);
		}

		if ($this->authToken !== NULL) {
			$globalConfig = SimpleSAML_Configuration::getInstance();
			$sessionHandler->setCookie($globalConfig->getString('session.authtoken.cookiename', 'SimpleSAMLAuthToken'), $this->authToken, $params);
	/**
	 * Marks the user as logged in with the specified authority.
	 *
	 * If the user already has logged in, the user will be logged out first.
	 *
	 * @param string $authority  The authority the user logged in with.
	 * @param array|NULL $data  The authentication data for this authority.
	public function doLogin($authority, array $data = NULL) {
		assert('is_array($data) || is_null($data)');

		SimpleSAML_Logger::debug('Session: doLogin("' . $authority . '")');

		$this->dirty = TRUE;

		if (isset($this->authData[$authority])) {
			/* We are already logged in. Log the user out first. */
			$this->doLogout($authority);
		if ($data === NULL) {
			$data = array();
		}

		$data['Authority'] = $authority;

		$globalConfig = SimpleSAML_Configuration::getInstance();
		if (!isset($data['AuthnInstant'])) {
			$data['AuthnInstant'] = time();
		}

		$maxSessionExpire = time() + $globalConfig->getInteger('session.duration', 8*60*60);
		if (!isset($data['Expire']) || $data['Expire'] > $maxSessionExpire) {
			/* Unset, or beyond our session lifetime. Clamp it to our maximum session lifetime. */
			$data['Expire'] = $maxSessionExpire;
		}

		$this->authData[$authority] = $data;
		$this->authority = $authority;

		$this->authToken = SimpleSAML_Utilities::generateID();
		$sessionHandler = SimpleSAML_SessionHandler::getSessionHandler();
		if (!$this->transient && (!empty($data['RememberMe']) || $this->rememberMeExpire) && $globalConfig->getBoolean('session.rememberme.enable', FALSE)) {
			$this->setRememberMeExpire();
			$sessionHandler->setCookie($globalConfig->getString('session.authtoken.cookiename', 'SimpleSAMLAuthToken'), $this->authToken);
	}


	/**
	 * Marks the user as logged out.
	 *
	 * This function will call any registered logout handlers before marking the user as logged out.
	 * @param string|NULL $authority  The authentication source we are logging out of.
	public function doLogout($authority = NULL) {
		SimpleSAML_Logger::debug('Session: doLogout(' . var_export($authority, TRUE) . ')');
		if ($authority === NULL) {
			if ($this->authority === NULL) {
				SimpleSAML_Logger::debug('Session: No current authsource - not logging out.');
				return;
			}
			$authority = $this->authority;
		}
		if (!isset($this->authData[$authority])) {
			SimpleSAML_Logger::debug('Session: Already logged out of ' . $authority . '.');
			return;
		}

		$this->dirty = TRUE;
		$this->callLogoutHandlers($authority);
		unset($this->authData[$authority]);
		if ($this->authority === $authority) {
			$this->authority = NULL;
		}
		if ($this->authority === NULL && $this->rememberMeExpire) {
			$this->rememberMeExpire = NULL;
			$this->updateSessionCookies();
		}

		/* Delete data which expires on logout. */
		$this->expireDataLogout();
	/**
	 * Set the lifetime for authentication source.
	 *
	 * @param string $authority  The authentication source we are setting expire time for.
	 * @param int $expire  The number of seconds authentication source is valid.
	 */
	public function setAuthorityExpire($authority, $expire = NULL) {
		assert('isset($this->authData[$authority])');
		assert('is_int($expire) || is_null($expire)');

		$this->dirty = true;

		if ($expire === NULL) {
			$globalConfig = SimpleSAML_Configuration::getInstance();
			$expire = time() + $globalConfig->getInteger('session.duration', 8*60*60);
		}

		$this->authData[$authority]['Expire'] = $expire;
	}


Olav Morken's avatar
Olav Morken committed
	/**
	 * Set the lifetime of our current authentication session.
	 *
	 * @param int $duration  The number of seconds this authentication session is valid.
	public function setSessionDuration($duration) {
Olav Morken's avatar
Olav Morken committed
		assert('is_int($duration)');
		assert('isset($this->authData[$this->authority])');
		SimpleSAML_Logger::debug('Library - Session: Set session duration ' . $duration);
		$this->sessionduration = $duration;

		$this->authData[$this->authority]['Expire'] = time() + $duration;
	 * Is the session representing an authenticated user, and is the session still alive.
	 * This function will return false after the user has timed out.
	 *
	 * @param string $authority  The authentication source that the user should be authenticated with.
	 * @return TRUE if the user has a valid session, FALSE if not.
	public function isValid($authority) {
		assert('is_string($authority)');

		if (!isset($this->authData[$authority])) {
			SimpleSAML_Logger::debug('Session: '. var_export($authority, TRUE) .' not valid because we are not authenticated.');
		if ($this->authData[$authority]['Expire'] <= time()) {
			SimpleSAML_Logger::debug('Session: ' . var_export($authority, TRUE) .' not valid because it is expired.');
		SimpleSAML_Logger::debug('Session: Valid session found with ' . var_export($authority, TRUE) . '.');

		return TRUE;
	 * If the user is authenticated, how much time is left of the session.
	 * @return int  The number of seconds until the session expires.
	 */
	public function remainingTime() {

		if (!isset($this->authData[$this->authority])) {
			/* Not authenticated. */
			return -1;
		}

		assert('isset($this->authData[$this->authority]["Expire"])');
		return $this->authData[$this->authority]['Expire'] - time();
Olav Morken's avatar
Olav Morken committed
	/**
	 * Is the user authenticated. This function does not check the session duration.
	 * @return bool  TRUE if the user is authenticated, FALSE otherwise.
	 */
	public function isAuthenticated() {
		return isset($this->authData[$this->authority]);


	/**
	 * Retrieve the time the user was authenticated.
	 *
	 * @return int|NULL  The timestamp for when the user was authenticated. NULL if the user hasn't authenticated.
	 */
	public function getAuthnInstant() {

		if (!isset($this->authData[$this->authority])) {
			/* Not authenticated. */
		assert('isset($this->authData[$this->authority]["AuthnInstant"])');
		return $this->authData[$this->authority]['AuthnInstant'];
Olav Morken's avatar
Olav Morken committed


	/**
	 * Retrieve the attributes associated with this session.
	 *
	 * @return array|NULL  The attributes.
	 */
	public function getAttributes() {
		if (!isset($this->authData[$this->authority]['Attributes'])) {
			return NULL;
		}
		return $this->authData[$this->authority]['Attributes'];
Olav Morken's avatar
Olav Morken committed

	/**
	 * Retrieve a single attribute.
	 *
	 * @param string $name  The name of the attribute.
	 * @return array|NULL  The values of the given attribute.
	 */
	public function getAttribute($name) {
		if (!isset($this->authData[$this->authority]['Attributes'][$name])) {
			return NULL;
		}
		return $this->authData[$this->authority]['Attributes'][$name];
Olav Morken's avatar
Olav Morken committed

	/**
	 * Set the attributes for this session.
	 *
	 * @param array|NULL $attributes  The attributes of this session.
	 */
	public function setAttributes($attributes) {
		assert('isset($this->authData[$this->authority])');

		$this->authData[$this->authority]['Attributes'] = $attributes;
Olav Morken's avatar
Olav Morken committed


	/**
	 * Set the values of a single attribute.
	 *
	 * @param string $name  The name of the attribute.
	 * @param array $value  The values of the attribute.
	public function setAttribute($name, $value) {
		assert('isset($this->authData[$this->authority])');

		$this->authData[$this->authority]['Attributes'][$name] = $value;
	/**
	 * Calculates the size of the session object after serialization
	 *
	 * @return The size of the session measured in bytes.
	public function getSize() {
		$s = serialize($this);
		return strlen($s);
	}


	/**
	 * This function registers a logout handler.
	 *
	 * @param $classname  The class which contains the logout handler.
	 * @param $functionname  The logout handler function.
	public function registerLogoutHandler($classname, $functionname) {
		assert('isset($this->authData[$this->authority])');

		$logout_handler = array($classname, $functionname);

		if(!is_callable($logout_handler)) {
			throw new Exception('Logout handler is not a vaild function: ' . $classname . '::' .
				$functionname);
		}


		$this->authData[$this->authority]['LogoutHandlers'][] = $logout_handler;
	}


	/**
	 * This function calls all registered logout handlers.
	 * @param string $authority  The authentication source we are logging out from.
	private function callLogoutHandlers($authority) {
		assert('is_string($authority)');
		assert('isset($this->authData[$authority])');

		if (empty($this->authData[$authority]['LogoutHandlers'])) {
			return;
		}
		foreach($this->authData[$authority]['LogoutHandlers'] as $handler) {
			/* Verify that the logout handler is a valid function. */
			if(!is_callable($handler)) {
				$classname = $handler[0];
				$functionname = $handler[1];

				throw new Exception('Logout handler is not a vaild function: ' . $classname . '::' .
					$functionname);
			}

			/* Call the logout handler. */
			call_user_func($handler);
		}

		/* We require the logout handlers to register themselves again if they want to be called later. */
		unset($this->authData[$authority]['LogoutHandlers']);
	/**
	 * This function removes expired data from the data store.
	 *
	 * Note that this function doesn't mark the session object as dirty. This means that
	 * if the only change to the session object is that some data has expired, it will not be
	 * written back to the session store.
		$ct = time();

		foreach($this->dataStore as &$typedData) {
			foreach($typedData as $id => $info) {
				if ($info['expires'] === self::DATA_TIMEOUT_LOGOUT) {
					/* This data only expires on logout. */
					continue;
				}

				if ($info['expires'] === self::DATA_TIMEOUT_SESSION_END) {
					/* This data never expires. */
					continue;
				}

	/**
	 * This function deletes data which should be deleted on logout from the data store.
	 */
	private function expireDataLogout() {

		if(!is_array($this->dataStore)) {
			return;
		}

		$this->dirty = TRUE;

		foreach ($this->dataStore as &$typedData) {
			foreach ($typedData as $id => $info) {
				if ($info['expires'] === self::DATA_TIMEOUT_LOGOUT) {
					unset($typedData[$id]);
				}
			}
		}
	}


	/**
	 * Delete data from the data store.
	 *
	 * This function immediately deletes the data with the given type and id from the data store.
	 *
	 * @param string $type  The type of the data.
	 * @param string $id  The identifier of the data.
	 */
	public function deleteData($type, $id) {
		assert('is_string($type)');
		assert('is_string($id)');

		if (!is_array($this->dataStore)) {
			return;
		}

		if(!array_key_exists($type, $this->dataStore)) {
			return;
		}

		unset($this->dataStore[$type][$id]);
		$this->dirty = TRUE;
	}


	/**
	 * This function stores data in the data store.
	 *
	 * The timeout value can be SimpleSAML_Session::DATA_TIMEOUT_LOGOUT, which indicates
	 * that the data should be deleted on logout (and not before).
	 *
	 * @param $type     The type of the data. This is checked when retrieving data from the store.
	 * @param $id       The identifier of the data.
	 * @param $data     The data.
	 * @param $timeout  The number of seconds this data should be stored after its last access.
	 *                  This parameter is optional. The default value is set in 'session.datastore.timeout',
	 *                  and the default is 4 hours.
	public function setData($type, $id, $data, $timeout = NULL) {
		assert('is_string($type)');
		assert('is_string($id)');
		assert('is_int($timeout) || is_null($timeout) || $timeout === self::DATA_TIMEOUT_LOGOUT || $timeout === self::DATA_TIMEOUT_SESSION_END');
		if($timeout === NULL) {
			/* Use the default timeout. */