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
-~----------~----~----~----~------~----~------~--~---

Reply via email to