Revision: 2139
Author: olavmrk
Date: Wed Jan 27 01:26:23 2010
Log: IdP core: Add logout processing.
http://code.google.com/p/simplesamlphp/source/detail?r=2139

Added:
 /trunk/lib/SimpleSAML/IdP
 /trunk/lib/SimpleSAML/IdP/LogoutHandler.php
 /trunk/lib/SimpleSAML/IdP/LogoutIFrame.php
 /trunk/lib/SimpleSAML/IdP/LogoutTraditional.php
 /trunk/modules/core/templates/logout-iframe-wrapper.php
 /trunk/modules/core/templates/logout-iframe.php
 /trunk/modules/core/www/idp/logout-iframe-done.php
 /trunk/modules/core/www/idp/logout-iframe.js
 /trunk/modules/core/www/idp/logout-iframe.php
Modified:
 /trunk/dictionaries/logout.definition.json
 /trunk/dictionaries/logout.translation.json
 /trunk/lib/SimpleSAML/IdP.php

=======================================
--- /dev/null
+++ /trunk/lib/SimpleSAML/IdP/LogoutHandler.php Wed Jan 27 01:26:23 2010
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * Base class for logout handlers.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+abstract class SimpleSAML_IdP_LogoutHandler {
+
+       /**
+        * The IdP we are logging out from.
+        *
+        * @var SimpleSAML_IdP
+        */
+       protected $idp;
+
+
+       /**
+        * Initialize this logout handler.
+        *
+        * @param SimpleSAML_IdP $idp  The IdP we are logging out from.
+        */
+       public function __construct(SimpleSAML_IdP $idp) {
+               $this->idp = $idp;
+       }
+
+
+       /**
+        * Start a logout operation.
+        *
+        * This function must never return.
+        *
+        * @param array &$state  The logout state.
+        * @param string|NULL $assocId  The association that started the logout.
+        */
+       abstract public function startLogout(array &$state, $assocId);
+
+
+       /**
+        * Handles responses to our logout requests.
+        *
+        * This function will never return.
+        *
+        * @param string $assocId  The association that is terminated.
+ * @param string|NULL $relayState The RelayState from the start of the logout. + * @param SimpleSAML_Error_Exception|NULL $error The error that occured during session termination (if any).
+        */
+ public function onResponse($assocId, $relayState, SimpleSAML_Error_Exception $error = NULL) {
+               assert('is_string($assocId)');
+               assert('is_string($relayState) || is_null($relayState)');
+
+               /* Don't do anything by default. */
+       }
+
+}
=======================================
--- /dev/null
+++ /trunk/lib/SimpleSAML/IdP/LogoutIFrame.php  Wed Jan 27 01:26:23 2010
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * Class which handles iframe logout.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SimpleSAML_IdP_LogoutIFrame extends SimpleSAML_IdP_LogoutHandler {
+
+       /**
+        * Start the logout operation.
+        *
+        * @param array &$state  The logout state.
+        * @param string|NULL $assocId  The SP we are logging out from.
+        */
+       public function startLogout(array &$state, $assocId) {
+               assert('is_string($assocId) || is_null($assocId)');
+
+               $associations = $this->idp->getAssociations();
+
+               if (count($associations) === 0) {
+                       $this->idp->finishLogout($state);
+               }
+
+               foreach ($associations as $id => &$association) {
+                       $association['core:Logout-IFrame:Name'] = 
$this->idp->getSPName($id);
+                       $association['core:Logout-IFrame:State'] = 'onhold';
+               }
+               $state['core:Logout-IFrame:Associations'] = $associations;
+
+               if (!is_null($assocId)) {
+                       $spName = $this->idp->getSPName($assocId);
+                       if ($spName === NULL) {
+                               $spName = array('en' => $assocId);
+                       }
+
+                       $state['core:Logout-IFrame:From'] = $spName;
+               } else {
+                       $state['core:Logout-IFrame:From'] = NULL;
+               }
+
+               $id = SimpleSAML_Auth_State::saveState($state, 
'core:Logout-IFrame');
+ $url = SimpleSAML_Module::getModuleURL('core/idp/logout-iframe.php', array('id' => $id));
+               SimpleSAML_Utilities::redirect($url);
+       }
+
+
+       /**
+        * Continue the logout operation.
+        *
+        * This function will never return.
+        *
+        * @param string $assocId  The association that is terminated.
+ * @param string|NULL $relayState The RelayState from the start of the logout. + * @param SimpleSAML_Error_Exception|NULL $error The error that occured during session termination (if any).
+        */
+ public function onResponse($assocId, $relayState, SimpleSAML_Error_Exception $error = NULL) {
+               assert('is_string($assocId)');
+
+               $spId = sha1($assocId);
+               $cookieId = 'logout-iframe-' . $spId;
+
+               $globalConfig = SimpleSAML_Configuration::getInstance();
+               $cookiePath = '/' . $globalConfig->getBaseURL();
+
+ setcookie($cookieId, ($error ? 'failed' : 'completed'), time() + 5*60, $cookiePath);
+
+               echo('<!DOCTYPE html>
+<html>
+<head>
+<title>Logout response from ' . htmlspecialchars(var_export($assocId, TRUE)) . '</title>
+<script>
+');
+               if ($error) {
+                       $errorMsg = $error->getMessage();
+ echo('window.parent.logoutFailed("' . $spId . '", "' . addslashes($errorMsg) . '");');
+               } else {
+                       echo('window.parent.logoutCompleted("' . $spId . '");');
+               }
+               echo('
+</script>
+</head>
+<body>
+</body>
+</html>
+');
+
+               exit(0);
+       }
+
+}
=======================================
--- /dev/null
+++ /trunk/lib/SimpleSAML/IdP/LogoutTraditional.php     Wed Jan 27 01:26:23 2010
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * Class which handles traditional logout.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SimpleSAML_IdP_LogoutTraditional extends SimpleSAML_IdP_LogoutHandler {
+
+       /**
+        * Picks the next SP and issues a logout request.
+        *
+        * This function never returns.
+        *
+        * @param array &$state  The logout state.
+        */
+       private function logoutNextSP(array &$state) {
+
+               $association = 
array_pop($state['core:LogoutTraditional:Remaining']);
+               if ($association === NULL) {
+                       $this->idp->finishLogout($state);
+               }
+
+ $relayState = SimpleSAML_Auth_State::saveState($state, 'core:LogoutTraditional', TRUE);
+
+               $id = $association['id'];
+               SimpleSAML_Logger::info('Logging out of ' . var_export($id, 
TRUE) . '.');
+
+               try {
+ $url = call_user_func(array($association['Handler'], 'getLogoutURL'), $this->idp, $association, $relayState);
+                       SimpleSAML_Utilities::redirect($url);
+               } catch (Exception $e) {
+ SimpleSAML_Logger::warning('Unable to initialize logout to ' . var_export($id, TRUE) . '.');
+                       $this->idp->terminateAssociation($id);
+                       $state['core:Failed'] = TRUE;
+
+                       /* Try the next SP. */
+                       $this->logoutNextSP($state);
+                       assert('FALSE');
+               }
+       }
+
+
+       /**
+        * Start the logout operation.
+        *
+        * This function never returns.
+        *
+        * @param array &$state  The logout state.
+        * @param string $assocId  The association that started the logout.
+        */
+       public function startLogout(array &$state, $assocId) {
+
+ $state['core:LogoutTraditional:Remaining'] = $this->idp->getAssociations();
+
+               self::logoutNextSP($state);
+       }
+
+
+       /**
+        * Continue the logout operation.
+        *
+        * This function will never return.
+        *
+        * @param string $assocId  The association that is terminated.
+ * @param string|NULL $relayState The RelayState from the start of the logout. + * @param SimpleSAML_Error_Exception|NULL $error The error that occured during session termination (if any).
+        */
+ public function onResponse($assocId, $relayState, SimpleSAML_Error_Exception $error = NULL) {
+               assert('is_string($assocId)');
+               assert('is_string($relayState) || is_null($relayState)');
+
+               if ($relayState === NULL) {
+                       throw new SimpleSAML_Error_Exception('RelayState lost 
during logout.');
+               }
+
+ $state = SimpleSAML_Auth_State::loadState($relayState, 'core:LogoutTraditional');
+
+               if ($error === NULL) {
+ SimpleSAML_Logger::info('Logged out of ' . var_export($assocId, TRUE) . '.');
+                       $this->idp->terminateAssociation($assocId);
+               } else {
+ SimpleSAML_Logger::warning('Error received from ' . var_export($assocId, TRUE) . ' during logout:');
+                       $error->logWarning();
+                       $state['core:Failed'] = TRUE;
+               }
+
+               self::logoutNextSP($state);
+       }
+
+}
=======================================
--- /dev/null
+++ /trunk/modules/core/templates/logout-iframe-wrapper.php Wed Jan 27 01:26:23 2010
@@ -0,0 +1,29 @@
+<?php
+
+$id = $this->data['id'];
+$SPs = $this->data['SPs'];
+$timeout = $this->data['timeout'];
+
+$iframeURL = 'logout-iframe.php?type=embed&id=' . urlencode($id) . '&timeout=' . (string)$timeout;
+
+/* Pretty arbitrary height, but should have enough safety margins for most cases. */
+$iframeHeight = 25 + count($SPs) * 4;
+
+$this->data['header'] = $this->t('{logout:progress}');
+$this->includeAtTemplateBase('includes/header.php');
+echo '<iframe style="width:100%; height:' . $iframeHeight . 'em; border:0;" src="' . htmlspecialchars($iframeURL) . '"></iframe>';
+
+foreach ($SPs AS $assocId => $sp) {
+       $spId = sha1($assocId);
+
+       if ($sp['core:Logout-IFrame:State'] !== 'inprogress') {
+               continue;
+       }
+       assert('isset($sp["core:Logout-IFrame:URL"])');
+
+       $url = $sp["core:Logout-IFrame:URL"];
+
+ echo('<iframe style="width:0; height:0; border:0;" src="' . htmlspecialchars($url) . '"></iframe>');
+}
+
+$this->includeAtTemplateBase('includes/footer.php');
=======================================
--- /dev/null
+++ /trunk/modules/core/templates/logout-iframe.php     Wed Jan 27 01:26:23 2010
@@ -0,0 +1,205 @@
+<?php
+
+$id = $this->data['id'];
+$type = $this->data['type'];
+$from = $this->data['from'];
+$SPs = $this->data['SPs'];
+$timeout = $this->data['timeout'];
+
+$stateImage = array(
+ 'unsupported' => '/' . $this->data['baseurlpath'] . 'resources/icons/silk/delete.png', + 'completed' => '/' . $this->data['baseurlpath'] . 'resources/icons/silk/accept.png', + 'onhold' => '/' . $this->data['baseurlpath'] . 'resources/icons/bullet16_grey.png', + 'inprogress' => '/' . $this->data['baseurlpath'] . 'resources/progress.gif', + 'failed' => '/' . $this->data['baseurlpath'] . 'resources/icons/silk/exclamation.png',
+);
+
+$stateText = array(
+       'unsupported' => '',
+       'completed' => $this->t('{logout:completed}'),
+       'onhold' => '',
+       'inprogress' => $this->t('{logout:progress}'),
+       'failed' => $this->t('{logout:failed}'),
+);
+
+$spStatus = array();
+$nFailed = 0;
+$nProgress = 0;
+foreach ($SPs as $assocId => $sp) {
+       assert('isset($sp["core:Logout-IFrame:State"])');
+       $state = $sp['core:Logout-IFrame:State'];
+       $spStatus[sha1($assocId)] = $state;
+       if ($state === 'failed') {
+               $nFailed += 1;
+       } elseif ($state === 'inprogress') {
+               $nProgress += 1;
+       }
+}
+
+if ($from !== NULL) {
+       $from = $this->getTranslation($from);
+}
+
+
+if (!isset($this->data['head'])) {
+       $this->data['head'] = '';
+}
+
+$this->data['head'] .= '<script type="text/javascript" src="/' . $this->data['baseurlpath'] . 'resources/jquery.js"></script>';
+
+$this->data['head'] .= '
+<script type="text/javascript" language="JavaScript">
+window.stateImage = ' . json_encode($stateImage) . ';
+window.stateText = ' . json_encode($stateText) . ';
+window.spStatus = ' . json_encode($spStatus) . ';
+window.type = "' . $type . '";
+window.timeoutIn = ' . (string)($timeout - time()) . ';
+window.asyncURL = "logout-iframe.php?id=' . $id . '&type=async";
+</script>';
+
+$this->data['head'] .= '<script type="text/javascript" src="logout-iframe.js"></script>';
+
+if ($type === 'embed') {
+       $this->data['head'] .= '<meta http-equiv="refresh" content="1" />';
+}
+
+$this->data['header'] = $this->t('{logout:progress}');
+if ($type === 'embed') {
+       $this->includeAtTemplateBase('includes/header-embed.php');
+} else {
+       $this->includeAtTemplateBase('includes/header.php');
+}
+
+if ($from !== NULL) {
+
+ echo('<div><img style="float: left; margin-right: 12px" src="/' . $this->data['baseurlpath'] . 'resources/icons/checkmark48.png" alt="Successful logout" />'); + echo('<p style="padding-top: 16px; ">' . $this->t('{logout:loggedoutfrom}', array('%SP%' => '<strong>' .htmlspecialchars($from).'</strong>')) . '</p>');
+       echo('<p style="height: 0px; clear: left;"></p>');
+       echo('</div>');
+}
+
+echo('<div style="margin-top: 3em; clear: both">');
+
+
+echo('<p style="margin-bottom: .5em">');
+if ($type === 'init') {
+       echo($this->t('{logout:also_from}'));
+} else {
+       echo($this->t('{logout:logging_out_from}'));
+}
+echo('</p>');
+
+echo '<table id="slostatustable">';
+
+foreach ($SPs AS $assocId => $sp) {
+       if (isset($sp['core:Logout-IFrame:Name'])) {
+               $spName = $this->getTranslation($sp['core:Logout-IFrame:Name']);
+       } else {
+               $spName = $assocId;
+       }
+
+       assert('isset($sp["core:Logout-IFrame:State"])');
+       $spState = $sp['core:Logout-IFrame:State'];
+
+       $spId = sha1($assocId);
+
+       echo '<tr>';
+
+       echo '<td>';
+ echo '<img class="logoutstatusimage" id="statusimage-' . $spId . '" src="' . htmlspecialchars($stateImage[$spState]) . '" alt="' . htmlspecialchars($stateText[$spState]) . '"/>';
+       echo '</td>';
+
+       echo '<td>' . htmlspecialchars($spName) . '</td>';
+
+       echo '</tr>';
+}
+
+if (isset($from)) {
+ $logoutCancelText = $this->t('{logout:logout_only}', array('%SP%' => htmlspecialchars($from)));
+} else {
+       $logoutCancelText = $this->t('{logout:no}');
+}
+
+?>
+</table>
+</div>
+
+<?php
+if ($type === 'init') {
+?>
+<div id="confirmation" style="margin-top: 1em" >
+<p><?php echo $this->t('{logout:logout_all_question}'); ?> <br /></p>
+
+<form id="startform" method="get" action="logout-iframe.php">
+<input type="hidden" name="id" value="<?php echo $id; ?>" />
+<input type="hidden" id="logout-type-selector" name="type" value="nojs" />
+<input type="submit" id="logout-all" name="ok" value="<?php echo $this->t('{logout:logout_all}'); ?>" />
+</form>
+
+<form method="get" action="logout-iframe-done.php">
+<input type="hidden" name="id" value="<?php echo $id; ?>" />
+<input type="submit" name="cancel" value="<?php echo $logoutCancelText; ?>" />
+</form>
+
+</div>
+
+<?php
+} else {
+?>
+
+<?php
+if ($nFailed > 0) {
+       $displayStyle = '';
+} else {
+       $displayStyle = 'display: none;';
+}
+echo('<div id="logout-failed-message" style="margin-top: 1em; border: 1px solid #ccc; padding: 1em; background: #eaeaea;' . $displayStyle . '">'); +echo('<img src="/' . $this->data['baseurlpath'] . 'resources/icons/caution.png" alt="" style="float: left; margin-right: 5px;" />');
+echo('<p>' . $this->t('{logout:failedsps}') . '</p>');
+echo('<form method="get" action="logout-iframe-done.php" target="_top">');
+echo('<input type="hidden" name="id" value="' . $id . '" />');
+echo('<input type="submit" name="continue" value="' . $this->t('{logout:return}'). '" />');
+echo('</form>');
+
+echo('</div>');
+
+if ($nProgress == 0 && $nFailed == 0) {
+       echo('<div id="logout-completed">');
+} else {
+       echo('<div id="logout-completed" style="display:none;">');
+}
+echo('<p>' . $this->t('{logout:success}') . '</p>');
+?>
+<form method="get" action="logout-iframe-done.php" id="done-form" target="_top">
+       <input type="hidden" name="id" value="<?php echo $id; ?>" />
+ <input type="submit" name="continue" value="<?php echo $this->t('{logout:return}'); ?>" />
+</form>
+</div>
+
+<?php
+if ($type === 'js') {
+       foreach ($SPs AS $assocId => $sp) {
+               $spId = sha1($assocId);
+
+               if ($sp['core:Logout-IFrame:State'] !== 'inprogress') {
+                       continue;
+               }
+               assert('isset($sp["core:Logout-IFrame:URL"])');
+
+               $url = $sp["core:Logout-IFrame:URL"];
+
+ echo('<iframe style="width:0; height:0; border:0;" src="' . htmlspecialchars($url) . '"></iframe>');
+       }
+}
+?>
+
+<?php
+}
+?>
+
+<?php
+if ($type === 'embed') {
+       $this->includeAtTemplateBase('includes/footer-embed.php');
+} else {
+       $this->includeAtTemplateBase('includes/footer.php');
+}
=======================================
--- /dev/null
+++ /trunk/modules/core/www/idp/logout-iframe-done.php Wed Jan 27 01:26:23 2010
@@ -0,0 +1,52 @@
+<?php
+
+if (!isset($_REQUEST['id'])) {
+       throw new SimpleSAML_Error_BadRequest('Missing required parameter: id');
+}
+$id = (string)$_REQUEST['id'];
+
+$state = SimpleSAML_Auth_State::loadState($id, 'core:Logout-IFrame');
+$idp = SimpleSAML_IdP::getByState($state);
+
+$associations = $idp->getAssociations();
+$SPs = $state['core:Logout-IFrame:Associations'];
+
+$globalConfig = SimpleSAML_Configuration::getInstance();
+$cookiePath = '/' . $globalConfig->getBaseURL();
+
+/* Find the status of all SPs. */
+foreach ($SPs as $assocId => &$sp) {
+
+       $spId = sha1($assocId);
+
+       $cookieId = 'logout-iframe-' . $spId;
+       if (isset($_COOKIE[$cookieId])) {
+               $cookie = $_COOKIE[$cookieId];
+               if ($cookie == 'completed' || $cookie == 'failed') {
+                       $sp['core:Logout-IFrame:State'] = $cookie;
+               }
+               setcookie($cookieId, '', time() - 3600, $cookiePath);
+       }
+
+       if (!isset($associations[$assocId])) {
+               $sp['core:Logout-IFrame:State'] = 'completed';
+       }
+
+}
+
+
+/* Terminate the associations. */
+foreach ($SPs as $assocId => $sp) {
+
+       if ($sp['core:Logout-IFrame:State'] === 'completed') {
+               $idp->terminateAssociation($assocId);
+       } else {
+ SimpleSAML_Logger::warning('Unable to terminate association with ' . var_export($assocId, TRUE) . '.');
+               $state['core:Failed'] = TRUE;
+       }
+
+}
+
+
+/* We are done. */
+$idp->finishLogout($state);
=======================================
--- /dev/null
+++ /trunk/modules/core/www/idp/logout-iframe.js        Wed Jan 27 01:26:23 2010
@@ -0,0 +1,75 @@
+function updateStatus() {
+
+       var nFailed = 0;
+       var nProgress = 0;
+       for (sp in window.spStatus) {
+               switch (window.spStatus[sp]) {
+               case 'failed':
+                       nFailed += 1;
+                       break;
+               case 'inprogress':
+                       nProgress += 1;
+                       break;
+               }
+       }
+
+       if (nFailed > 0) {
+               $('#logout-failed-message').show();
+       }
+
+       if (nProgress == 0 && nFailed == 0) {
+               $('#logout-completed').show();
+               $('#done-form').submit();
+       }
+}
+
+function updateSPStatus(spId, status, reason) {
+       if (window.spStatus[spId] == status) {
+               /* Unchanged. */
+               return;
+       }
+
+ $('#statusimage-' + spId).attr('src', window.stateImage[status]).attr('alt', window.stateText[status]).attr('title', reason);
+       window.spStatus[spId] = status;
+
+       updateStatus();
+}
+function logoutCompleted(spId) {
+       updateSPStatus(spId, 'completed', '');
+}
+function logoutFailed(spId, reason) {
+       updateSPStatus(spId, 'failed', reason);
+}
+
+function timeoutSPs() {
+       for (sp in window.spStatus) {
+               if (window.spStatus[sp] == 'inprogress') {
+                       logoutFailed(sp, 'Timeout');
+               }
+       }
+}
+
+function asyncUpdate() {
+ jQuery.getJSON(window.asyncURL, window.spStatus, function(data, textStatus) {
+               for (sp in data) {
+                       if (data[sp] == 'completed') {
+                               logoutCompleted(sp);
+                       } else if (data[sp] == 'failed') {
+                               logoutFailed(sp, 'async update');
+                       }
+               }
+               window.setTimeout(asyncUpdate, 1000);
+       });
+}
+
+
+$('document').ready(function(){
+       if (window.type == 'js') {
+ window.timeoutID = window.setTimeout(timeoutSPs, window.timeoutIn * 1000);
+               window.setTimeout(asyncUpdate, 1000);
+               updateStatus();
+       } else if (window.type == 'init') {
+               $('#logout-type-selector').attr('value', 'js');
+               $('#logout-all').focus();
+       }
+});
=======================================
--- /dev/null
+++ /trunk/modules/core/www/idp/logout-iframe.php       Wed Jan 27 01:26:23 2010
@@ -0,0 +1,118 @@
+<?php
+
+if (!isset($_REQUEST['id'])) {
+       throw new SimpleSAML_Error_BadRequest('Missing required parameter: id');
+}
+$id = (string)$_REQUEST['id'];
+
+if (isset($_REQUEST['type'])) {
+       $type = (string)$_REQUEST['type'];
+ if (!in_array($type, array('init', 'js', 'nojs', 'embed', 'async'), TRUE)) {
+               throw new SimpleSAML_Error_BadRequest('Invalid value for 
type.');
+       }
+} else {
+       $type = 'init';
+}
+
+if (isset($_REQUEST['timeout'])) {
+       $timeout = (int)$_REQUEST['timeout'];
+} else {
+       $timeout = time() + 10;
+}
+
+$state = SimpleSAML_Auth_State::loadState($id, 'core:Logout-IFrame');
+$idp = SimpleSAML_IdP::getByState($state);
+
+if ($type !== 'init') {
+       /* Update association state. */
+
+       $associations = $idp->getAssociations();
+
+       foreach ($state['core:Logout-IFrame:Associations'] as $assocId => &$sp) 
{
+
+               $spId = sha1($assocId);
+
+               /* Move SPs from 'onhold' to 'inprogress'. */
+               if ($sp['core:Logout-IFrame:State'] === 'onhold') {
+                       $sp['core:Logout-IFrame:State'] = 'inprogress';
+               }
+
+               /* Check for update by cookie. */
+               $cookieId = 'logout-iframe-' . $spId;
+               if (isset($_COOKIE[$cookieId])) {
+                       $cookie = $_COOKIE[$cookieId];
+                       if ($cookie == 'completed' || $cookie == 'failed') {
+                               $sp['core:Logout-IFrame:State'] = $cookie;
+                       }
+               }
+
+               /* Check for update through request. */
+               if (isset($_REQUEST[$spId])) {
+                       $s = $_REQUEST[$spId];
+                       if ($s == 'completed' || $s == 'failed') {
+                               $sp['core:Logout-IFrame:State'] = $s;
+                       }
+               }
+
+               /* In case we are refreshing a page. */
+               if (!isset($associations[$assocId])) {
+                       $sp['core:Logout-IFrame:State'] = 'completed';
+               }
+
+               /* Update the IdP. */
+               if ($sp['core:Logout-IFrame:State'] === 'completed') {
+                       $idp->terminateAssociation($assocId);
+               }
+       }
+}
+
+if ($type === 'js' || $type === 'nojs') {
+       foreach ($state['core:Logout-IFrame:Associations'] as $assocId => &$sp) 
{
+
+               if ($sp['core:Logout-IFrame:State'] !== 'inprogress') {
+                       /* This SP isn't logging out. */
+                       continue;
+               }
+
+               try {
+ $url = call_user_func(array($sp['Handler'], 'getLogoutURL'), $idp, $sp, NULL);
+                       $sp['core:Logout-IFrame:URL'] = $url;
+               } catch (Exception $e) {
+                       $sp['core:Logout-IFrame:State'] = 'failed';
+               }
+       }
+}
+
+$id = SimpleSAML_Auth_State::saveState($state, 'core:Logout-IFrame');
+
+$globalConfig = SimpleSAML_Configuration::getInstance();
+
+if ($type === 'nojs') {
+ $t = new SimpleSAML_XHTML_Template($globalConfig, 'core:logout-iframe-wrapper.php');
+       $t->data['id'] = $id;
+       $t->data['SPs'] = $state['core:Logout-IFrame:Associations'];
+       $t->data['timeout'] = $timeout;
+       $t->show();
+       exit(0);
+
+} elseif ($type == 'async') {
+       header('Content-Type: application/json');
+       $res = array();
+       foreach ($state['core:Logout-IFrame:Associations'] as $assocId => $sp) {
+               if ($sp['core:Logout-IFrame:State'] !== 'completed') {
+                       continue;
+               }
+               $res[sha1($assocId)] = 'completed';
+       }
+       echo(json_encode($res));
+       exit(0);
+}
+
+$t = new SimpleSAML_XHTML_Template($globalConfig, 'core:logout-iframe.php');
+$t->data['id'] = $id;
+$t->data['type'] = $type;
+$t->data['from'] = $state['core:Logout-IFrame:From'];
+$t->data['SPs'] = $state['core:Logout-IFrame:Associations'];
+$t->data['timeout'] = $timeout;
+$t->show();
+exit(0);
=======================================
--- /trunk/dictionaries/logout.definition.json  Tue Jan 12 03:33:22 2010
+++ /trunk/dictionaries/logout.definition.json  Wed Jan 27 01:26:23 2010
@@ -79,5 +79,11 @@
        },
        "no": {
                "en": "No"
+       },
+       "logging_out_from": {
+               "en": "Logging out of the following services:"
+       },
+       "failedsps": {
+ "en": "Unable to log out of one or more services. To ensure that all your sessions are closed, you are encouraged to <i>close your webbrowser<\/i>."
        }
 }
=======================================
--- /trunk/dictionaries/logout.translation.json Tue Jan 26 00:19:26 2010
+++ /trunk/dictionaries/logout.translation.json Wed Jan 27 01:26:23 2010
@@ -453,5 +453,13 @@
                "pt": "N\u00e3o",
                "pl": "Nie",
                "tr": "Hay\u0131r"
+       },
+       "logging_out_from": {
+               "sl": "Odjava iz naslednjih storitev:",
+               "da": "Du logger ud af f\u00f8lgende services:"
+       },
+       "failedsps": {
+ "sl": "Odjava z ene ali ve\u010d storitev ni uspela. Odjavo dokon\u010dajte tako, da <i>zaprete spletni brskalnik<\/i>.", + "da": "Kan ikke logge ud af en eller flere services. For at sikre at alle dine sessioner er lukket <i>skal du lukke din browser<\/i>."
        }
 }
=======================================
--- /trunk/lib/SimpleSAML/IdP.php       Sun Jan 24 23:19:07 2010
+++ /trunk/lib/SimpleSAML/IdP.php       Wed Jan 27 01:26:23 2010
@@ -105,6 +105,84 @@

                return $this->config;
        }
+
+
+       /**
+        * Get SP name.
+        *
+        * @param string $assocId  The association identifier.
+ * @return array|NULL The name of the SP, as an associative array of language=>text, or NULL if this isn't an SP.
+        */
+       public function getSPName($assocId) {
+               assert('is_string($assocId)');
+
+               if (substr($assocId, 0, 5) !== 'saml:') {
+                       return NULL;
+               }
+
+               $spEntityId = substr($assocId, 5);
+ $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+               try {
+ $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
+               } catch (Exception $e) {
+                       try {
+ $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'shib13-sp-remote');
+                       } catch (Exception $e) {
+                               return NULL;
+                       }
+               }
+
+ return $spMetadata->getLocalizedString('name', array('en' => $spEntityId));
+       }
+
+
+       /**
+        * Retrieve list of SP associations.
+        *
+        * @return array  List of SP associations.
+        */
+       public function getAssociations() {
+
+               $session = SimpleSAML_Session::getInstance();
+
+               $associations = array();
+
+               foreach ($session->get_sp_list() as $spEntityId) {
+
+                       $nameId = 
$session->getSessionNameId('saml20-sp-remote', $spEntityId);
+                       if($nameId === NULL) {
+                               $nameId = $this->getNameID();
+                       }
+
+                       $id = 'saml:' . $spEntityId;
+
+                       $associations[$id] = array(
+                               'id' => $id,
+                               'Handler' => 'sspmod_saml_IdP_SAML2',
+                               'saml:entityID' => $spEntityId,
+                               'saml:NameID' => $nameId,
+                               'saml:SessionIndex' => 
$session->getSessionIndex(),
+                       );
+               }
+
+               return $associations;
+       }
+
+
+       /**
+        * Remove an SP association.
+        *
+        * @param string $assocId  The association id.
+        */
+       public function terminateAssociation($assocId) {
+               assert('is_string($assocId)');
+
+               $session = SimpleSAML_Session::getInstance();
+
+               if (substr($assocId, 0, 5) === 'saml:') {
+                       $session->set_sp_logout_completed(substr($assocId, 5));
+               }
+       }


        /**
@@ -278,5 +356,150 @@
                        SimpleSAML_Auth_State::throwException($state, $e);
                }
        }
+
+
+       /**
+        * Find the logout handler of this IdP.
+        *
+        * @return string  The logout handler class.
+        */
+       public function getLogoutHandler() {
+
+               /* Find the logout handler. */
+               $logouttype = $this->getConfig()->getString('logouttype', 
'traditional');
+               switch ($logouttype) {
+               case 'traditional':
+                       $handler = 'SimpleSAML_IdP_LogoutTraditional';
+                       break;
+               case 'iframe':
+                       $handler = 'SimpleSAML_IdP_LogoutIFrame';
+                       break;
+               default:
+ throw new SimpleSAML_Error_Exception('Unknown logout handler: ' . var_export($logouttype, TRUE));
+               }
+
+               return new $handler($this);
+
+       }
+
+
+       /**
+        * Finish the logout operation.
+        *
+        * This function will never return.
+        *
+        * @param array &$state  The logout request state.
+        */
+       public function finishLogout(array &$state) {
+               assert('isset($state["Responder"])');
+
+               call_user_func($state['Responder'], $this, $state);
+               assert('FALSE');
+       }
+
+
+       /**
+        * Process a logout request.
+        *
+        * This function will never return.
+        *
+        * @param array &$state  The logout request state.
+ * @param string|NULL $assocId The association we received the logout request from, or NULL if there was no association.
+        */
+       public function handleLogoutRequest(array &$state, $assocId) {
+               assert('isset($state["Responder"])');
+               assert('is_string($assocId) || is_null($assocId)');
+
+               $state['core:IdP'] = $this->id;
+               $state['core:TerminatedAssocId'] = $assocId;
+
+               if ($assocId !== NULL) {
+                       $this->terminateAssociation($assocId);
+               }
+
+               /* Terminate the local session. */
+               $session = SimpleSAML_Session::getInstance();
+               $authority = $session->getAuthority();
+               if ($authority !== NULL) {
+                       /* We are logged in. */
+
+ $id = SimpleSAML_Auth_State::saveState($state, 'core:Logout:afterbridge');
+                       $returnTo = 
SimpleSAML_Module::getModuleURL('core/idp/resumelogout.php',
+                               array('id' => $id)
+                       );
+
+                       if ($authority === $this->config->getString('auth')) {
+                               /* This is probably an authentication source. */
+                               
SimpleSAML_Auth_Default::initLogoutReturn($returnTo);
+                       } elseif ($authority === 'saml2') {
+                               /* SAML 2 SP which isn't an authentication 
source. */
+ SimpleSAML_Utilities::redirect('/' . $config->getBaseURL() . 'saml2/sp/initSLO.php',
+                                       array('RelayState' => $returnTo)
+                               );
+                       } else {
+                               /* A different old-style authentication file. */
+                               $session->doLogout();
+                       }
+               }
+
+               $handler = $this->getLogoutHandler();
+               $handler->startLogout($state, $assocId);
+               assert('FALSE');
+       }
+
+
+       /**
+        * Process a logout response.
+        *
+        * This function will never return.
+        *
+        * @param string $assocId  The association that is terminated.
+ * @param string|NULL $relayState The RelayState from the start of the logout. + * @param SimpleSAML_Error_Exception|NULL $error The error that occured during session termination (if any).
+        */
+ public function handleLogoutResponse($assocId, $relayState, SimpleSAML_Error_Exception $error = NULL) {
+               assert('is_string($assocId)');
+               assert('is_string($relayState) || is_null($relayState)');
+
+               $handler = $this->getLogoutHandler();
+               $handler->onResponse($assocId, $relayState, $error);
+
+               assert('FALSE');
+       }
+
+
+       /**
+        * Log out, then redirect to an URL.
+        *
+        * This function never returns.
+        *
+ * @param string $url The URL the user should be returned to after logout.
+        */
+       public function doLogoutRedirect($url) {
+               assert('is_string($url)');
+
+               $state = array(
+                       'Responder' => array('SimpleSAML_IdP', 
'finishLogoutRedirect'),
+                       'core:Logout:URL' => $url,
+               );
+
+               $this->handleLogoutRequest($state, NULL);
+               assert('FALSE');
+       }
+
+
+       /**
+        * Redirect to an URL after logout.
+        *
+        * This function never returns.
+        *
+        * @param array &$state  The logout state from doLogoutRedirect().
+        */
+ public static function finishLogoutRedirect(SimpleSAML_IdP $idp, array $state) {
+               assert('isset($state["core:Logout:URL"])');
+
+               SimpleSAML_Utilities::redirect($state['core:Logout:URL']);
+               assert('FALSE');
+       }

 }

--
You received this message because you are subscribed to the Google Groups 
"simpleSAMLphp commits" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/simplesamlphp-commits?hl=en.

Reply via email to