Revision: 1830
Author: olavmrk
Date: Thu Oct 8 02:55:07 2009
Log: saml: Add SAML 1 artifact support.
http://code.google.com/p/simplesamlphp/source/detail?r=1830
Added:
/trunk/lib/SimpleSAML/Bindings/Shib13/Artifact.php
Modified:
/trunk/docs/simplesamlphp-reference-idp-remote.txt
/trunk/lib/SimpleSAML/XML/Shib13/AuthnResponse.php
/trunk/modules/saml/docs/sp.txt
/trunk/modules/saml/lib/Auth/Source/SP.php
/trunk/modules/saml/www/sp/saml1-acs.php
=======================================
--- /dev/null
+++ /trunk/lib/SimpleSAML/Bindings/Shib13/Artifact.php Thu Oct 8 02:55:07
2009
@@ -0,0 +1,172 @@
+<?php
+
+/**
+ * Implementation of the Shibboleth 1.3 Artifact binding.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SimpleSAML_Bindings_Shib13_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="' . SimpleSAML_Utilities::generateID() .
'"' .
+ ' MajorVersion="1" MinorVersion="1"' .
+ ' IssueInstant="' .
SimpleSAML_Utilities::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.
+ */
+ private static function extractResponse($soapResponse) {
+ assert('is_string($soapResponse)');
+
+ $doc = new DOMDocument();
+ if (!$doc->loadXML($soapResponse)) {
+ throw new SimpleSAML_Error_Exception('Error parsing
SAML 1 artifact
response.');
+ }
+
+ $soapEnvelope = $doc->firstChild;
+ if
(!SimpleSAML_Utilities::isDOMElementOfType($soapEnvelope, 'Envelope',
'http://schemas.xmlsoap.org/soap/envelope/'))
{
+ throw new SimpleSAML_Error_Exception('Expected artifact
response to
contain a <soap:Envelope> element.');
+ }
+
+ $soapBody =
SimpleSAML_Utilities::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 =
SimpleSAML_Utilities::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 = new DOMDocument();
+ $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.
+ */
+ public static function receive(SimpleSAML_Configuration $spMetadata,
SimpleSAML_Configuration $idpMetadata) {
+
+ $artifacts = self::getArtifacts();
+ $request = self::buildRequest($artifacts);
+
+ $url = 'https://skjak.uninett.no:1245/test...';
+ $url = $idpMetadata->getString('ArtifactResolutionService');
+
+ $certData =
SimpleSAML_Utilities::loadPublicKey($idpMetadata->toArray(),
TRUE);
+ if (!array_key_exists('PEM', $certData)) {
+ throw new SimpleSAML_Error_Exception('Missing one of
certData or
certificate in metadata for '
+ .
var_export($idpMetadata->getString('entityid'), TRUE));
+ }
+ $certData = $certData['PEM'];
+
+ $file = SimpleSAML_Utilities::getTempDir() . '/' .
sha1($certData) . '.crt';
+ if (!file_exists($file)) {
+ SimpleSAML_Utilities::writeFile($file, $certData);
+ }
+
+ $globalConfig = SimpleSAML_Configuration::getInstance();
+ $spKeyCertFile = $globalConfig->getPathValue('certdir',
'cert/') .
$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',
+ ),
+ );
+ $context = stream_context_create($opts);
+
+ /* Fetch the artifact. */
+ $response = file_get_contents($url, FALSE, $context);
+ if ($response === FALSE) {
+ throw new SimpleSAML_Error_Exception('Failed to
retrieve assertion from
IdP.');
+ }
+
+ /* Find the response in the SOAP message. */
+ $response = self::extractResponse($response);
+
+ return $response;
+ }
+
+}
=======================================
--- /trunk/docs/simplesamlphp-reference-idp-remote.txt Tue Apr 28 00:29:03
2009
+++ /trunk/docs/simplesamlphp-reference-idp-remote.txt Thu Oct 8 02:55:07
2009
@@ -122,7 +122,14 @@
Shibboleth 1.3 options
----------------------
-There are no options specific for a Shibboleth 1.3 IdP.
+`saml1.useartifact`
+: Request that the IdP returns the result to the artifact binding.
+ The default is to use the POST binding, set this option to TRUE to use
the artifact binding instead.
+
+: This option can be set for all IdPs connected to a SP by setting it in
the entry for the SP in `config/authsources.php`.
+
+: *Note*: This option only works with the `saml:SP` authentication
source.
+
Examples
=======================================
--- /trunk/lib/SimpleSAML/XML/Shib13/AuthnResponse.php Wed Aug 19 04:07:27
2009
+++ /trunk/lib/SimpleSAML/XML/Shib13/AuthnResponse.php Thu Oct 8 02:55:07
2009
@@ -15,6 +15,14 @@
private $validator = null;
+ /**
+ * Whether this response was validated by some external means (e.g.
SSL).
+ *
+ * @var bool
+ */
+ private $messageValidated = FALSE;
+
+
const SHIB_PROTOCOL_NS = 'urn:oasis:names:tc:SAML:1.0:protocol';
const SHIB_ASSERT_NS = 'urn:oasis:names:tc:SAML:1.0:assertion';
@@ -32,6 +40,18 @@
* @var string|NULL
*/
private $relayState = null;
+
+
+ /**
+ * Set whether this message was validated externally.
+ *
+ * @param bool $messageValidated TRUE if the message is already
validated, FALSE if not.
+ */
+ public function setMessageValidated($messageValidated) {
+ assert('is_bool($messageValidated)');
+
+ $this->messageValidated = $messageValidated;
+ }
public function setXML($xml) {
@@ -55,6 +75,11 @@
public function validate() {
assert('$this->dom instanceof DOMDocument');
+ if ($this->messageValidated) {
+ /* This message was validated externally. */
+ return TRUE;
+ }
+
/* Validate the signature. */
$this->validator = new SimpleSAML_XML_Validator($this->dom,
array('ResponseID', 'AssertionID'));
@@ -90,6 +115,11 @@
*/
private function isNodeValidated($node) {
+ if ($this->messageValidated) {
+ /* This message was validated externally. */
+ return TRUE;
+ }
+
if($this->validator === NULL) {
return FALSE;
}
=======================================
--- /trunk/modules/saml/docs/sp.txt Tue Sep 29 04:12:16 2009
+++ /trunk/modules/saml/docs/sp.txt Thu Oct 8 02:55:07 2009
@@ -130,6 +130,14 @@
: *Note*: SAML 2 specific.
+`saml1.useartifact`
+: Request that the IdP returns the result to the artifact binding.
+ The default is to use the POST binding, set this option to TRUE to use
the artifact binding instead.
+
+: This option can also be set in the `shib13-idp-remote` metadata, in
which case the setting in `shib13-idp-remote` takes precedence.
+
+: *Note*: SAML 1 specific.
+
`redirect.sign`
: Whether authentication requests, logout requests and logout responses
sent from this SP should be signed. The default is `FALSE`.
=======================================
--- /trunk/modules/saml/lib/Auth/Source/SP.php Tue Sep 29 04:12:07 2009
+++ /trunk/modules/saml/lib/Auth/Source/SP.php Thu Oct 8 02:55:07 2009
@@ -149,7 +149,18 @@
$id = SimpleSAML_Auth_State::saveState($state,
'saml:sp:ssosent-saml1');
$ar->setRelayState($id);
- $url = $ar->createRedirect($idpEntityId,
SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' . $this->authId));
+ $useArtifact = $idpMetadata->getBoolean('saml1.useartifact',
NULL);
+ if ($useArtifact === NULL) {
+ $useArtifact =
$this->metadata->getBoolean('saml1.useartifact', FALSE);
+ }
+
+ if ($useArtifact) {
+ $shire =
SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' .
$this->authId . '/artifact');
+ } else {
+ $shire =
SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' .
$this->authId);
+ }
+
+ $url = $ar->createRedirect($idpEntityId, $shire);
SimpleSAML_Logger::debug('Starting SAML 1 SSO to ' .
var_export($idpEntityId, TRUE) .
' from ' . var_export($this->entityId, TRUE) . '.');
=======================================
--- /trunk/modules/saml/www/sp/saml1-acs.php Thu Sep 24 04:54:08 2009
+++ /trunk/modules/saml/www/sp/saml1-acs.php Thu Oct 8 02:55:07 2009
@@ -1,7 +1,7 @@
<?php
-if (!array_key_exists('SAMLResponse', $_REQUEST)) {
- throw new SimpleSAML_Error_BadRequest('Missing SAMLResponse
parameter.');
+if (!array_key_exists('SAMLResponse', $_REQUEST)
&& !array_key_exists('SAMLart', $_REQUEST)) {
+ throw new SimpleSAML_Error_BadRequest('Missing SAMLResponse or SAMLart
parameter.');
}
if (!array_key_exists('TARGET', $_REQUEST)) {
@@ -25,16 +25,27 @@
if ($state['saml:sp:AuthId'] !== $sourceId) {
throw new SimpleSAML_Error_Exception('The authentication source id in
the
URL does not match the authentication source which sent the request.');
}
+
+$spMetadata = $source->getMetadata();
$idpEntityId = $state['saml:idp'];
$idpMetadata = $source->getIdPMetadata($idpEntityId);
-$responseXML = $_REQUEST['SAMLResponse'];
-$responseXML = base64_decode($responseXML);
+if (array_key_exists('SAMLart', $_REQUEST)) {
+ $responseXML =
SimpleSAML_Bindings_Shib13_Artifact::receive($spMetadata,
$idpMetadata);
+ $isValidated = TRUE; /* Artifact binding validated with ssl
certificate.
*/
+} elseif (array_key_exists('SAMLResponse', $_REQUEST)) {
+ $responseXML = $_REQUEST['SAMLResponse'];
+ $responseXML = base64_decode($responseXML);
+ $isValidated = FALSE; /* Must check signature on response. */
+} else {
+ assert('FALSE');
+}
$response = new SimpleSAML_XML_Shib13_AuthnResponse();
$response->setXML($responseXML);
+$response->setMessageValidated($isValidated);
$response->validate();
$responseIssuer = $response->getIssuer();
--~--~---------~--~----~------------~-------~--~----~
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
-~----------~----~----~----~------~----~------~--~---