From 8bc1cbc2f1cc716bd422c57d7bd4119b9c34e468 Mon Sep 17 00:00:00 2001 From: Patrick Radtke <patrick@cirrusidentity.com> Date: Wed, 24 Feb 2016 13:56:32 -0800 Subject: [PATCH] RegistrationInfo can be inherited from EntitiesDescriptor --- lib/SimpleSAML/Metadata/SAMLParser.php | 46 +++++++++++--- .../SimpleSAML/Metadata/SAMLParserTest.php | 62 ++++++++++++++++--- 2 files changed, 91 insertions(+), 17 deletions(-) diff --git a/lib/SimpleSAML/Metadata/SAMLParser.php b/lib/SimpleSAML/Metadata/SAMLParser.php index 2786fff5a..7955fba22 100644 --- a/lib/SimpleSAML/Metadata/SAMLParser.php +++ b/lib/SimpleSAML/Metadata/SAMLParser.php @@ -159,11 +159,13 @@ class SimpleSAML_Metadata_SAMLParser * @param int|NULL $maxExpireTime The unix timestamp for when this entity should expire, or * NULL if unknown. * @param array $validators An array of parent elements that may validate this element. + * @param array $parentExtensions An optional array of extensions from the parent element. */ private function __construct( SAML2_XML_md_EntityDescriptor $entityElement, $maxExpireTime, - array $validators = array() + array $validators = array(), + array $parentExtensions = null ) { assert('is_null($maxExpireTime) || is_int($maxExpireTime)'); @@ -181,7 +183,7 @@ class SimpleSAML_Metadata_SAMLParser $this->validators[] = $entityElement; // process Extensions element, if it exists - $ext = self::processExtensions($entityElement); + $ext = self::processExtensions($entityElement, $parentExtensions); $this->scopes = $ext['scope']; $this->tags = $ext['tags']; $this->entityAttributes = $ext['EntityAttributes']; @@ -379,15 +381,21 @@ class SimpleSAML_Metadata_SAMLParser * of the entities. * @param array $validators The parent-elements that may be * signed. + * @param array $parentExtensions An optional array of + * extensions from the parent element. * * @return SimpleSAML_Metadata_SAMLParser[] Array of SAMLParser instances. */ - private static function processDescriptorsElement($element, $maxExpireTime = null, array $validators = array()) - { + private static function processDescriptorsElement( + $element, + $maxExpireTime = null, + array $validators = array(), + array $parentExtensions = array() + ) { assert('is_null($maxExpireTime) || is_int($maxExpireTime)'); if ($element instanceof SAML2_XML_md_EntityDescriptor) { - $ret = new SimpleSAML_Metadata_SAMLParser($element, $maxExpireTime, $validators); + $ret = new SimpleSAML_Metadata_SAMLParser($element, $maxExpireTime, $validators, $parentExtensions); $ret = array($ret->getEntityId() => $ret); /** @var SimpleSAML_Metadata_SAMLParser[] $ret */ return $ret; @@ -395,13 +403,14 @@ class SimpleSAML_Metadata_SAMLParser assert('$element instanceof SAML2_XML_md_EntitiesDescriptor'); + $extensions = self::processExtensions($element, $parentExtensions); $expTime = self::getExpireTime($element, $maxExpireTime); $validators[] = $element; $ret = array(); foreach ($element->children as $child) { - $ret += self::processDescriptorsElement($child, $expTime, $validators); + $ret += self::processDescriptorsElement($child, $expTime, $validators, $extensions); } return $ret; @@ -992,13 +1001,15 @@ class SimpleSAML_Metadata_SAMLParser /** - * Parse an Extensions element. + * Parse an Extensions element. Extensions may appear in multiple elements and certain extension may get inherited + * from a parent element. * * @param mixed $element The element which contains the Extensions element. + * @param array $parentExtensions An optional array of extensions from the parent element. * * @return array An associative array with the extensions parsed. */ - private static function processExtensions($element) + private static function processExtensions($element, $parentExtensions = array()) { $ret = array( 'scope' => array(), @@ -1009,6 +1020,12 @@ class SimpleSAML_Metadata_SAMLParser 'DiscoHints' => array(), ); + // Some extensions may get inherited from a parent element + if (($element instanceof SAML2_XML_md_EntityDescriptor || $element instanceof SAML2_XML_md_EntitiesDescriptor) + && !empty($parentExtensions['RegistrationInfo'])) { + $ret['RegistrationInfo'] = $parentExtensions['RegistrationInfo']; + } + foreach ($element->Extensions as $e) { if ($e instanceof SAML2_XML_shibmd_Scope) { @@ -1017,9 +1034,18 @@ class SimpleSAML_Metadata_SAMLParser } // Entity Attributes are only allowed at entity level extensions and not at RoleDescriptor level - if ($element instanceof SAML2_XML_md_EntityDescriptor) { + if ($element instanceof SAML2_XML_md_EntityDescriptor || + $element instanceof SAML2_XML_md_EntitiesDescriptor) { + + if ($e instanceof SAML2_XML_mdrpi_RegistrationInfo) { - $ret['RegistrationInfo']['registrationAuthority'] = $e->registrationAuthority; + // Registration Authority cannot be overridden + if (isset($ret['RegistrationInfo']['registrationAuthority'])) { + SimpleSAML_Logger::debug('Invalid attempt to override registrationAuthority ' + . $ret['RegistrationInfo']['registrationAuthority'] . " with {$e->registrationAuthority}"); + } else { + $ret['RegistrationInfo']['registrationAuthority'] = $e->registrationAuthority; + } } if ($e instanceof SAML2_XML_mdattr_EntityAttributes && !empty($e->children)) { foreach ($e->children as $attr) { diff --git a/tests/lib/SimpleSAML/Metadata/SAMLParserTest.php b/tests/lib/SimpleSAML/Metadata/SAMLParserTest.php index ce5fdd506..7239f7971 100644 --- a/tests/lib/SimpleSAML/Metadata/SAMLParserTest.php +++ b/tests/lib/SimpleSAML/Metadata/SAMLParserTest.php @@ -18,23 +18,71 @@ class SAMLParserTest extends \PHPUnit_Framework_TestCase $document = \SAML2_DOMDocumentFactory::fromString( <<<XML -<EntityDescriptor entityID="theEntityID" - xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"> +<EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"> + <EntityDescriptor entityID="theEntityID"> + <Extensions> + <mdrpi:RegistrationInfo registrationAuthority="https://incommon.org"/> + </Extensions> + <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/> + </EntityDescriptor> +</EntitiesDescriptor> +XML + ); + + + $entities = \SimpleSAML_Metadata_SAMLParser::parseDescriptorsElement($document->documentElement); + $this->assertArrayHasKey('theEntityID', $entities); + // RegistrationInfo is accessible in the SP or IDP metadata accessors + $metadata = $entities['theEntityID']->getMetadata20SP(); + $this->assertEquals($expected, $metadata['RegistrationInfo']); + + } + + /** + * Test RegistrationInfo is inherited correctly from parent EntitiesDescriptor. + * According to the spec overriding RegistrationInfo is not valid. We ignore attempts to override + */ + public function testRegistrationInfoInheritance() + { + $expected = array( + 'registrationAuthority' => 'https://incommon.org', + ); + + $document = \SAML2_DOMDocumentFactory::fromString( + <<<XML +<EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"> <Extensions> <mdrpi:RegistrationInfo registrationAuthority="https://incommon.org"/> - </Extensions> - <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> - </SPSSODescriptor> -</EntityDescriptor> + </Extensions> + <EntityDescriptor entityID="theEntityID"> + <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/> + </EntityDescriptor> + <EntitiesDescriptor> + <EntityDescriptor entityID="subEntityId"> + <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/> + </EntityDescriptor> + <EntityDescriptor entityID="subEntityIdOverride"> + <Extensions> + <mdrpi:RegistrationInfo registrationAuthority="overrides-are-ignored"/> + </Extensions> + <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/> + </EntityDescriptor> + </EntitiesDescriptor> +</EntitiesDescriptor> XML ); - $entities = \SimpleSAML_Metadata_SAMLParser::parseDescriptorsElement($document->documentElement); $this->assertArrayHasKey('theEntityID', $entities); + $this->assertArrayHasKey('subEntityId', $entities); // RegistrationInfo is accessible in the SP or IDP metadata accessors $metadata = $entities['theEntityID']->getMetadata20SP(); $this->assertEquals($expected, $metadata['RegistrationInfo']); + $metadata = $entities['subEntityId']->getMetadata20SP(); + $this->assertEquals($expected, $metadata['RegistrationInfo']); + + $metadata = $entities['subEntityIdOverride']->getMetadata20SP(); + $this->assertEquals($expected, $metadata['RegistrationInfo']); } } -- GitLab