diff --git a/composer.json b/composer.json index 6b5567f21..4ab7c484e 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,8 @@ "config": { "allow-plugins": { "composer/package-versions-deprecated": true, - "dealerdirect/phpcodesniffer-composer-installer": true + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true } } } diff --git a/src/SAML2/Constants.php b/src/SAML2/Constants.php index 0cc5cee71..fe12984db 100644 --- a/src/SAML2/Constants.php +++ b/src/SAML2/Constants.php @@ -231,6 +231,11 @@ class Constants */ const NS_ECP = 'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp'; + /** + * The namespace for the IDP Discovery protocol. + */ + const NS_IDPDISC = 'urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol'; + /** * The namespace for the SOAP protocol. */ diff --git a/src/SAML2/Utils.php b/src/SAML2/Utils.php index 3d55dd0c8..450009c39 100644 --- a/src/SAML2/Utils.php +++ b/src/SAML2/Utils.php @@ -172,7 +172,7 @@ public static function validateSignature(array $info, XMLSecurityKey $key) : voi /** @var XMLSecurityDSig $objXMLSecDSig */ $objXMLSecDSig = $info['Signature']; - + /** * @var \DOMElement[] $sigMethod * @var \DOMElement $objXMLSecDSig->sigNode @@ -221,6 +221,7 @@ public static function xpQuery(DOMNode $node, string $query) : array $xpCache->registerNamespace('saml_protocol', Constants::NS_SAMLP); $xpCache->registerNamespace('saml_assertion', Constants::NS_SAML); $xpCache->registerNamespace('saml_metadata', Constants::NS_MD); + $xpCache->registerNamespace('saml_idpdisc', Constants::NS_IDPDISC); $xpCache->registerNamespace('ds', XMLSecurityDSig::XMLDSIGNS); $xpCache->registerNamespace('xenc', XMLSecEnc::XMLENCNS); } diff --git a/src/SAML2/XML/idpdisc/DiscoveryResponse.php b/src/SAML2/XML/idpdisc/DiscoveryResponse.php new file mode 100644 index 000000000..9ac0a2019 --- /dev/null +++ b/src/SAML2/XML/idpdisc/DiscoveryResponse.php @@ -0,0 +1,57 @@ +toXMLInternal($parent, Constants::NS_IDPDISC, 'idpdisc:DiscoveryResponse'); + } +} diff --git a/src/SAML2/XML/md/EndpointType.php b/src/SAML2/XML/md/EndpointType.php index ab4e986d2..3747355cd 100644 --- a/src/SAML2/XML/md/EndpointType.php +++ b/src/SAML2/XML/md/EndpointType.php @@ -59,7 +59,7 @@ public function __construct(?DOMElement $xml = null) if (!$xml->hasAttribute('Binding')) { throw new \Exception('Missing Binding on '.$xml->tagName); } - $this->Binding = $xml->getAttribute('Binding'); + $this->setBinding($xml->getAttribute('Binding')); if (!$xml->hasAttribute('Location')) { throw new \Exception('Missing Location on '.$xml->tagName); @@ -230,11 +230,12 @@ public function setResponseLocation(?string $responseLocation = null) : void * * @param \DOMElement $parent The element we should append this endpoint to. * @param string $name The name of the element we should create. + * @param string $namespace The namespace of the element we should create * @return \DOMElement */ - public function toXML(DOMElement $parent, string $name) : DOMElement + protected function toXMLInternal(DOMElement $parent, string $namespace, string $name) : DOMElement { - $e = $parent->ownerDocument->createElementNS(Constants::NS_MD, $name); + $e = $parent->ownerDocument->createElementNS($namespace, $name); $parent->appendChild($e); if (empty($this->Binding)) { @@ -257,4 +258,17 @@ public function toXML(DOMElement $parent, string $name) : DOMElement return $e; } + + + /** + * Convert this Attribute to XML. + * + * @param \DOMElement $parent The element we should append this Attribute to. + * @param string $name + * @return \DOMElement + */ + public function toXML(DOMElement $parent, string $name) : \DOMElement + { + return $this->toXMLInternal($parent, Constants::NS_MD, $name); + } } diff --git a/src/SAML2/XML/md/Extensions.php b/src/SAML2/XML/md/Extensions.php index fda7a4a3c..14a9f6a9f 100644 --- a/src/SAML2/XML/md/Extensions.php +++ b/src/SAML2/XML/md/Extensions.php @@ -12,6 +12,7 @@ use SAML2\XML\alg\DigestMethod; use SAML2\XML\alg\SigningMethod; use SAML2\XML\Chunk; +use SAML2\XML\idpdisc\DiscoveryResponse; use SAML2\XML\mdattr\EntityAttributes; use SAML2\XML\mdrpi\Common as MDRPI; use SAML2\XML\mdrpi\PublicationInfo; @@ -32,6 +33,7 @@ class Extensions * * @param \DOMElement $parent The element that may contain the md:Extensions element. * @return (\SAML2\XML\shibmd\Scope| + * \SAML2\XML\idpdisc\DiscoveryResponse| * \SAML2\XML\mdattr\EntityAttributes| * \SAML2\XML\mdrpi\RegistrationInfo| * \SAML2\XML\mdrpi\PublicationInfo| @@ -45,6 +47,9 @@ public static function getList(DOMElement $parent) : array { $ret = []; $supported = [ + Constants::NS_IDPDISC => [ + 'DiscoveryResponse' => DiscoveryResponse::class, + ], Scope::NS => [ 'Scope' => Scope::class, ], diff --git a/src/SAML2/XML/md/IndexedEndpointType.php b/src/SAML2/XML/md/IndexedEndpointType.php index 162c137a2..341994ca6 100644 --- a/src/SAML2/XML/md/IndexedEndpointType.php +++ b/src/SAML2/XML/md/IndexedEndpointType.php @@ -104,11 +104,12 @@ public function setIsDefault(?bool $flag = null) : void * * @param \DOMElement $parent The element we should append this endpoint to. * @param string $name The name of the element we should create. + * @param string $namespace The namesapce of the element we should create. * @return \DOMElement */ - public function toXML(DOMElement $parent, string $name) : DOMElement + protected function toXMLInternal(DOMElement $parent, string $namespace, string $name) : DOMElement { - $e = parent::toXML($parent, $name); + $e = parent::toXMLInternal($parent, $namespace, $name); $e->setAttribute('index', strval($this->index)); if (is_bool($this->isDefault)) { diff --git a/tests/SAML2/XML/md/EndpointTypeTest.php b/tests/SAML2/XML/md/EndpointTypeTest.php index 0763e660d..fb49abafe 100644 --- a/tests/SAML2/XML/md/EndpointTypeTest.php +++ b/tests/SAML2/XML/md/EndpointTypeTest.php @@ -25,8 +25,8 @@ public function testMarshalling() : void $document = DOMDocumentFactory::fromString(''); $endpointTypeElement = $endpointType->toXML($document->firstChild, 'md:Test'); - $endpointTypeElements = Utils::xpQuery($endpointTypeElement, '/root/saml_metadata:Test'); + $this->assertCount(1, $endpointTypeElements); $endpointTypeElement = $endpointTypeElements[0]; diff --git a/tests/SAML2/XML/md/IndexedEndpointTypeTest.php b/tests/SAML2/XML/md/IndexedEndpointTypeTest.php index 4698dda14..d349b2bee 100644 --- a/tests/SAML2/XML/md/IndexedEndpointTypeTest.php +++ b/tests/SAML2/XML/md/IndexedEndpointTypeTest.php @@ -4,7 +4,10 @@ namespace SAML2\XML\md; +use InvalidArgumentException; +use SAML2\Constants; use SAML2\DOMDocumentFactory; +use SAML2\XML\idpdisc\DiscoveryResponse; use SAML2\XML\md\IndexedEndpointType; use SAML2\Utils; @@ -16,7 +19,7 @@ class IndexedEndpointTypeTest extends \PHPUnit\Framework\TestCase /** * @return void */ - public function testMarshalling() : void + public function testMarshalling(): void { $indexedEndpointType = new IndexedEndpointType(); $indexedEndpointType->setBinding('TestBinding'); @@ -50,4 +53,41 @@ public function testMarshalling() : void $this->assertCount(1, $indexedEndpointTypeElement); $this->assertTrue(!$indexedEndpointTypeElement[0]->hasAttribute('isDefault')); } + + + /** + * @return void + */ + public function testMarshallingDiscoveryResponse(): void + { + $discoResponse = new DiscoveryResponse(); + $discoResponse->setBinding(Constants::NS_IDPDISC); + $discoResponse->setLocation('TestLocation'); + $discoResponse->setIndex(42); + $discoResponse->setIsDefault(false); + + $document = DOMDocumentFactory::fromString(''); + $discoResponseElement = $discoResponse->toXML($document->firstChild, 'idpdisc:DiscoverResponse'); + + $discoResponseElements = Utils::xpQuery($discoResponseElement, '/root/saml_idpdisc:DiscoveryResponse'); + $this->assertCount(1, $discoResponseElements); + $discoResponseElement = $discoResponseElements[0]; + + $this->assertEquals(Constants::NS_IDPDISC, $discoResponseElement->getAttribute('Binding')); + $this->assertEquals('TestLocation', $discoResponseElement->getAttribute('Location')); + $this->assertEquals('42', $discoResponseElement->getAttribute('index')); + $this->assertEquals('false', $discoResponseElement->getAttribute('isDefault')); + } + + + /** + * @return void + */ + public function testMarshallingDiscoveryResponseWrongBindingFails(): void + { + $discoResponse = new DiscoveryResponse(); + + $this->expectException(InvalidArgumentException::class); + $discoResponse->setBinding('This is not OK.'); + } }