From 93bca0538e52ca333f52ed13434b1691e70f961a Mon Sep 17 00:00:00 2001
From: Hans Zandbelt <hans.zandbelt@surfnet.nl>
Date: Mon, 19 May 2008 09:15:44 +0000
Subject: [PATCH] initial support for connecting to WS-Fed/ADFS IDPs

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@581 44740490-163a-0410-bde0-09ae8108e29a
---
 config-templates/config.php                   |   2 +
 .../MetaDataStorageHandlerFlatfile.php        |   3 +-
 metadata-templates/wsfed-idp-remote.php       |  12 ++
 metadata-templates/wsfed-sp-hosted.php        |  10 ++
 www/example-simple/wsfed-example.php          |  34 +++++
 www/wsfed/sp/AssertionConsumerService.php     | 121 ++++++++++++++++++
 www/wsfed/sp/initSSO.php                      |  71 ++++++++++
 7 files changed, 252 insertions(+), 1 deletion(-)
 create mode 100644 metadata-templates/wsfed-idp-remote.php
 create mode 100644 metadata-templates/wsfed-sp-hosted.php
 create mode 100644 www/example-simple/wsfed-example.php
 create mode 100644 www/wsfed/sp/AssertionConsumerService.php
 create mode 100644 www/wsfed/sp/initSSO.php

diff --git a/config-templates/config.php b/config-templates/config.php
index d43a1c065..0429b10f5 100644
--- a/config-templates/config.php
+++ b/config-templates/config.php
@@ -113,6 +113,7 @@ $config = array (
 	'enable.saml20-idp'		=> false,
 	'enable.shib13-sp'		=> false,
 	'enable.shib13-idp'		=> false,
+	'enable.wsfed-sp'		=> false,
 	'enable.openid-provider'=> false,
 	'enable.authmemcookie' => false,
 
@@ -169,6 +170,7 @@ $config = array (
 	 */
 	'default-saml20-idp' => 'https://openidp.feide.no',
 	'default-shib13-idp' => NULL,
+	'default-wsfed-idp'	=> 'urn:federation:pingfederate:localhost',
 	
 	/*
 	 * IdP Discovery service look configuration. 
diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatfile.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatfile.php
index e4a0fbcd6..d4533d4fa 100644
--- a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatfile.php
+++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatfile.php
@@ -9,7 +9,7 @@ require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSA
  * Instantiation of session handler objects should be done through
  * the class method getMetadataHandler().
  *
- * @author Andreas Ĺkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
+ * @author Andreas �kre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
  * @package simpleSAMLphp
  * @version $Id$
  */
@@ -21,6 +21,7 @@ class SimpleSAML_Metadata_MetaDataStorageHandlerFlatFile extends SimpleSAML_Meta
 	private static $validSets = array(
 		'saml20-sp-hosted', 'saml20-sp-remote','saml20-idp-hosted', 'saml20-idp-remote',
 		'shib13-sp-hosted', 'shib13-sp-remote', 'shib13-idp-hosted', 'shib13-idp-remote',
+		'wsfed-sp-hosted', 'wsfed-idp-remote',
 		'openid-provider'
 		);
 
diff --git a/metadata-templates/wsfed-idp-remote.php b/metadata-templates/wsfed-idp-remote.php
new file mode 100644
index 000000000..b7c11596a
--- /dev/null
+++ b/metadata-templates/wsfed-idp-remote.php
@@ -0,0 +1,12 @@
+<?php
+
+$metadata = array( 
+
+	'urn:federation:pingfederate:localhost' => array(
+		'host' => 'localhost',
+		'prp' => 'https://localhost:9031/idp/prp.wsf',
+		'cert' => '/cert/pingfed-localhost.pem',
+	),	
+);
+
+?>
diff --git a/metadata-templates/wsfed-sp-hosted.php b/metadata-templates/wsfed-sp-hosted.php
new file mode 100644
index 000000000..d588ba776
--- /dev/null
+++ b/metadata-templates/wsfed-sp-hosted.php
@@ -0,0 +1,10 @@
+<?php
+
+$metadata = array( 
+	'urn:federation:simplesamlphp:localhost' => array(
+		'host'				=>	'localhost',
+		'realm' => 'urn:federation:simplesamlphp:localhost',
+	),
+);
+
+?>
diff --git a/www/example-simple/wsfed-example.php b/www/example-simple/wsfed-example.php
new file mode 100644
index 000000000..ed17ef1dc
--- /dev/null
+++ b/www/example-simple/wsfed-example.php
@@ -0,0 +1,34 @@
+<?php
+
+require_once('../_include.php');
+
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Utilities.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Session.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/XHTML/Template.php');
+
+
+$config = SimpleSAML_Configuration::getInstance();
+$session = SimpleSAML_Session::getInstance();
+
+if (!$session->isValid('wsfed') ) {
+	SimpleSAML_Utilities::redirect(
+		'/' . $config->getBaseURL() . 'wsfed/sp/initSSO.php',
+		array('RelayState' => SimpleSAML_Utilities::selfURL())
+	);
+}
+
+$attributes = $session->getAttributes();
+
+$t = new SimpleSAML_XHTML_Template($config, 'status.php', 'attributes.php');
+
+$t->data['header'] = 'WS-Fed SP Demo Example';
+$t->data['remaining'] = $session->remainingTime();
+$t->data['sessionsize'] = $session->getSize();
+$t->data['attributes'] = $attributes;
+$t->data['icon'] = 'bino.png';
+$t->data['logout'] = '[ <a href="/' . $config->getBaseURL() . 'wsfed/sp/initSLO.php?RelayState=/' . 
+	$config->getBaseURL() . 'logout.html">Logout</a> ]';
+$t->show();
+
+
+?>
diff --git a/www/wsfed/sp/AssertionConsumerService.php b/www/wsfed/sp/AssertionConsumerService.php
new file mode 100644
index 000000000..f749987a8
--- /dev/null
+++ b/www/wsfed/sp/AssertionConsumerService.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * WS-Federation/ADFS PRP protocol support for simpleSAMLphp.
+ *
+ * The AssertionConsumerService handler accepts responses from a WS-Federation
+ * Account Partner using the Passive Requestor Profile (PRP) and handles it as
+ * a Resource Partner.  It receives a response, parses it and passes on the
+ * authentication+attributes.
+ *
+ * @author Hans Zandbelt, SURFnet BV. <hans.zandbelt@surfnet.nl>
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+
+require_once('../../_include.php');
+
+require_once('xmlseclibs.php');
+
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Configuration.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Utilities.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Session.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Logger.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/XML/AttributeFilter.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Metadata/MetaDataStorageHandler.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/XHTML/Template.php');
+
+$config = SimpleSAML_Configuration::getInstance();
+$session = SimpleSAML_Session::getInstance();
+$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+
+SimpleSAML_Logger::info('WS-Fed - SP.AssertionConsumerService: Accessing WS-Fed SP endpoint AssertionConsumerService');
+
+if (!$config->getValue('enable.wsfed-sp', false))
+	SimpleSAML_Utilities::fatalError($session->getTrackID(), 'NOACCESS');
+
+if (empty($_POST['wresult'])) 
+	SimpleSAML_Utilities::fatalError($session->getTrackID(), 'ACSPARAMS', $exception);
+
+// verify the response from the Account Partner, containing the assertion
+function wsf_verify_response($dom, $cert) {
+	$objXMLSecDSig = new XMLSecurityDSig();
+	$objXMLSecDSig->idKeys[] = 'AssertionID';
+	$signatureElement = $objXMLSecDSig->locateSignature($dom);
+	if  (!$signatureElement) {
+		throw new Exception('Could not locate XML Signature element.');
+	}
+	$objXMLSecDSig->canonicalizeSignedInfo();
+	if (!$objXMLSecDSig->validateReference()) {
+		throw new Exception('XMLsec: digest validation failed');
+	}
+	$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'public'));
+	$objKey->loadKey($cert, TRUE, TRUE);
+	if (! $objXMLSecDSig->verify($objKey)) {
+		throw new Exception('Unable to validate Signature');
+	}
+}
+
+try {
+	
+	$idpmetadata = $metadata->getMetaData($session->getIdP(), 'wsfed-idp-remote');
+	$spmetadata = $metadata->getMetaDataCurrent();
+
+	$wa = $_POST['wa'];
+	$wresult = $_POST['wresult'];
+	$wctx = $_POST['wctx'];
+		
+	$attributes = array();
+	$dom = new DOMDocument();
+	# accommodate for MS-ADFS escaped quotes
+	$wresult = str_replace('\"', '"', $wresult);
+	$dom->loadXML(str_replace ("\r", "", $wresult));	
+
+	wsf_verify_response($dom, $config->getBaseDir() . $idpmetadata['cert']);
+
+	$session = SimpleSAML_Session::getInstance(true);
+	
+	$xpath = new DOMXpath($dom);
+	$xpath->registerNamespace('wst', 'http://schemas.xmlsoap.org/ws/2005/02/trust');
+	$xpath->registerNamespace('saml', 'urn:oasis:names:tc:SAML:1.0:assertion');
+	$assertions = $xpath->query('/wst:RequestSecurityTokenResponse/wst:RequestedSecurityToken/saml:Assertion', $dom->documentElement);
+	foreach ($assertions as $assertion) {
+		$statement = $xpath->query('saml:AuthenticationStatement', $assertion);
+		if (($statement == NULL) or ($statement->item(0) == NULL)) {
+			throw new Exception('no authentication statement found');
+		}
+		// TODO: only process first authentication statement for now;
+		$subject = $xpath->query('saml:Subject', $statement->item(0));
+		if (($subject == NULL) or ($subject->item(0) == NULL)) {
+			throw new Exception('no subject found in authentication statement');
+		}
+		$nameid = $xpath->query('saml:NameIdentifier', $subject->item(0));
+		if (($nameid == NULL) or ($nameid->item(0) == NULL)) {
+			throw new Exception('no nameid found in subject in authentication statement');
+		}
+		$session->setNameID(array(
+				'Format' => $nameid->item(0)->getAttribute('Format'),
+				'value' => $nameid->item(0)->textContent,
+			)
+		);
+		$statement = $xpath->query('saml:AttributeStatement', $assertion);
+		if (($statement != NULL) and ($statement->item(0) != NULL)) {
+			foreach ($xpath->query('saml:Attribute/saml:AttributeValue', $statement->item(0)) as $attribute) {
+				$name = $attribute->parentNode->getAttribute('AttributeName');
+				$value = $attribute->textContent;
+				if(!array_key_exists($name, $attributes)) {
+					$attributes[$name] = array();
+				}
+				$attributes[$name][] = $value;
+			}
+		}
+		// TODO: only process first assertion for now;
+		break;		
+	}	
+	$session->setAuthenticated(true, 'wsfed');
+	$session->setAttributes($attributes);
+		
+	SimpleSAML_Utilities::redirect($wctx);
+	
+} catch(Exception $exception) {		
+	SimpleSAML_Utilities::fatalError($session->getTrackID(), 'PROCESSASSERTION', $exception);
+}
diff --git a/www/wsfed/sp/initSSO.php b/www/wsfed/sp/initSSO.php
new file mode 100644
index 000000000..cbd0f2a16
--- /dev/null
+++ b/www/wsfed/sp/initSSO.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * WS-Federation/ADFS PRP protocol support for simpleSAMLphp.
+ *
+ * The initSSO handler relays an internal request from a simpleSAMLphp
+ * Service Provider as a WS-Federation Resource Partner using the Passive
+ * Requestor Profile (PRP) to an Account Partner.
+ *
+ * @author Hans Zandbelt, SURFnet BV. <hans.zandbelt@surfnet.nl>
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+
+require_once('../../_include.php');
+
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Utilities.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Session.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Logger.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/XHTML/Template.php');
+require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Metadata/MetaDataStorageHandler.php');
+
+$config = SimpleSAML_Configuration::getInstance();
+$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
+$session = SimpleSAML_Session::getInstance();
+
+SimpleSAML_Logger::info('WS-Fed - SP.initSSO: Accessing WS-Fed SP initSSO script');
+
+if (!$config->getValue('enable.wsfed-sp', false))
+	SimpleSAML_Utilities::fatalError($session->getTrackID(), 'NOACCESS');
+
+if (empty($_GET['RelayState'])) {
+	SimpleSAML_Utilities::fatalError($session->getTrackID(), 'NORELAYSTATE');
+}
+
+try {
+
+	$idpentityid = isset($_GET['idpentityid']) ? $_GET['idpentityid'] : $config->getValue('default-wsfed-idp') ;
+	$spentityid = isset($_GET['spentityid']) ? $_GET['spentityid'] : $metadata->getMetaDataCurrentEntityID('wsfed-sp-hosted');
+
+} catch (Exception $exception) {
+	SimpleSAML_Utilities::fatalError($session->getTrackID(), 'METADATA', $exception);
+}
+
+if ($idpentityid == null) {
+
+	SimpleSAML_Logger::info('WS-Fed - SP.initSSO: No chosen or default IdP, go to WSFeddisco');
+
+	SimpleSAML_Utilities::redirect('/' . $config->getBaseURL() . 'wsfed/sp/idpdisco.php', array(
+		'entityID' => $spentityid,
+		'return' => SimpleSAML_Utilities::selfURL(),
+		'returnIDParam' => 'idpentityid')
+	);
+}
+
+try {
+	$relaystate = $_GET['RelayState'];
+	
+	$idpmeta = $metadata->getMetaData($idpentityid, 'wsfed-idp-remote');
+	$spmeta = $metadata->getMetaData($spentityid, 'wsfed-sp-hosted');
+	
+	$url = $idpmeta['prp'] .
+		'?wa=wsignin1.0' .
+		'&wct=' . gmdate("Y-m-d\TH:i:s\Z", time()) .
+		'&wtrealm=' . $spmeta['realm'] .
+		'&wctx=' . urlencode($relaystate);
+
+	SimpleSAML_Utilities::redirect($url);
+	
+} catch (Exception $exception) {
+	SimpleSAML_Utilities::fatalError($session->getTrackID(), 'CREATEREQUEST', $exception);
+}
-- 
GitLab