Skip to content

Commit

Permalink
Merge pull request #9 from mfairchild365/slo
Browse files Browse the repository at this point in the history
Implement Single Log Out (discussion)
  • Loading branch information
saltybeagle committed Dec 15, 2014
2 parents 690ea4b + b86affd commit f49f3ee
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 13 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
}
],
"require": {
"pear-pear/HTTP_Request2": "*"
"pear-pear.php.net/HTTP_Request2": "*",
"tedivm/stash": "0.12.*"
},
"autoload": {
"psr-0": { "": "src/" }
Expand Down
5 changes: 2 additions & 3 deletions docs/examples/simple.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<?php
ini_set('display_errors', true);
set_include_path(dirname(dirname(__DIR__)).'/src'.PATH_SEPARATOR.dirname(dirname(__DIR__)).'/vendor/php');
require_once 'SimpleCAS/Autoload.php';
require_once 'HTTP/Request2.php';
require_once __DIR__ . '/../../vendor/autoload.php';

$options = array('hostname' =>'login.unl.edu',
'port' => 443,
Expand All @@ -12,6 +10,7 @@
$protocol->getRequest()->setConfig('ssl_verify_peer', false);

$client = SimpleCAS::client($protocol);
$client->handleSingleLogOut();
$client->forceAuthentication();

if (isset($_GET['logout'])) {
Expand Down
39 changes: 30 additions & 9 deletions src/SimpleCAS.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,18 +191,26 @@ private function __construct(SimpleCAS_Protocol $protocol)
{
$this->protocol = $protocol;

if ($this->protocol instanceof SimpleCAS_SingleSignOut
&& !empty($_POST)) {
if ($ticket = $this->protocol->validateLogoutRequest($_POST)) {
$this->logout($ticket);
}
}

if (isset($_GET['ticket'])) {
$this->setTicket($_GET['ticket']);
}
}

/**
* Handle any potential Single Log Out requests.
*/
public function handleSingleLogOut()
{
if (!$session_map = $this->protocol->getSessionMap()) {
return;
}

if ($slo_ticket = $session_map->validateLogoutRequest($_POST)) {
$session_map->logout($slo_ticket);
exit();
}
}

/**
* Returns the session namespace or the named session member
*
Expand Down Expand Up @@ -253,7 +261,16 @@ public function getProtocol()
*/
public function getTicket()
{
return $this->_ticket;
if (!empty($this->_ticket)) {
return $this->_ticket;
}

$ticket = $this->_getSession('TICKET');
if (!is_array($ticket)) {
return $ticket;
}

return null;
}

/**
Expand Down Expand Up @@ -325,12 +342,16 @@ protected function validateTicket($ticket)
protected function setAuthenticated($uid)
{
$session =& $this->_getSession();
$session['TICKET'] = true;
$session['TICKET'] = $this->getTicket();
$session['UID'] = $uid;
if (isset($this->protocol->renew)) {
$session['FROM_RENEW'] = true;
}
$this->_authenticated = true;

if ($session_map = $this->protocol->getSessionMap()) {
$session_map->set($this->getTicket(), session_id());
}
}

/**
Expand Down
27 changes: 27 additions & 0 deletions src/SimpleCAS/Protocol.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ abstract class SimpleCAS_Protocol

protected $requestClass;
protected $request;
protected $sessionMap = false;

/**
* Option to request the CAS server redirect after processing a logout URL.
Expand Down Expand Up @@ -112,4 +113,30 @@ function setLogoutServiceRedirect($logoutServiceRedirect)
{
$this->logoutServiceRedirect = (boolean) $logoutServiceRedirect;
}

/**
* Set the session map. The session map is used for single log out.
*
* To disable SLO support, set the session map to null.
*
* @param SimpleCAS_SLOMapInterface|null $sessionMap
*/
function setSessionMap(SimpleCAS_SLOMapInterface $sessionMap = NULL)
{
$this->sessionMap = $sessionMap;
}

/**
* Get the sessions map. If one is not set yet, it will return the default SimpleCAS_SLOMap
*
* @return SimpleCAS_SLOMapInterface|null (null if SLO is disabled)
*/
function getSessionMap()
{
if ($this->sessionMap === false) {
$this->setSessionMap(new SimpleCAS_SLOMap());
}

return $this->sessionMap;
}
}
96 changes: 96 additions & 0 deletions src/SimpleCAS/SLOMap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php
/**
* A class that implments Single Log out (SLO) and maintains a mapping between php session IDs and CAS tickets.
*
* PHP version 5
*
* @category Authentication
* @package SimpleCAS
* @author Michael Fairchild <mfairchild365@gmail.com>
* @copyright 2014 Regents of the University of Nebraska
* @license http://www1.unl.edu/wdn/wiki/Software_License BSD License
* @link http://code.google.com/p/simplecas/
*/
class SimpleCAS_SLOMap extends SimpleCAS_SLOMapInterface
{
protected $pool = false;

public function __construct($cache_driver = NULL)
{
if (!$cache_driver) {
// Create Driver with default options
$cache_driver = new \Stash\Driver\FileSystem();

$cache_driver->setOptions(array(
//Scope the cache to the current application only.
'path' => sys_get_temp_dir() . '/simpleCAS_map_' . md5($this->getApplicationID())
));
}

// Inject the driver into a new Pool object.
$this->pool = new \Stash\Pool($cache_driver);
}

/**
* Generate a unique ID for the current application. This is based on the session cookie's domain/path.
*
* @return string
*/
public function getApplicationID()
{
$cookie_params = session_get_cookie_params();

if (empty($cookie_params['domain'])) {
//By default, the domain will be empty, so if it is empty, lets use the current server_name.
$cookie_params['domain'] = $_SERVER['SERVER_NAME'];
}
return $cookie_params['domain'] . '-' . $cookie_params['path'];
}

/**
* get the session id by a cas ticket
*
* @param $cas_ticket
* @return bool
*/
public function get($cas_ticket)
{
$item = $this->pool->getItem($cas_ticket);

if ($item->isMiss()) {
return false;
}

return $item->get();
}

/**
* Set the session id for a cas ticket
*
* @param $cas_ticket
* @param $session_id
* @return bool
*/
public function set($cas_ticket, $session_id)
{
$item = $this->pool->getItem($cas_ticket);
return $item->set($session_id);
}

/**
* Remove a CAS ticket from the registry
*
* @param $cas_ticket
* @return mixed|void
*/
public function remove($cas_ticket)
{
$item = $this->pool->getItem($cas_ticket);

if ($item->isMiss()) {
return false;
}

return $item->clear();
}
}
101 changes: 101 additions & 0 deletions src/SimpleCAS/SLOMapInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php
/**
* A class that implments Single Log out (SLO) and maintains a mapping between php session IDs and CAS tickets.
*
* PHP version 5
*
* @category Authentication
* @package SimpleCAS
* @author Michael Fairchild <mfairchild365@gmail.com>
* @copyright 2014 Regents of the University of Nebraska
* @license http://www1.unl.edu/wdn/wiki/Software_License BSD License
* @link http://code.google.com/p/simplecas/
*/
abstract class SimpleCAS_SLOMapInterface implements SimpleCAS_SingleSignOut
{
/**
* Determines if the posted request is a valid single sign out request.
*
* @param mixed $post $_POST data sent to the service.
*
* @return bool
*/
public function validateLogoutRequest($post)
{
if (isset($_POST['logoutRequest']) && ($ticket = $this->parseLogoutRequest($_POST['logoutRequest']))) {
return $ticket;
}

return false;
}

/**
* Log out a session by the single log out ticket.
*
* @param $cas_ticket
* @return bool
*/
public function logout($cas_ticket)
{
if (!$session_id = $this->get($cas_ticket)) {
return false;
}

if (session_id()) {
//If a current session exists, save it and close it.
session_commit();
}

//Start the session for this ticket.
session_id($session_id);
session_start();
$result = session_destroy();

$this->loadMapFile();
unset($this->data[$cas_ticket]);
$this->saveMapFile();

return $result;
}

/**
* @param $xml - the XML from the single sign out request
* @return bool|string - the CAS ticket to sign out, false if no ticket was found.
*/
protected function parseLogoutRequest($xml)
{
$xml = new \SimpleXMLElement($xml);
$element = $xml->xpath('//samlp:SessionIndex');

if (empty($element)) {
return false;
}

return (string)$element[0];
}

/**
* Get a session id for a given CAS ticket
*
* @param string $cas_ticket
* @return string mixed
*/
abstract public function get($cas_ticket);

/**
* Save a mapping between a cas ticket and session id
*
* @param $cas_ticket
* @param $session_id
* @return mixed
*/
abstract public function set($cas_ticket, $session_id);

/**
* Remove a CAS ticket
*
* @param $cas_ticket
* @return mixed
*/
abstract public function remove($cas_ticket);
}

0 comments on commit f49f3ee

Please sign in to comment.