This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
spip-saml/inc/simplesamlphp/lib/SAML2/HTTPRedirect.php

235 lines
5.8 KiB
PHP

<?php
/**
* Class which implements the HTTP-Redirect binding.
*
* @package simpleSAMLphp
* @version $Id$
*/
class SAML2_HTTPRedirect extends SAML2_Binding {
const DEFLATE = 'urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE';
/**
* Create the redirect URL for a message.
*
* @param SAML2_Message $message The message.
* @return string The URL the user should be redirected to in order to send a message.
*/
public function getRedirectURL(SAML2_Message $message) {
if ($this->destination === NULL) {
$destination = $message->getDestination();
} else {
$destination = $this->destination;
}
$relayState = $message->getRelayState();
$key = $message->getSignatureKey();
$msgStr = $message->toUnsignedXML();
$msgStr = $msgStr->ownerDocument->saveXML($msgStr);
SimpleSAML_Utilities::debugMessage($msgStr, 'out');
$msgStr = gzdeflate($msgStr);
$msgStr = base64_encode($msgStr);
/* Build the query string. */
if ($message instanceof SAML2_Request) {
$msg = 'SAMLRequest=';
} else {
$msg = 'SAMLResponse=';
}
$msg .= urlencode($msgStr);
if ($relayState !== NULL) {
$msg .= '&RelayState=' . urlencode($relayState);
}
if ($key !== NULL) {
/* Add the signature. */
$msg .= '&SigAlg=' . urlencode(XMLSecurityKey::RSA_SHA1);
$signature = $key->signData($msg);
$msg .= '&Signature=' . urlencode(base64_encode($signature));
}
if (strpos($destination, '?') === FALSE) {
$destination .= '?' . $msg;
} else {
$destination .= '&' . $msg;
}
return $destination;
}
/**
* Send a SAML 2 message using the HTTP-Redirect binding.
*
* Note: This function never returns.
*
* @param SAML2_Message $message The message we should send.
*/
public function send(SAML2_Message $message) {
$destination = $this->getRedirectURL($message);
SimpleSAML_Logger::debug('Redirect to ' . strlen($destination) . ' byte URL: ' . $destination);
SimpleSAML_Utilities::redirect($destination);
}
/**
* Receive a SAML 2 message sent using the HTTP-Redirect binding.
*
* Throws an exception if it is unable receive the message.
*
* @return SAML2_Message The received message.
*/
public function receive() {
$data = self::parseQuery();
if (array_key_exists('SAMLRequest', $data)) {
$msg = $data['SAMLRequest'];
} elseif (array_key_exists('SAMLResponse', $data)) {
$msg = $data['SAMLResponse'];
} else {
throw new Exception('Missing SAMLRequest or SAMLResponse parameter.');
}
if (array_key_exists('SAMLEncoding', $data)) {
$encoding = $data['SAMLEncoding'];
} else {
$encoding = self::DEFLATE;
}
$msg = base64_decode($msg);
switch ($encoding) {
case self::DEFLATE:
$msg = gzinflate($msg);
break;
default:
throw new Exception('Unknown SAMLEncoding: ' . var_export($encoding, TRUE));
}
SimpleSAML_Utilities::debugMessage($msg, 'in');
$document = new DOMDocument();
$document->loadXML($msg);
$xml = $document->firstChild;
$msg = SAML2_Message::fromXML($xml);
if (array_key_exists('Signature', $data)) {
/* Save the signature validation data until we need it. */
$signatureValidationData = array(
'Signature' => $data['Signature'],
'Query' => $data['SignedQuery'],
);
}
if (array_key_exists('RelayState', $data)) {
$msg->setRelayState($data['RelayState']);
}
if (array_key_exists('Signature', $data)) {
if (!array_key_exists('SigAlg', $data)) {
throw new Exception('Missing signature algorithm.');
}
$signData = array(
'Signature' => $data['Signature'],
'SigAlg' => $data['SigAlg'],
'Query' => $data['SignedQuery'],
);
$msg->addValidator(array(get_class($this), 'validateSignature'), $signData);
}
return $msg;
}
/**
* Helper function to parse query data.
*
* This function returns the query string split into key=>value pairs.
* It also adds a new parameter, SignedQuery, which contains the data that is
* signed.
*
* @return string The query data that is signed.
*/
private static function parseQuery() {
/*
* Parse the query string. We need to do this ourself, so that we get access
* to the raw (urlencoded) values. This is required because different software
* can urlencode to different values.
*/
$data = array();
$relayState = '';
$sigAlg = '';
foreach (explode('&', $_SERVER['QUERY_STRING']) as $e) {
list($name, $value) = explode('=', $e, 2);
$name = urldecode($name);
$data[$name] = urldecode($value);
switch ($name) {
case 'SAMLRequest':
case 'SAMLResponse':
$sigQuery = $name . '=' . $value;
break;
case 'RelayState':
$relayState = '&RelayState=' . $value;
break;
case 'SigAlg':
$sigAlg = '&SigAlg=' . $value;
break;
}
}
$data['SignedQuery'] = $sigQuery . $relayState . $sigAlg;
return $data;
}
/**
* Validate the signature on a HTTP-Redirect message.
*
* Throws an exception if we are unable to validate the signature.
*
* @param array $data The data we need to validate the query string.
* @param XMLSecurityKey $key The key we should validate the query against.
*/
public static function validateSignature(array $data, XMLSecurityKey $key) {
assert('array_key_exists("Query", $data)');
assert('array_key_exists("SigAlg", $data)');
assert('array_key_exists("Signature", $data)');
$query = $data['Query'];
$sigAlg = $data['SigAlg'];
$signature = $data['Signature'];
$signature = base64_decode($signature);
switch ($sigAlg) {
case XMLSecurityKey::RSA_SHA1:
if ($key->type !== XMLSecurityKey::RSA_SHA1) {
throw new Exception('Invalid key type for validating signature on query string.');
}
if (!$key->verifySignature($query,$signature)) {
throw new Exception('Unable to validate signature on query string.');
}
break;
default:
throw new Exception('Unknown signature algorithm: ' . var_export($sigAlg, TRUE));
}
}
}
?>