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