Skip to content
Snippets Groups Projects
Commit 27d9162a authored by Olav Morken's avatar Olav Morken
Browse files

cdc: New module for working with common domain cookies.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@2761 44740490-163a-0410-bde0-09ae8108e29a
parent e92707d0
No related branches found
No related tags found
No related merge requests found
<?php
$config = array(
'example.org' => array(
/*
* The shared key for this CDC server.
*/
'key' => 'ExampleSharedKey',
/*
* The URL to the server script.
*/
'server' => 'https://my-cdc.example.org/simplesaml/module.php/cdc/server.php',
/*
* The lifetime of our cookie, in seconds.
*
* If this is 0, the cookie will expire when the browser is closed.
*/
'cookie.lifetime' => 0,
),
);
This file indicates that the default state of this module
is disabled. To enable, create a file named enable in the
same directory as this file.
<?php
/**
* Filter for setting the SAML 2 common domain cookie.
*
* @package simpleSAMLphp
* @version $Id$
*/
class sspmod_cdc_Auth_Process_CDC extends SimpleSAML_Auth_ProcessingFilter {
/**
* Our CDC domain.
*
* @var string
*/
private $domain;
/**
* Our CDC client.
*
* @var sspmod_cdc_Client
*/
private $client;
/**
* Initialize this filter.
*
* @param array $config Configuration information about this filter.
* @param mixed $reserved For future use.
*/
public function __construct($config, $reserved) {
parent::__construct($config, $reserved);
assert('is_array($config)');
if (!isset($config['domain'])) {
throw new SimpleSAML_Error_Exception('Missing domain option in cdc:CDC filter.');
}
$this->domain = (string)$config['domain'];
$this->client = new sspmod_cdc_Client($this->domain);
}
/**
* Redirect to page setting CDC.
*
* @param array &$state The request state.
*/
public function process(&$state) {
assert('is_array($state)');
if (!isset($state['Source']['entityid'])) {
SimpleSAML_Logger::warning('saml:CDC: Could not find IdP entityID.');
return;
}
/* Save state and build request. */
$id = SimpleSAML_Auth_State::saveState($state, 'cdc:resume');
$returnTo = SimpleSAML_Module::getModuleURL('cdc/resume.php', array('domain' => $this->domain));
$params = array(
'id' => $id,
'entityID' => $state['Source']['entityid'],
);
$this->client->sendRequest($returnTo, 'append', $params);
}
}
<?php
/**
* CDC client class.
*
* @package simpleSAMLphp
* @version $Id$
*/
class sspmod_cdc_Client {
/**
* Our CDC domain.
*
* @var string
*/
private $domain;
/**
* The CDC server we send requests to.
*
* @var sspmod_cdc_Server|NULL
*/
private $server;
/**
* Initialize a CDC client.
*
* @param string $domain The domain we should query the server for.
*/
public function __construct($domain) {
assert('is_string($domain)');
$this->domain = $domain;
$this->server = new sspmod_cdc_Server($domain);
}
/**
* Receive a CDC response.
*
* @return array|NULL The response, or NULL if no response is received.
*/
public function getResponse() {
return $this->server->getResponse();
}
/**
* Send a request.
*
* @param string $returnTo The URL we should return to afterwards.
* @param string $op The operation we are performing.
* @param array $params Additional parameters.
*/
public function sendRequest($returnTo, $op, array $params = array()) {
assert('is_string($returnTo)');
assert('is_string($op)');
$params['op'] = $op;
$params['return'] = $returnTo;
$this->server->sendRequest($params);
}
}
<?php
/**
* CDC server class.
*
* @package simpleSAMLphp
* @version $Id$
*/
class sspmod_cdc_Server {
/**
* The domain.
*
* @var string
*/
private $domain;
/**
* The URL to the server.
*
* @var string
*/
private $server;
/**
* Our shared key.
*
* @var string
*/
private $key;
/**
* The lifetime of our cookie, in seconds.
*
* If this is 0, the cookie will expire when the browser is closed.
*
* @param int
*/
private $cookieLifetime;
/**
* Initialize a CDC server.
*
* @param string $domain The domain we are a server for.
*/
public function __construct($domain) {
assert('is_string($domain)');
$cdcConfig = SimpleSAML_Configuration::getConfig('module_cdc.php');
$config = $cdcConfig->getConfigItem($domain, NULL);
if ($config === NULL) {
throw new SimpleSAML_Error_Exception('Unknown CDC domain: ' . var_export($domain, TRUE));
}
$this->domain = $domain;
$this->server = $config->getString('server');
$this->key = $config->getString('key');
$this->cookieLifetime = $config->getInteger('cookie.lifetime', 0);
if ($this->key === 'ExampleSharedKey') {
throw new SimpleSAML_Error_Exception('Key for CDC domain ' . var_export($domain, TRUE) . ' not changed from default.');
}
}
/**
* Send a request to this CDC server.
*
* @param array $request The CDC request.
*/
public function sendRequest(array $request) {
assert('isset($request["return"])');
assert('isset($request["op"])');
$request['domain'] = $this->domain;
$this->send($this->server, 'CDCRequest', $request);
}
/**
* Parse and validate response received from a CDC server.
*
* @return array|NULL The response, or NULL if no response is received.
*/
public function getResponse() {
$response = self::get('CDCResponse');
if ($response === NULL) {
return NULL;
}
if ($response['domain'] !== $this->domain) {
throw new SimpleSAML_Error_Exception('Response received from wrong domain.');
}
$this->validate('CDCResponse');
return $response;
}
/**
* Parse and process a CDC request.
*/
public static function processRequest() {
$request = self::get('CDCRequest');
if ($request === NULL) {
throw new SimpleSAML_Error_BadRequest('Missing "CDCRequest" parameter.');
}
$domain = $request['domain'];
$server = new sspmod_cdc_Server($domain);
$server->handleRequest($request);
}
/**
* Handle a parsed CDC requst.
*
* @param array $request
*/
private function handleRequest(array $request) {
if (!isset($request['op'])) {
throw new SimpleSAML_Error_BadRequest('Missing "op" in CDC request.');
}
$op = (string)$request['op'];
SimpleSAML_Logger::info('Received CDC request with "op": ' . var_export($op, TRUE));
if (!isset($request['return'])) {
throw new SimpleSAML_Error_BadRequest('Missing "return" in CDC request.');
}
$return = (string)$request['return'];
switch ($op) {
case 'append':
$response = $this->handleAppend($request);
break;
case 'delete':
$response = $this->handleDelete($request);
break;
case 'read':
$response = $this->handleRead($request);
break;
default:
$response = 'unknown-op';
}
if (is_string($response)) {
$response = array(
'status' => $response,
);
}
$response['op'] = $op;
if (isset($request['id'])) {
$response['id'] = (string)$request['id'];
}
$response['domain'] = $this->domain;
$this->send($return, 'CDCResponse', $response);
}
/**
* Handle an append request.
*
* @param array $request The request.
* @return array The response.
*/
private function handleAppend(array $request) {
if (!isset($request['entityID'])) {
throw new SimpleSAML_Error_BadRequest('Missing entityID in append request.');
}
$entityID = (string)$request['entityID'];
$list = $this->getCDC();
$prevIndex = array_search($entityID, $list, TRUE);
if ($prevIndex !== FALSE) {
unset($list[$prevIndex]);
}
$list[] = $entityID;
$this->setCDC($list);
return 'ok';
}
/**
* Handle a delete request.
*
* @param array $request The request.
* @return array The response.
*/
private function handleDelete(array $request) {
setcookie('_saml_idp', 'DELETE', time() - 86400 , '/', '.' . $this->domain, TRUE);
return 'ok';
}
/**
* Handle a read request.
*
* @param array $request The request.
* @return array The response.
*/
private function handleRead(array $request) {
$list = $this->getCDC();
return array(
'status' => 'ok',
'cdc' => $list,
);
}
/**
* Helper function for parsing and validating a CDC message.
*
* @param string $parameter The name of the query parameter.
* @return array|NULL The response, or NULL if no response is received.
*/
private static function get($parameter) {
assert('is_string($parameter)');
if (!isset($_REQUEST[$parameter])) {
return NULL;
}
$message = (string)$_REQUEST[$parameter];
$message = @base64_decode($message);
if ($message === FALSE) {
throw new SimpleSAML_Error_BadRequest('Error base64-decoding CDC message.');
}
$message = @json_decode($message, TRUE);
if ($message === FALSE) {
throw new SimpleSAML_Error_BadRequest('Error json-decoding CDC message.');
}
if (!isset($message['timestamp'])) {
throw new SimpleSAML_Error_BadRequest('Missing timestamp in CDC message.');
}
$timestamp = (int)$message['timestamp'];
if ($timestamp + 60 < time()) {
throw new SimpleSAML_Error_BadRequest('CDC signature has expired.');
}
if ($timestamp - 60 > time()) {
throw new SimpleSAML_Error_BadRequest('CDC signature from the future.');
}
if (!isset($message['domain'])) {
throw new SimpleSAML_Error_BadRequest('Missing domain in CDC message.');
}
return $message;
}
/**
* Helper function for validating the signature on a CDC message.
*
* Will throw an exception if the message is invalid.
*
* @param string $parameter The name of the query parameter.
*/
private function validate($parameter) {
assert('is_string($parameter)');
assert('isset($_REQUEST[$parameter])');
$message = (string)$_REQUEST[$parameter];
if (!isset($_REQUEST['Signature'])) {
throw new SimpleSAML_Error_BadRequest('Missing Signature on CDC message.');
}
$signature = (string)$_REQUEST['Signature'];
$cSignature = $this->calcSignature($message);
if ($signature !== $cSignature) {
throw new SimpleSAML_Error_BadRequest('Invalid signature on CDC message.');
}
}
/**
* Helper function for sending CDC messages.
*
* @param string $to The URL the message should be delivered to.
* @param string $parameter The query parameter the message should be sent in.
* @param array $message The CDC message.
*/
private function send($to, $parameter, array $message) {
assert('is_string($to)');
assert('is_string($parameter)');
$message['timestamp'] = time();
$message = json_encode($message);
$message = base64_encode($message);
$signature = $this->calcSignature($message);
$params = array(
$parameter => $message,
'Signature' => $signature,
);
$url = SimpleSAML_Utilities::addURLparameter($to, $params);
if (strlen($url) < 2048) {
SimpleSAML_Utilities::redirect($url);
} else {
SimpleSAML_Utilities::postRedirect($to, $params);
}
}
/**
* Calculate the signature on the given message.
*
* @param string $rawMessage The base64-encoded message.
* @return string The signature.
*/
private function calcSignature($rawMessage) {
assert('is_string($rawMessage)');
return sha1($this->key . $rawMessage . $this->key);
}
/**
* Get the IdP entities saved in the common domain cookie.
*
* @return array List of IdP entities.
*/
private function getCDC() {
if (!isset($_COOKIE['_saml_idp'])) {
return array();
}
$ret = (string)$_COOKIE['_saml_idp'];
$ret = explode(' ', $ret);
foreach ($ret as &$idp) {
$idp = base64_decode($idp);
if ($idp === FALSE) {
/* Not properly base64 encoded. */
SimpleSAML_Logger::warning('CDC - Invalid base64-encoding of CDC entry.');
return array();
}
}
return $ret;
}
/**
* Build a CDC cookie string.
*
* @param array $list The list of IdPs.
* @return string The CDC cookie value.
*/
function setCDC(array $list) {
foreach ($list as &$value) {
$value = base64_encode($value);
}
$cookie = implode(' ', $list);
while (strlen($cookie) > 4000) {
/* The cookie is too long. Remove the oldest elements until it is short enough. */
$tmp = explode(' ', $cookie, 2);
if (count($tmp) === 1) {
/*
* We are left with a single entityID whose base64
* representation is too long to fit in a cookie.
*/
break;
}
$cookie = $tmp[1];
}
if ($this->cookieLifetime === 0) {
$expire = 0;
} else {
$expire = time() + $this->cookieLifetime;
}
setcookie('_saml_idp', $cookie, $expire, '/', '.' . $this->domain, TRUE);
}
}
<?php
if (!array_key_exists('domain', $_REQUEST)) {
throw new SimpleSAML_Error_BadRequest('Missing domain to CDC resume handler.');
}
$domain = (string)$_REQUEST['domain'];
$client = new sspmod_cdc_Client($domain);
$response = $client->getResponse();
if ($response === NULL) {
throw new SimpleSAML_Error_BadRequest('Missing CDC response to CDC resume handler.');
}
if (!isset($response['id'])) {
throw new SimpleSAML_Error_BadRequest('CDCResponse without id.');
}
$state = SimpleSAML_Auth_State::loadState($response['id'], 'cdc:resume');
SimpleSAML_Auth_ProcessingChain::resumeProcessing($state);
<?php
sspmod_cdc_Server::processRequest();
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment