From edd1709ebfa651ed2b20ec4f53beeb6d915710ba Mon Sep 17 00:00:00 2001
From: Olav Morken <olav.morken@uninett.no>
Date: Tue, 16 Nov 2010 14:30:23 +0000
Subject: [PATCH] Session: Add support for multiple authentication sessions.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@2636 44740490-163a-0410-bde0-09ae8108e29a
---
 lib/SimpleSAML/Session.php | 286 +++++++++++++++++++++++++++++--------
 1 file changed, 227 insertions(+), 59 deletions(-)

diff --git a/lib/SimpleSAML/Session.php b/lib/SimpleSAML/Session.php
index 6720e7f7d..79ecd025c 100644
--- a/lib/SimpleSAML/Session.php
+++ b/lib/SimpleSAML/Session.php
@@ -131,15 +131,22 @@ class SimpleSAML_Session {
 	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) {
 
-		
-		$configuration = SimpleSAML_Configuration::getInstance();
-		$this->sessionduration = $configuration->getInteger('session.duration', 8*60*60);
-		
+		$this->authData = array();
 
 		if ($transient) {
 			$this->trackid = 'XXXXXXXXXX';
@@ -156,11 +163,70 @@ class SimpleSAML_Session {
 	}
 
 
+	/**
+	 * 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() {
 		$this->addShutdownFunction();
+
+		/* TODO: Remove for version 1.8. */
+		if ($this->authData === NULL) {
+			$this->upgradeAuthData();
+		}
 	}
 
 
@@ -303,10 +369,16 @@ class SimpleSAML_Session {
 	 */
 	public function setIdP($idp) {
 		assert('is_string($idp) || is_null($idp)');
+		assert('isset($this->authData[$this->authority])');
 
 		SimpleSAML_Logger::debug('Library - Session: Set IdP to : ' . $idp);
 		$this->dirty = true;
-		$this->idp = $idp;
+		if ($idp !== NULL) {
+			$this->authData[$this->authority]['saml:sp:IdP'] = $idp;
+		} else {
+			unset($this->authData[$this->authority]['saml:sp:IdP']);
+		}
+
 	}
 
 
@@ -316,7 +388,10 @@ class SimpleSAML_Session {
 	 * @return string|NULL  Our current IdP, or NULL if we aren't authenticated with an IdP.
 	 */
 	public function getIdP() {
-		return $this->idp;
+		if (!isset($this->authData[$this->authority]['saml:sp:IdP'])) {
+			return NULL;
+		}
+		return $this->authData[$this->authority]['saml:sp:IdP'];
 	}
 
 
@@ -327,10 +402,15 @@ class SimpleSAML_Session {
 	 */
 	public function setSessionIndex($sessionindex) {
 		assert('is_string($sessionindex) || is_null($sessionindex)');
+		assert('isset($this->authData[$this->authority])');
 
 		SimpleSAML_Logger::debug('Library - Session: Set sessionindex: ' . $sessionindex);
 		$this->dirty = true;
-		$this->sessionindex = $sessionindex;
+		if ($sessionindex !== NULL) {
+			$this->authData[$this->authority]['saml:sp:SessionIndex'] = $sessionindex;
+		} else {
+			unset($this->authData[$this->authority]['saml:sp:SessionIndex']);
+		}
 	}
 
 
@@ -340,7 +420,10 @@ class SimpleSAML_Session {
 	 * @return string|NULL  Our SessionIndex.
 	 */
 	public function getSessionIndex() {
-		return $this->sessionindex;
+		if (!isset($this->authData[$this->authority]['saml:sp:SessionIndex'])) {
+			return NULL;
+		}
+		return $this->authData[$this->authority]['saml:sp:SessionIndex'];
 	}
 
 
@@ -351,10 +434,15 @@ class SimpleSAML_Session {
 	 */
 	public function setNameID($nameid) {
 		assert('is_array($nameid) || is_null($nameid)');
+		assert('isset($this->authData[$this->authority])');
 
 		SimpleSAML_Logger::debug('Library - Session: Set nameID: ');
 		$this->dirty = true;
-		$this->nameid = $nameid;
+		if ($nameid !== NULL) {
+			$this->authData[$this->authority]['saml:sp:NameID'] = $nameid;
+		} else {
+			unset($this->authData[$this->authority]['saml:sp:NameID']);
+		}
 	}
 
 
@@ -364,7 +452,10 @@ class SimpleSAML_Session {
 	 * @return array|NULL The NameID we received from the IdP.
 	 */
 	public function getNameID() {
-		return $this->nameid;
+		if (!isset($this->authData[$this->authority]['saml:sp:NameID'])) {
+			return NULL;
+		}
+		return $this->authData[$this->authority]['saml:sp:NameID'];
 	}
 
 
@@ -374,25 +465,36 @@ class SimpleSAML_Session {
 	 * 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 $authState  The persistent auth state for this authority.
+	 * @param array|NULL $data  The authentication data for this authority.
 	 */
-	public function doLogin($authority, array $authState = NULL) {
+	public function doLogin($authority, array $data = NULL) {
 		assert('is_string($authority)');
+		assert('is_array($data) || is_null($data)');
 
 		SimpleSAML_Logger::debug('Session: doLogin("' . $authority . '")');
 
 		$this->dirty = TRUE;
 
-		if($this->authenticated) {
+		if (isset($this->authData[$authority])) {
 			/* We are already logged in. Log the user out first. */
-			$this->doLogout();
+			$this->doLogout($authority);
 		}
 
-		$this->authenticated = TRUE;
-		$this->authority = $authority;
-		$this->authState = $authState;
 
-		$this->sessionstarted = time();
+		if ($data === NULL) {
+			$data = array();
+		}
+
+		if (!isset($data['AuthnInstant'])) {
+			$data['AuthnInstant'] = time();
+		}
+		if (!isset($data['Expire'])) {
+			$globalConfig = SimpleSAML_Configuration::getInstance();
+			$data['Expire'] = time() + $globalConfig->getInteger('session.duration', 8*60*60);
+		}
+
+		$this->authData[$authority] = $data;
+		$this->authority = $authority;
 
 		$this->authToken = SimpleSAML_Utilities::generateID();
 		$sessionHandler = SimpleSAML_SessionHandler::getSessionHandler();
@@ -404,21 +506,33 @@ class SimpleSAML_Session {
 	 * 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() {
+	public function doLogout($authority = NULL) {
 
-		SimpleSAML_Logger::debug('Session: doLogout()');
+		SimpleSAML_Logger::debug('Session: doLogout(' . var_export($authority, TRUE) . ')');
 
-		$this->dirty = TRUE;
+		if ($authority === NULL) {
+			if ($this->authority === NULL) {
+				SimpleSAML_Logger::debug('Session: No current authsource - not logging out.');
+				return;
+			}
+			$authority = $this->authority;
+		}
 
-		$this->callLogoutHandlers();
+		if (!isset($this->authData[$authority])) {
+			SimpleSAML_Logger::debug('Session: Already logged out of ' . $authority . '.');
+			return;
+		}
+
+		$this->dirty = TRUE;
 
-		$this->authenticated = FALSE;
-		$this->authority = NULL;
-		$this->attributes = NULL;
-		$this->logoutState = NULL;
-		$this->authState = NULL;
-		$this->idp = NULL;
+		$this->callLogoutHandlers($authority);
+		unset($this->authData[$authority]);
+		if ($this->authority === $authority) {
+			$this->authority = NULL;
+		}
 
 		/* Delete data which expires on logout. */
 		$this->expireDataLogout();
@@ -432,10 +546,13 @@ class SimpleSAML_Session {
 	 */
 	public function setSessionDuration($duration) {
 		assert('is_int($duration)');
+		assert('isset($this->authData[$this->authority])');
 
 		SimpleSAML_Logger::debug('Library - Session: Set session duration ' . $duration);
 		$this->dirty = true;
 		$this->sessionduration = $duration;
+
+		$this->authData[$this->authority]['Expire'] = time() + $duration;
 	}
 
 
@@ -449,19 +566,19 @@ class SimpleSAML_Session {
 	public function isValid($authority) {
 		assert('is_string($authority)');
 
-		SimpleSAML_Logger::debug('Library - Session: Check if session is valid.' .
-			' checkauthority:' . $authority .
-			' thisauthority:' . (isset($this->authority) ? $this->authority : 'null') .
-			' isauthenticated:' . ($this->isAuthenticated() ? 'yes' : 'no') . 
-			' remainingtime:' . $this->remainingTime());
-			
-		if (!$this->isAuthenticated()) return false;
+		if (!isset($this->authData[$authority])) {
+			SimpleSAML_Logger::debug('Session: '. var_export($authority, TRUE) .' not valid because we are not authenticated.');
+			return FALSE;
+		}
 
-		if ($authority !== $this->authority) {
+		if ($this->authData[$authority]['Expire'] <= time()) {
+			SimpleSAML_Logger::debug('Session: ' . var_export($authority, TRUE) .' not valid because it is expired.');
 			return FALSE;
 		}
 
-		return $this->remainingTime() > 0;
+		SimpleSAML_Logger::debug('Session: Valid session found with ' . var_export($authority, TRUE) . '.');
+
+		return TRUE;
 	}
 
 
@@ -471,7 +588,14 @@ class SimpleSAML_Session {
 	 * @return int  The number of seconds until the session expires.
 	 */
 	public function remainingTime() {
-		return $this->sessionduration - (time() - $this->sessionstarted);
+
+		if (!isset($this->authData[$this->authority])) {
+			/* Not authenticated. */
+			return -1;
+		}
+
+		assert('isset($this->authData[$this->authority]["Expire"])');
+		return $this->authData[$this->authority]['Expire'] - time();
 	}
 
 	/**
@@ -480,7 +604,7 @@ class SimpleSAML_Session {
 	 * @return bool  TRUE if the user is authenticated, FALSE otherwise.
 	 */
 	public function isAuthenticated() {
-		return $this->authenticated;
+		return isset($this->authData[$this->authority]);
 	}
 
 
@@ -490,11 +614,14 @@ class SimpleSAML_Session {
 	 * @return int|NULL  The timestamp for when the user was authenticated. NULL if the user hasn't authenticated.
 	 */
 	public function getAuthnInstant() {
-		if (!$this->isAuthenticated()) {
+
+		if (!isset($this->authData[$this->authority])) {
+			/* Not authenticated. */
 			return NULL;
 		}
 
-		return $this->sessionstarted;
+		assert('isset($this->authData[$this->authority]["AuthnInstant"])');
+		return $this->authData[$this->authority]['AuthnInstant'];
 	}
 
 
@@ -504,7 +631,10 @@ class SimpleSAML_Session {
 	 * @return array|NULL  The attributes.
 	 */
 	public function getAttributes() {
-		return $this->attributes;
+		if (!isset($this->authData[$this->authority]['Attributes'])) {
+			return NULL;
+		}
+		return $this->authData[$this->authority]['Attributes'];
 	}
 
 
@@ -515,7 +645,10 @@ class SimpleSAML_Session {
 	 * @return array|NULL  The values of the given attribute.
 	 */
 	public function getAttribute($name) {
-		return $this->attributes[$name];
+		if (!isset($this->authData[$this->authority]['Attributes'][$name])) {
+			return NULL;
+		}
+		return $this->authData[$this->authority]['Attributes'][$name];
 	}
 
 
@@ -525,8 +658,10 @@ class SimpleSAML_Session {
 	 * @param array|NULL $attributes  The attributes of this session.
 	 */
 	public function setAttributes($attributes) {
+		assert('isset($this->authData[$this->authority])');
+
 		$this->dirty = true;
-		$this->attributes = $attributes;
+		$this->authData[$this->authority]['Attributes'] = $attributes;
 	}
 
 
@@ -537,8 +672,10 @@ class SimpleSAML_Session {
 	 * @param array $value  The values of the attribute.
 	 */
 	public function setAttribute($name, $value) {
+		assert('isset($this->authData[$this->authority])');
+
 		$this->dirty = true;
-		$this->attributes[$name] = $value;
+		$this->authData[$this->authority]['Attributes'][$name] = $value;
 	}
 
 
@@ -560,6 +697,7 @@ class SimpleSAML_Session {
 	 * @param $functionname  The logout handler function.
 	 */
 	public function registerLogoutHandler($classname, $functionname) {
+		assert('isset($this->authData[$this->authority])');
 
 		$logout_handler = array($classname, $functionname);
 
@@ -569,16 +707,24 @@ class SimpleSAML_Session {
 		}
 
 
-		$this->logout_handlers[] = $logout_handler;
+		$this->authData[$this->authority]['LogoutHandlers'][] = $logout_handler;
 		$this->dirty = TRUE;
 	}
 
 
 	/**
 	 * This function calls all registered logout handlers.
+	 *
+	 * @param string $authority  The authentication source we are logging out from.
 	 */
-	private function callLogoutHandlers() {
-		foreach($this->logout_handlers as $handler) {
+	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)) {
@@ -595,7 +741,7 @@ class SimpleSAML_Session {
 		}
 
 		/* We require the logout handlers to register themselves again if they want to be called later. */
-		$this->logout_handlers = array();
+		unset($this->authData[$authority]['LogoutHandlers']);
 	}
 
 
@@ -905,9 +1051,10 @@ class SimpleSAML_Session {
 	 * @param array $state  The state array.
 	 */
 	public function setLogoutState(array $state) {
+		assert('isset($this->authData[$this->authority])');
 
 		$this->dirty = TRUE;
-		$this->logoutState = $state;
+		$this->authData[$this->authority]['LogoutState'] = $state;
 	}
 
 
@@ -917,31 +1064,34 @@ class SimpleSAML_Session {
 	 * @return array  The logout state. If no logout state is set, an empty array will be returned.
 	 */
 	public function getLogoutState() {
+		assert('isset($this->authData[$this->authority])');
 
-		if ($this->logoutState === NULL) {
+		if (!isset($this->authData[$this->authority]['LogoutState'])) {
 			return array();
 		}
 
-		return $this->logoutState;
+		return $this->authData[$this->authority]['LogoutState'];
 	}
 
 
 	/**
 	 * Get the current persistent authentication state.
 	 *
+	 * @param string|NULL $authority  The authority to retrieve the data from.
 	 * @return array  The current persistent authentication state, or NULL if not authenticated.
 	 */
-	public function getAuthState() {
-		if (!$this->isAuthenticated()) {
-			return NULL;
+	public function getAuthState($authority = NULL) {
+		assert('is_string($authority) || is_null($authority)');
+
+		if ($authority === NULL) {
+			$authority = $this->authority;
 		}
 
-		if (!isset($this->authState)) {
-			/* No AuthState for this login handler. */
-			return array();
+		if (!isset($this->authData[$authority])) {
+			return NULL;
 		}
 
-		return $this->authState;
+		return $this->authData[$authority];
 	}
 
 
@@ -1086,4 +1236,22 @@ class SimpleSAML_Session {
 		return $ret;
 	}
 
+
+	/**
+	 * Retrieve authentication data.
+	 *
+	 * @param string $authority  The authentication source we should retrieve data from.
+	 * @param string $name  The name of the data we should retrieve.
+	 * @return mixed  The value, or NULL if the value wasn't found.
+	 */
+	public function getAuthData($authority, $name) {
+		assert('is_string($authority)');
+		assert('is_string($name)');
+
+		if (!isset($this->authData[$authority][$name])) {
+			return NULL;
+		}
+		return $this->authData[$authority][$name];
+	}
+
 }
-- 
GitLab