runff 1.0 commit
This commit is contained in:
190
lib/SimpleSAML/Bindings/Shib13/Artifact.php
Executable file
190
lib/SimpleSAML/Bindings/Shib13/Artifact.php
Executable file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implementation of the Shibboleth 1.3 Artifact binding.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\Bindings\Shib13;
|
||||
|
||||
use SAML2\DOMDocumentFactory;
|
||||
use SimpleSAML\Utils\Config;
|
||||
use SimpleSAML\Utils\HTTP;
|
||||
use SimpleSAML\Utils\Random;
|
||||
use SimpleSAML\Utils\System;
|
||||
use SimpleSAML\Utils\Time;
|
||||
use SimpleSAML\Utils\XML;
|
||||
|
||||
class Artifact
|
||||
{
|
||||
|
||||
/**
|
||||
* Parse the query string, and extract the SAMLart parameters.
|
||||
*
|
||||
* This function is required because each query contains multiple
|
||||
* artifact with the same parameter name.
|
||||
*
|
||||
* @return array The artifacts.
|
||||
*/
|
||||
private static function getArtifacts()
|
||||
{
|
||||
assert(array_key_exists('QUERY_STRING', $_SERVER));
|
||||
|
||||
// We need to process the query string manually, to capture all SAMLart parameters
|
||||
|
||||
$artifacts = array();
|
||||
|
||||
$elements = explode('&', $_SERVER['QUERY_STRING']);
|
||||
foreach ($elements as $element) {
|
||||
list($name, $value) = explode('=', $element, 2);
|
||||
$name = urldecode($name);
|
||||
$value = urldecode($value);
|
||||
|
||||
if ($name === 'SAMLart') {
|
||||
$artifacts[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $artifacts;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build the request we will send to the IdP.
|
||||
*
|
||||
* @param array $artifacts The artifacts we will request.
|
||||
* @return string The request, as an XML string.
|
||||
*/
|
||||
private static function buildRequest(array $artifacts)
|
||||
{
|
||||
$msg = '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">' .
|
||||
'<SOAP-ENV:Body>' .
|
||||
'<samlp:Request xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol"' .
|
||||
' RequestID="' . Random::generateID() . '"' .
|
||||
' MajorVersion="1" MinorVersion="1"' .
|
||||
' IssueInstant="' . Time::generateTimestamp() . '"' .
|
||||
'>';
|
||||
|
||||
foreach ($artifacts as $a) {
|
||||
$msg .= '<samlp:AssertionArtifact>' . htmlspecialchars($a) . '</samlp:AssertionArtifact>';
|
||||
}
|
||||
|
||||
$msg .= '</samlp:Request>' .
|
||||
'</SOAP-ENV:Body>' .
|
||||
'</SOAP-ENV:Envelope>';
|
||||
|
||||
return $msg;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract the response element from the SOAP response.
|
||||
*
|
||||
* @param string $soapResponse The SOAP response.
|
||||
* @return string The <saml1p:Response> element, as a string.
|
||||
* @throws \SimpleSAML_Error_Exception
|
||||
*/
|
||||
private static function extractResponse($soapResponse)
|
||||
{
|
||||
assert(is_string($soapResponse));
|
||||
|
||||
try {
|
||||
$doc = DOMDocumentFactory::fromString($soapResponse);
|
||||
} catch (\Exception $e) {
|
||||
throw new \SimpleSAML_Error_Exception('Error parsing SAML 1 artifact response.');
|
||||
}
|
||||
|
||||
$soapEnvelope = $doc->firstChild;
|
||||
if (!XML::isDOMNodeOfType($soapEnvelope, 'Envelope', 'http://schemas.xmlsoap.org/soap/envelope/')) {
|
||||
throw new \SimpleSAML_Error_Exception('Expected artifact response to contain a <soap:Envelope> element.');
|
||||
}
|
||||
|
||||
$soapBody = XML::getDOMChildren($soapEnvelope, 'Body', 'http://schemas.xmlsoap.org/soap/envelope/');
|
||||
if (count($soapBody) === 0) {
|
||||
throw new \SimpleSAML_Error_Exception('Couldn\'t find <soap:Body> in <soap:Envelope>.');
|
||||
}
|
||||
$soapBody = $soapBody[0];
|
||||
|
||||
|
||||
$responseElement = XML::getDOMChildren($soapBody, 'Response', 'urn:oasis:names:tc:SAML:1.0:protocol');
|
||||
if (count($responseElement) === 0) {
|
||||
throw new \SimpleSAML_Error_Exception('Couldn\'t find <saml1p:Response> in <soap:Body>.');
|
||||
}
|
||||
$responseElement = $responseElement[0];
|
||||
|
||||
/*
|
||||
* Save the <saml1p:Response> element. Note that we need to import it
|
||||
* into a new document, in order to preserve namespace declarations.
|
||||
*/
|
||||
$newDoc = DOMDocumentFactory::create();
|
||||
$newDoc->appendChild($newDoc->importNode($responseElement, true));
|
||||
$responseXML = $newDoc->saveXML();
|
||||
|
||||
return $responseXML;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function receives a SAML 1.1 artifact.
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $spMetadata The metadata of the SP.
|
||||
* @param \SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
|
||||
* @return string The <saml1p:Response> element, as an XML string.
|
||||
* @throws \SimpleSAML_Error_Exception
|
||||
*/
|
||||
public static function receive(\SimpleSAML_Configuration $spMetadata, \SimpleSAML_Configuration $idpMetadata)
|
||||
{
|
||||
$artifacts = self::getArtifacts();
|
||||
$request = self::buildRequest($artifacts);
|
||||
|
||||
XML::debugSAMLMessage($request, 'out');
|
||||
|
||||
$url = $idpMetadata->getDefaultEndpoint('ArtifactResolutionService', array('urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding'));
|
||||
$url = $url['Location'];
|
||||
|
||||
$peerPublicKeys = $idpMetadata->getPublicKeys('signing', true);
|
||||
$certData = '';
|
||||
foreach ($peerPublicKeys as $key) {
|
||||
if ($key['type'] !== 'X509Certificate') {
|
||||
continue;
|
||||
}
|
||||
$certData .= "-----BEGIN CERTIFICATE-----\n" .
|
||||
chunk_split($key['X509Certificate'], 64) .
|
||||
"-----END CERTIFICATE-----\n";
|
||||
}
|
||||
|
||||
$file = System::getTempDir() . DIRECTORY_SEPARATOR . sha1($certData) . '.crt';
|
||||
if (!file_exists($file)) {
|
||||
System::writeFile($file, $certData);
|
||||
}
|
||||
|
||||
$spKeyCertFile = Config::getCertPath($spMetadata->getString('privatekey'));
|
||||
|
||||
$opts = array(
|
||||
'ssl' => array(
|
||||
'verify_peer' => true,
|
||||
'cafile' => $file,
|
||||
'local_cert' => $spKeyCertFile,
|
||||
'capture_peer_cert' => true,
|
||||
'capture_peer_chain' => true,
|
||||
),
|
||||
'http' => array(
|
||||
'method' => 'POST',
|
||||
'content' => $request,
|
||||
'header' => 'SOAPAction: http://www.oasis-open.org/committees/security' . "\r\n" .
|
||||
'Content-Type: text/xml',
|
||||
),
|
||||
);
|
||||
|
||||
// Fetch the artifact
|
||||
$response = HTTP::fetch($url, $opts);
|
||||
/** @var string $response */
|
||||
XML::debugSAMLMessage($response, 'in');
|
||||
|
||||
// Find the response in the SOAP message
|
||||
$response = self::extractResponse($response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
154
lib/SimpleSAML/Bindings/Shib13/HTTPPost.php
Executable file
154
lib/SimpleSAML/Bindings/Shib13/HTTPPost.php
Executable file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of the Shibboleth 1.3 HTTP-POST binding.
|
||||
*
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\Bindings\Shib13;
|
||||
|
||||
use SAML2\DOMDocumentFactory;
|
||||
use SimpleSAML\Utils\Crypto;
|
||||
use SimpleSAML\Utils\HTTP;
|
||||
use SimpleSAML\Utils\XML;
|
||||
use SimpleSAML\XML\Shib13\AuthnResponse;
|
||||
use SimpleSAML\XML\Signer;
|
||||
|
||||
class HTTPPost
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \SimpleSAML_Configuration
|
||||
*/
|
||||
private $configuration = null;
|
||||
|
||||
/**
|
||||
* @var \SimpleSAML_Metadata_MetaDataStorageHandler
|
||||
*/
|
||||
private $metadata = null;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor for the \SimpleSAML\Bindings\Shib13\HTTPPost class.
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $configuration The configuration to use.
|
||||
* @param \SimpleSAML_Metadata_MetaDataStorageHandler $metadatastore A store where to find metadata.
|
||||
*/
|
||||
public function __construct(
|
||||
\SimpleSAML_Configuration $configuration,
|
||||
\SimpleSAML_Metadata_MetaDataStorageHandler $metadatastore
|
||||
) {
|
||||
$this->configuration = $configuration;
|
||||
$this->metadata = $metadatastore;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send an authenticationResponse using HTTP-POST.
|
||||
*
|
||||
* @param string $response The response which should be sent.
|
||||
* @param \SimpleSAML_Configuration $idpmd The metadata of the IdP which is sending the response.
|
||||
* @param \SimpleSAML_Configuration $spmd The metadata of the SP which is receiving the response.
|
||||
* @param string|null $relayState The relaystate for the SP.
|
||||
* @param string $shire The shire which should receive the response.
|
||||
*/
|
||||
public function sendResponse(
|
||||
$response,
|
||||
\SimpleSAML_Configuration $idpmd,
|
||||
\SimpleSAML_Configuration $spmd,
|
||||
$relayState,
|
||||
$shire
|
||||
) {
|
||||
XML::checkSAMLMessage($response, 'saml11');
|
||||
|
||||
$privatekey = Crypto::loadPrivateKey($idpmd, true);
|
||||
$publickey = Crypto::loadPublicKey($idpmd, true);
|
||||
|
||||
$responsedom = DOMDocumentFactory::fromString(str_replace("\r", "", $response));
|
||||
|
||||
$responseroot = $responsedom->getElementsByTagName('Response')->item(0);
|
||||
$firstassertionroot = $responsedom->getElementsByTagName('Assertion')->item(0);
|
||||
|
||||
/* Determine what we should sign - either the Response element or the Assertion. The default is to sign the
|
||||
* Assertion, but that can be overridden by the 'signresponse' option in the SP metadata or
|
||||
* 'saml20.signresponse' in the global configuration.
|
||||
*
|
||||
* TODO: neither 'signresponse' nor 'shib13.signresponse' are valid options any longer. Remove!
|
||||
*/
|
||||
if ($spmd->hasValue('signresponse')) {
|
||||
$signResponse = $spmd->getBoolean('signresponse');
|
||||
} else {
|
||||
$signResponse = $this->configuration->getBoolean('shib13.signresponse', true);
|
||||
}
|
||||
|
||||
// check if we have an assertion to sign. Force to sign the response if not
|
||||
if ($firstassertionroot === null) {
|
||||
$signResponse = true;
|
||||
}
|
||||
|
||||
$signer = new Signer(array(
|
||||
'privatekey_array' => $privatekey,
|
||||
'publickey_array' => $publickey,
|
||||
'id' => ($signResponse ? 'ResponseID' : 'AssertionID'),
|
||||
));
|
||||
|
||||
if ($idpmd->hasValue('certificatechain')) {
|
||||
$signer->addCertificate($idpmd->getString('certificatechain'));
|
||||
}
|
||||
|
||||
if ($signResponse) {
|
||||
// sign the response - this must be done after encrypting the assertion
|
||||
// we insert the signature before the saml2p:Status element
|
||||
$statusElements = XML::getDOMChildren($responseroot, 'Status', '@saml1p');
|
||||
assert(count($statusElements) === 1);
|
||||
$signer->sign($responseroot, $responseroot, $statusElements[0]);
|
||||
} else {
|
||||
// Sign the assertion
|
||||
$signer->sign($firstassertionroot, $firstassertionroot);
|
||||
}
|
||||
|
||||
$response = $responsedom->saveXML();
|
||||
|
||||
XML::debugSAMLMessage($response, 'out');
|
||||
|
||||
HTTP::submitPOSTData($shire, array(
|
||||
'TARGET' => $relayState,
|
||||
'SAMLResponse' => base64_encode($response),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decode a received response.
|
||||
*
|
||||
* @param array $post POST data received.
|
||||
* @return \SimpleSAML\XML\Shib13\AuthnResponse The response decoded into an object.
|
||||
* @throws \Exception If there is no SAMLResponse parameter.
|
||||
*/
|
||||
public function decodeResponse($post)
|
||||
{
|
||||
assert(is_array($post));
|
||||
|
||||
if (!array_key_exists('SAMLResponse', $post)) {
|
||||
throw new \Exception('Missing required SAMLResponse parameter.');
|
||||
}
|
||||
$rawResponse = $post['SAMLResponse'];
|
||||
$samlResponseXML = base64_decode($rawResponse);
|
||||
|
||||
XML::debugSAMLMessage($samlResponseXML, 'in');
|
||||
|
||||
XML::checkSAMLMessage($samlResponseXML, 'saml11');
|
||||
|
||||
$samlResponse = new AuthnResponse();
|
||||
$samlResponse->setXML($samlResponseXML);
|
||||
|
||||
if (array_key_exists('TARGET', $post)) {
|
||||
$samlResponse->setRelayState($post['TARGET']);
|
||||
}
|
||||
|
||||
return $samlResponse;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user