diff --git a/lib/SAML2/SignedElementHelper.php b/lib/SAML2/SignedElementHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..ecae866c957a8724290287812a2d49d956ef6791 --- /dev/null +++ b/lib/SAML2/SignedElementHelper.php @@ -0,0 +1,216 @@ +<?php + +/** + * Helper class for processing signed elements. + * + * Can either be inherited from, or can be used by proxy. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_SignedElementHelper implements SAML2_SignedElement { + + /** + * The private key we should use to sign the message. + * + * The private key can be NULL, in which case the message is sent unsigned. + * + * @var XMLSecurityKey|NULL + */ + private $signatureKey; + + + /** + * List of certificates that should be included in the message. + * + * @var array + */ + private $certificates; + + + /** + * Available methods for validating this message. + * + * @var array + */ + private $validators; + + + /** + * Initialize the helper class. + * + * @param DOMElement|NULL $xml The XML element which may be signed. + */ + protected function __construct(DOMElement $xml = NULL) { + + $this->certificates = array(); + $this->validators = array(); + + if ($xml === NULL) { + return; + } + + /* Validate the signature element of the message. */ + try { + $sig = SAML2_Utils::validateElement($xml); + + if ($sig !== FALSE) { + $this->certificates = $sig['Certificates']; + $this->validators[] = array( + 'Function' => array('SAML2_Utils', 'validateSignature'), + 'Data' => $sig, + ); + } + + } catch (Exception $e) { + /* Ignore signature validation errors. */ + } + } + + + /** + * Add a method for validating this element. + * + * This function is used for custom validation extensions + * + * @param callback $function The function which should be called. + * @param mixed $data The data that should be included as the first parameter to the function. + */ + public function addValidator($function, $data) { + assert('is_callable($function)'); + + $this->validators[] = array( + 'Function' => $function, + 'Data' => $data, + ); + } + + + /** + * Validate this element against a public key. + * + * TRUE is returned on success, FALSE is returned if we don't have any + * signature we can validate. An exception is thrown if the signature + * validation fails. + * + * @param XMLSecurityKey $key The key we should check against. + * @return boolean TRUE on success, FALSE when we don't have a signature. + */ + public function validate(XMLSecurityKey $key) { + + if (count($this->validators) === 0) { + return FALSE; + } + + $exceptions = array(); + + foreach ($this->validators as $validator) { + $function = $validator['Function']; + $data = $validator['Data']; + + try { + call_user_func($function, $data, $key); + /* We were able to validate the message with this validator. */ + return TRUE; + } catch (Exception $e) { + $exceptions[] = $e; + } + } + + /* No validators were able to validate the message. */ + throw $exceptions[0]; + } + + + /** + * Retrieve the private key we should use to sign the message. + * + * @return XMLSecurityKey|NULL The key, or NULL if no key is specified. + */ + public function getSignatureKey() { + return $this->signatureKey; + } + + + /** + * Set the private key we should use to sign the message. + * + * If the key is NULL, the message will be sent unsigned. + * + * @param XMLSecurityKey|NULL $key + */ + public function setSignatureKey(XMLsecurityKey $signatureKey = NULL) { + $this->signatureKey = $signatureKey; + } + + + /** + * Set the certificates that should be included in the message. + * + * The certificates should be strings with the PEM encoded data. + * + * @param array $certificates An array of certificates. + */ + public function setCertificates(array $certificates) { + $this->certificates = $certificates; + } + + + /** + * Retrieve the certificates that are included in the message. + * + * @return array An array of certificates. + */ + public function getCertificates() { + return $this->certificates; + } + + + /** + * Retrieve certificates that sign this element. + * + * @return array Array with certificates. + */ + public function getValidatingCertificates() { + + $ret = array(); + foreach ($this->certificates as $cert) { + + /* We have found a matching fingerprint. */ + $pemCert = "-----BEGIN CERTIFICATE-----\n" . + chunk_split($cert, 64) . + "-----END CERTIFICATE-----\n"; + + /* Extract the public key from the certificate for validation. */ + $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'public')); + $key->loadKey($pemCert); + + /* Check the signature. */ + if ($this->validate($key)) { + $ret[] = $cert; + } + } + + return $ret; + } + + + /** + * Sign the given XML element. + * + * @param DOMElement $root The element we should sign. + * @param DOMElement|NULL $insertBefore The element we should insert the signature node before. + */ + protected function signElement(DOMElement $root, DOMElement $insertBefore = NULL) { + + if ($this->signatureKey === NULL) { + /* We cannot sign this element. */ + return; + } + + SAML2_Utils::insertSignature($this->signatureKey, $this->certificates, $root, $insertBefore); + + return $root; + } + +} diff --git a/lib/SAML2/XML/Chunk.php b/lib/SAML2/XML/Chunk.php new file mode 100644 index 0000000000000000000000000000000000000000..fb4792867baab5632c8bdeeb0cd487a5a098b879 --- /dev/null +++ b/lib/SAML2/XML/Chunk.php @@ -0,0 +1,104 @@ +<?php + +/** + * Serializable class used to hold an XML element. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_Chunk { + + /** + * The localName of the element. + * + * @var string + */ + public $localName; + + + /** + * The namespaceURI of this element. + * + * @var string + */ + public $namespaceURI; + + + /** + * The DOMElement we contain. + * + * @var DOMElement + */ + private $xml; + + + /** + * The DOMElement as a text string. Used during serialization. + * + * @var string|NULL + */ + private $xmlString; + + + /** + * Create a XMLChunk from a copy of the given DOMElement. + * + * @param DOMElement $xml The element we should copy. + */ + public function __construct(DOMElement $xml) { + + $this->localName = $xml->localName; + $this->namespaceURI = $xml->namespaceURI; + + $this->xml = SAML2_Utils::copyElement($xml); + } + + + /** + * Get this DOMElement. + * + * @return DOMElement This element. + */ + public function getXML() { + assert('$this->xml instanceof DOMElement || is_string($this->xmlString)'); + + if ($this->xml === NULL) { + $doc = new DOMDocument(); + $doc->loadXML($this->xmlString); + $this->xml = $doc->firstChild; + } + + return $this->xml; + } + + + /** + * Append this XML element to a different XML element. + * + * @param DOMElement $parent The element we should append this element to. + * @return DOMElement The new element. + */ + public function toXML(DOMElement $parent) { + + return SAML2_Utils::copyElement($this->getXML(), $parent); + } + + + /** + * Serialization handler. + * + * Converts the XML data to a string that can be serialized + * + * @return array List of properties that should be serialized. + */ + public function __sleep() { + assert('$this->xml instanceof DOMElement || is_string($this->xmlString)'); + + if ($this->xmlString === NULL) { + $this->xmlString = $this->xml->ownerDocument->saveXML($this->xml); + } + + return array('xmlString'); + } + +} diff --git a/lib/SAML2/XML/ds/KeyInfo.php b/lib/SAML2/XML/ds/KeyInfo.php new file mode 100644 index 0000000000000000000000000000000000000000..44b4b0d0ed3af3e48392a45d0066400a1387388a --- /dev/null +++ b/lib/SAML2/XML/ds/KeyInfo.php @@ -0,0 +1,94 @@ +<?php + +/** + * Class representing a ds:KeyInfo element. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_ds_KeyInfo { + + /** + * The Id attribute on this element. + * + * @var string|NULL + */ + public $Id = NULL; + + + /** + * The various key information elements. + * + * Array with various elements describing this key. + * Unknown elements will be represented by SAML2_XML_Chunk. + * + * @var array + */ + public $info = array(); + + + /** + * Initialize a KeyInfo element. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + + if ($xml === NULL) { + return; + } + + if ($xml->hasAttribute('Id')) { + $this->Id = $xml->getAttribute('Id'); + } + + for ($n = $xml->firstChild; $n !== NULL; $n = $n->nextSibling) { + if (!($n instanceof DOMElement)) { + continue; + } + + if ($n->namespaceURI !== XMLSecurityDSig::XMLDSIGNS) { + $this->info[] = new SAML2_XML_Chunk($n); + continue; + } + switch ($n->localName) { + case 'KeyName': + $this->info[] = new SAML2_XML_ds_KeyName($n); + break; + case 'X509Data': + $this->info[] = new SAML2_XML_ds_X509Data($n); + break; + default: + $this->info[] = new SAML2_XML_Chunk($n); + break; + } + } + } + + + /** + * Convert this KeyInfo to XML. + * + * @param DOMElement $parent The element we should append this KeyInfo to. + */ + public function toXML(DOMElement $parent) { + assert('is_null($this->Id) || is_string($this->Id)'); + assert('is_array($this->info)'); + + $doc = $parent->ownerDocument; + + $e = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:KeyInfo'); + $parent->appendChild($e); + + if (isset($this->Id)) { + $e->setAttribute('Id', $this->Id); + } + + foreach ($this->info as $n) { + $n->toXML($e); + } + + return $e; + } + +} diff --git a/lib/SAML2/XML/ds/KeyName.php b/lib/SAML2/XML/ds/KeyName.php new file mode 100644 index 0000000000000000000000000000000000000000..6eae3a4f2addb5cbe4304495da5ac2f4d0f6334e --- /dev/null +++ b/lib/SAML2/XML/ds/KeyName.php @@ -0,0 +1,45 @@ +<?php + +/** + * Class representing a ds:KeyName element. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_ds_KeyName { + + /** + * The key name. + * + * @var string + */ + public $name; + + + /** + * Initialize a KeyName element. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + + if ($xml === NULL) { + return; + } + + $this->name = $xml->textContent; + } + + + /** + * Convert this KeyName element to XML. + * + * @param DOMElement $parent The element we should append this KeyName element to. + */ + public function toXML(DOMElement $parent) { + assert('is_string($this->name)'); + + return SAML2_Utils::addString($parent, XMLSecurityDSig::XMLDSIGNS, 'ds:KeyName', $this->name); + } + +} diff --git a/lib/SAML2/XML/ds/X509Certificate.php b/lib/SAML2/XML/ds/X509Certificate.php new file mode 100644 index 0000000000000000000000000000000000000000..c4dcac197951b0e1c87b28814f845d45acf90c87 --- /dev/null +++ b/lib/SAML2/XML/ds/X509Certificate.php @@ -0,0 +1,45 @@ +<?php + +/** + * Class representing a ds:X509Certificate element. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_ds_X509Certificate { + + /** + * The base64-encoded certificate. + * + * @var string + */ + public $certificate; + + + /** + * Initialize an X509Certificate element. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + + if ($xml === NULL) { + return; + } + + $this->certificate = $xml->textContent; + } + + + /** + * Convert this X509Certificate element to XML. + * + * @param DOMElement $parent The element we should append this X509Certificate element to. + */ + public function toXML(DOMElement $parent) { + assert('is_string($this->certificate)'); + + return SAML2_Utils::addString($parent, XMLSecurityDSig::XMLDSIGNS, 'ds:X509Certificate', $this->certificate); + } + +} diff --git a/lib/SAML2/XML/ds/X509Data.php b/lib/SAML2/XML/ds/X509Data.php new file mode 100644 index 0000000000000000000000000000000000000000..e6b3c066f59461fb335cd337ee87a08ae19c364c --- /dev/null +++ b/lib/SAML2/XML/ds/X509Data.php @@ -0,0 +1,74 @@ +<?php + +/** + * Class representing a ds:X509Data element. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_ds_X509Data { + + /** + * The various X509 data elements. + * + * Array with various elements describing this certificate. + * Unknown elements will be represented by SAML2_XML_Chunk. + * + * @var array + */ + public $data = array(); + + + /** + * Initialize a X509Data. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + + if ($xml === NULL) { + return; + } + + for ($n = $xml->firstChild; $n !== NULL; $n = $n->nextSibling) { + if (!($n instanceof DOMElement)) { + continue; + } + + if ($n->namespaceURI !== XMLSecurityDSig::XMLDSIGNS) { + $this->data[] = new SAML2_XML_Chunk($n); + continue; + } + switch ($n->localName) { + case 'X509Certificate': + $this->data[] = new SAML2_XML_ds_X509Certificate($n); + break; + default: + $this->data[] = new SAML2_XML_Chunk($n); + break; + } + } + } + + + /** + * Convert this X509Data element to XML. + * + * @param DOMElement $parent The element we should append this X509Data element to. + */ + public function toXML(DOMElement $parent) { + assert('is_array($this->data)'); + + $doc = $parent->ownerDocument; + + $e = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Data'); + $parent->appendChild($e); + + foreach ($this->data as $n) { + $n->toXML($e); + } + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/AdditionalMetadataLocation.php b/lib/SAML2/XML/md/AdditionalMetadataLocation.php new file mode 100644 index 0000000000000000000000000000000000000000..3bdb6ba72354877bc177ca4a289f8b6b2a7a0d70 --- /dev/null +++ b/lib/SAML2/XML/md/AdditionalMetadataLocation.php @@ -0,0 +1,62 @@ +<?php + +/** + * Class representing SAML 2 metadata AdditionalMetadataLocation element. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_AdditionalMetadataLocation { + + /** + * The namespace of this metadata. + * + * @var string + */ + public $namespace; + + /** + * The URI where the metadata is located. + * + * @var string + */ + public $location; + + + /** + * Initialize an AdditionalMetadataLocation element. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + + if ($xml === NULL) { + return; + } + + if (!$xml->hasAttribute('namespace')) { + throw new Exception('Missing namespace attribute on AdditionalMetadataLocation element.'); + } + $this->namespace = $xml->getAttribute('namespace'); + + $this->location = $xml->textContent; + } + + + /** + * Convert this AdditionalMetadataLocation to XML. + * + * @param DOMElement $parent The element we should append to. + * @return DOMElement This AdditionalMetadataLocation-element. + */ + public function toXML(DOMElement $parent) { + assert('is_string($this->namespace)'); + assert('is_string($this->location)'); + + $e = SAML2_Utils::addString($parent, SAML2_Const::NS_MD, 'md:AdditionalMetadataLocation', $this->location); + $e->setAttribute('namespace', $this->namespace); + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/AffiliationDescriptor.php b/lib/SAML2/XML/md/AffiliationDescriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..ad333230586705a347f589e6a807aff0cde20de0 --- /dev/null +++ b/lib/SAML2/XML/md/AffiliationDescriptor.php @@ -0,0 +1,162 @@ +<?php + +/** + * Class representing SAML 2 AffiliationDescriptor element. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_AffiliationDescriptor extends SAML2_SignedElementHelper { + + /** + * The affiliationOwnerID. + * + * @var string + */ + public $affiliationOwnerID; + + + /** + * The ID of this element. + * + * @var string|NULL + */ + public $ID; + + + /** + * How long this element is valid, as a unix timestamp. + * + * @var int|NULL + */ + public $validUntil; + + + /** + * The length of time this element can be cached, as string. + * + * @var string|NULL + */ + public $cacheDuration; + + + /** + * Extensions on this element. + * + * Array of extension elements. + * + * @var array + */ + public $Extensions = array(); + + + /** + * The AffiliateMember(s). + * + * Array of entity ID strings. + * + * @var array + */ + public $AffiliateMember = array(); + + + /** + * KeyDescriptor elements. + * + * Array of SAML2_XML_md_KeyDescriptor elements. + * + * @var array + */ + public $KeyDescriptor = array(); + + + /** + * Initialize a AffiliationDescriptor. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + + parent::__construct($xml); + + if ($xml === NULL) { + return; + } + + if (!$xml->hasAttribute('affiliationOwnerID')) { + throw new Exception('Missing affiliationOwnerID on AffiliationDescriptor.'); + } + $this->affiliationOwnerID = $xml->getAttribute('affiliationOwnerID'); + + if ($xml->hasAttribute('ID')) { + $this->ID = $xml->getAttribute('ID'); + } + + if ($xml->hasAttribute('validUntil')) { + $this->validUntil = SimpleSAML_Utilities::parseSAML2Time($xml->getAttribute('validUntil')); + } + + if ($xml->hasAttribute('cacheDuration')) { + $this->cacheDuration = $xml->getAttribute('cacheDuration'); + } + + $this->Extensions = SAML2_XML_md_Extensions::getList($xml); + + $this->AffiliateMember = SAML2_Utils::extractStrings($xml, './saml_metadata:AffiliateMember'); + if (empty($this->AffiliateMember)) { + throw new Exception('Missing AffiliateMember in AffiliationDescriptor.'); + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:KeyDescriptor') as $kd) { + $this->KeyDescriptor[] = new SAML2_XML_md_KeyDescriptor($kd); + } + } + + + /** + * Add this AffiliationDescriptor to an EntityDescriptor. + * + * @param DOMElement $parent The EntityDescriptor we should append this endpoint to. + * @param string $name The name of the element we should create. + */ + public function toXML(DOMElement $parent) { + assert('is_string($this->affiliationOwnerID)'); + assert('is_null($this->ID) || is_string($this->ID)'); + assert('is_null($this->validUntil) || is_int($this->validUntil)'); + assert('is_null($this->cacheDuration) || is_string($this->cacheDuration)'); + assert('is_array($this->Extensions)'); + assert('is_array($this->AffiliateMember)'); + assert('!empty($this->AffiliateMember)'); + assert('is_array($this->KeyDescriptor)'); + + $e = $parent->ownerDocument->createElementNS(SAML2_Const::NS_MD, 'md:AffiliationDescriptor'); + $parent->appendChild($e); + + $e->setAttribute('affiliationOwnerID', $this->affiliationOwnerID); + + if (isset($this->ID)) { + $e->setAttribute('ID', $this->ID); + } + + if (isset($this->validUntil)) { + $e->setAttribute('validUntil', gmdate('Y-m-d\TH:i:s\Z', $this->validUntil)); + } + + if (isset($this->cacheDuration)) { + $e->setAttribute('cacheDuration', $this->cacheDuration); + } + + SAML2_XML_md_Extensions::addList($e, $this->Extensions); + + SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:AffiliateMember', FALSE, $this->AffiliateMember); + + foreach ($this->KeyDescriptor as $kd) { + $kd->toXML($e); + } + + $this->signElement($e, $e->firstChild); + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/AttributeAuthorityDescriptor.php b/lib/SAML2/XML/md/AttributeAuthorityDescriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..bbf95075052bf02789a19bf12a0f719471e6dd82 --- /dev/null +++ b/lib/SAML2/XML/md/AttributeAuthorityDescriptor.php @@ -0,0 +1,128 @@ +<?php + +/** + * Class representing SAML 2 metadata AttributeAuthorityDescriptor. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_AttributeAuthorityDescriptor extends SAML2_XML_md_RoleDescriptor { + + /** + * List of AttributeService endpoints. + * + * Array with EndpointType objects. + * + * @var array + */ + public $AttributeService = array(); + + + /** + * List of AssertionIDRequestService endpoints. + * + * Array with EndpointType objects. + * + * @var array + */ + public $AssertionIDRequestService = array(); + + + /** + * List of supported NameID formats. + * + * Array of strings. + * + * @var array + */ + public $NameIDFormat = array(); + + + /** + * List of supported attribute profiles. + * + * Array with strings. + * + * @var array + */ + public $AttributeProfile = array(); + + + /** + * List of supported attributes. + * + * Array with SAML2_XML_saml_Attribute objects. + * + * @var array + */ + public $Attribute = array(); + + + /** + * Initialize an IDPSSODescriptor. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + parent::__construct('md:AttributeAuthorityDescriptor', $xml); + + if ($xml === NULL) { + return; + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AttributeService') as $ep) { + $this->AttributeService[] = new SAML2_XML_md_EndpointType($ep); + } + if (empty($this->AttributeService)) { + throw new Exception('Must have at least one AttributeService in AttributeAuthorityDescriptor.'); + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AssertionIDRequestService') as $ep) { + $this->AssertionIDRequestService[] = new SAML2_XML_md_EndpointType($airs); + } + + $this->NameIDFormat = SAML2_Utils::extractStrings($xml, './saml_metadata:NameIDFormat'); + + $this->AttributeProfile = SAML2_Utils::extractStrings($xml, './saml_metadata:AttributeProfile'); + + foreach (SAML2_Utils::xpQuery($xml, './saml_assertion:Attribute') as $a) { + $this->Attribute[] = new SAML2_XML_saml_Attribute($a); + } + } + + + /** + * Add this AttributeAuthorityDescriptor to an EntityDescriptor. + * + * @param DOMElement $parent The EntityDescriptor we should append this IDPSSODescriptor to. + */ + public function toXML(DOMElement $parent) { + assert('is_array($this->AttributeService)'); + assert('!empty($this->AttributeService)'); + assert('is_array($this->AssertionIDRequestService)'); + assert('is_array($this->NameIDFormat)'); + assert('is_array($this->AttributeProfile)'); + assert('is_array($this->Attribute)'); + + $e = parent::toXML($parent); + + foreach ($this->AttributeService as $ep) { + $ep->toXML($e, 'md:AttributeService'); + } + + foreach ($this->AssertionIDRequestService as $ep) { + $ep->toXML($e, 'md:AssertionIDRequestService'); + } + + SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:NameIDFormat', FALSE, $this->NameIDFormat); + + SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:AttributeProfile', FALSE, $this->AttributeProfile); + + foreach ($this->Attribute as $a) { + $a->toXML($e); + } + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/AttributeConsumingService.php b/lib/SAML2/XML/md/AttributeConsumingService.php new file mode 100644 index 0000000000000000000000000000000000000000..3e0b6a3dea16cbdbedc2c85d0052b30f83ab8ceb --- /dev/null +++ b/lib/SAML2/XML/md/AttributeConsumingService.php @@ -0,0 +1,124 @@ +<?php + +/** + * Class representing SAML 2 Metadata AttributeConsumingService element. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_AttributeConsumingService { + + /** + * The index of this AttributeConsumingService. + * + * @var int + */ + public $index; + + + /** + * Whether this is the default AttributeConsumingService. + * + * @var bool|NULL + */ + public $isDefault = NULL; + + + /** + * The ServiceName of this AttributeConsumingService. + * + * This is an associative array with language => translation. + * + * @var array + */ + public $ServiceName = array(); + + + /** + * The ServiceDescription of this AttributeConsumingService. + * + * This is an associative array with language => translation. + * + * @var array + */ + public $ServiceDescription = array(); + + + /** + * The RequestedAttribute elements. + * + * This is an array of SAML_RequestedAttributeType elements. + * + * @var array + */ + public $RequestedAttribute = array(); + + + /** + * Initialize / parse an AttributeConsumingService. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + + if ($xml === NULL) { + return; + } + + + if (!$xml->hasAttribute('index')) { + throw new Exception('Missing index on AttributeConsumingService.'); + } + $this->index = (int)$xml->getAttribute('index'); + + $this->isDefault = SAML2_Utils::parseBoolean($xml, 'isDefault', NULL); + + $this->ServiceName = SAML2_Utils::extractLocalizedStrings($xml, './saml_metadata:ServiceName'); + if (empty($this->ServiceName)) { + throw new Exception('Missing ServiceName in AttributeConsumingService.'); + } + + $this->ServiceDescription = SAML2_Utils::extractLocalizedStrings($xml, './saml_metadata:ServiceDescription'); + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:RequestedAttribute') as $ra) { + $this->RequestedAttribute[] = new SAML2_XML_md_RequestedAttribute($ra); + } + } + + + /** + * Convert to DOMElement. + * + * @param DOMElement $parent The element we should append this AttributeConsumingService to. + */ + public function toXML(DOMElement $parent) { + assert('is_int($this->index)'); + assert('is_null($this->isDefault) || is_bool($this->isDefault)'); + assert('is_array($this->ServiceName)'); + assert('is_array($this->ServiceDescription)'); + assert('is_array($this->RequestedAttribute)'); + + $doc = $parent->ownerDocument; + + $e = $doc->createElementNS(SAML2_Const::NS_MD, 'md:AttributeConsumingService'); + $parent->appendChild($e); + + $e->setAttribute('index', (string)$this->index); + + if ($this->isDefault === TRUE) { + $e->setAttribute('isDefault', 'true'); + } elseif ($this->isDefault === FALSE) { + $e->setAttribute('isDefault', 'false'); + } + + SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:ServiceName', TRUE, $this->ServiceName); + SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:ServiceDescription', TRUE, $this->ServiceDescription); + + foreach ($this->RequestedAttribute as $ra) { + $ra->toXML($e); + } + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/AuthnAuthorityDescriptor.php b/lib/SAML2/XML/md/AuthnAuthorityDescriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..9a059823fcc3bd1ebef1b4915c604abdf80afcd8 --- /dev/null +++ b/lib/SAML2/XML/md/AuthnAuthorityDescriptor.php @@ -0,0 +1,94 @@ +<?php + +/** + * Class representing SAML 2 metadata AuthnAuthorityDescriptor. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_AuthnAuthorityDescriptor extends SAML2_XML_md_RoleDescriptor { + + /** + * List of AuthnQueryService endpoints. + * + * Array with EndpointType objects. + * + * @var array + */ + public $AuthnQueryService = array(); + + + /** + * List of AssertionIDRequestService endpoints. + * + * Array with EndpointType objects. + * + * @var array + */ + public $AssertionIDRequestService = array(); + + + /** + * List of supported NameID formats. + * + * Array of strings. + * + * @var array + */ + public $NameIDFormat = array(); + + + /** + * Initialize an IDPSSODescriptor. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + parent::__construct('md:AuthnAuthorityDescriptor', $xml); + + if ($xml === NULL) { + return; + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AuthnQueryService') as $ep) { + $this->AuthnQueryService[] = new SAML2_XML_md_EndpointType($ep); + } + if (empty($this->AuthnQueryService)) { + throw new Exception('Must have at least one AuthnQueryService in AuthnAuthorityDescriptor.'); + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AssertionIDRequestService') as $ep) { + $this->AssertionIDRequestService[] = new SAML2_XML_md_EndpointType($airs); + } + + $this->NameIDFormat = SAML2_Utils::extractStrings($xml, './saml_metadata:NameIDFormat'); + } + + + /** + * Add this IDPSSODescriptor to an EntityDescriptor. + * + * @param DOMElement $parent The EntityDescriptor we should append this AuthnAuthorityDescriptor to. + */ + public function toXML(DOMElement $parent) { + assert('is_array($this->AuthnQueryService)'); + assert('!empty($this->AuthnQueryService)'); + assert('is_array($this->AssertionIDRequestService)'); + assert('is_array($this->NameIDFormat)'); + + $e = parent::toXML($parent); + + foreach ($this->AuthnQueryService as $ep) { + $ep->toXML($e, 'md:AuthnQueryService'); + } + + foreach ($this->AssertionIDRequestService as $ep) { + $ep->toXML($e, 'md:AssertionIDRequestService'); + } + + SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:NameIDFormat', FALSE, $this->NameIDFormat); + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/ContactPerson.php b/lib/SAML2/XML/md/ContactPerson.php new file mode 100644 index 0000000000000000000000000000000000000000..ea347c3f908ff0724357604b8da8fa6c6160c5e2 --- /dev/null +++ b/lib/SAML2/XML/md/ContactPerson.php @@ -0,0 +1,182 @@ +<?php + +/** + * Class representing SAML 2 ContactPerson. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_ContactPerson { + + /** + * The contact type. + * + * @var string + */ + public $contactType; + + + /** + * Extensions on this element. + * + * Array of extension elements. + * + * @var array + */ + public $Extensions = array(); + + + /** + * The Company of this contact. + * + * @var string + */ + public $Company = NULL; + + + /** + * The GivenName of this contact. + * + * @var string + */ + public $GivenName = NULL; + + + /** + * The SurName of this contact. + * + * @var string + */ + public $SurName = NULL; + + + /** + * The EmailAddresses of this contact. + * + * @var array + */ + public $EmailAddress = array(); + + + /** + * The TelephoneNumbers of this contact. + * + * @var array + */ + public $TelephoneNumber = array(); + + + /** + * Initialize a ContactPerson element. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + + if ($xml === NULL) { + return; + } + + if (!$xml->hasAttribute('contactType')) { + throw new Exception('Missing contactType on ContactPerson.'); + } + $this->contactType = $xml->getAttribute('contactType'); + + $this->Extensions = SAML2_XML_md_Extensions::getList($xml); + + + $this->Company = self::getStringElement($xml, 'Company'); + $this->GivenName = self::getStringElement($xml, 'GivenName'); + $this->SurName = self::getStringElement($xml, 'SurName'); + $this->EmailAddress = self::getStringElements($xml, 'EmailAddress'); + $this->TelephoneNumber = self::getStringElements($xml, 'TelephoneNumber'); + } + + + /** + * Retrieve the value of a child DOMElements as an array of strings. + * + * @param DOMElement $parent The parent element. + * @param string $name The name of the child elements. + * @return array The value of the child elements. + */ + private static function getStringElements(DOMElement $parent, $name) { + assert('is_string($name)'); + + $e = SAML2_Utils::xpQuery($parent, './saml_metadata:' . $name); + + $ret = array(); + foreach ($e as $i) { + $ret[] = $i->textContent; + } + + return $ret; + } + + + /** + * Retrieve the value of a child DOMElement as a string. + * + * @param DOMElement $parent The parent element. + * @param string $name The name of the child element. + * @return string|NULL The value of the child element. + */ + private static function getStringElement(DOMElement $parent, $name) { + assert('is_string($name)'); + + $e = self::getStringElements($parent, $name); + if (empty($e)) { + return NULL; + } + if (count($e) > 1) { + throw new Exception('More than one ' . $name . ' in ' . $parent->tagName); + } + + return $e[0]; + } + + + /** + * Convert this ContactPerson to XML. + * + * @param DOMElement $parent The element we should add this contact to. + * @return DOMElement The new ContactPerson-element. + */ + public function toXML(DOMElement $parent) { + assert('is_string($this->contactType)'); + assert('is_array($this->Extensions)'); + assert('is_null($this->Company) || is_string($this->Company)'); + assert('is_null($this->GivenName) || is_string($this->GivenName)'); + assert('is_null($this->SurName) || is_string($this->SurName)'); + assert('is_array($this->EmailAddress)'); + assert('is_array($this->TelephoneNumber)'); + + $doc = $parent->ownerDocument; + + $e = $doc->createElementNS(SAML2_Const::NS_MD, 'md:ContactPerson'); + $parent->appendChild($e); + + $e->setAttribute('contactType', $this->contactType); + + SAML2_XML_md_Extensions::addList($e, $this->Extensions); + + if (isset($this->Company)) { + SAML2_Utils::addString($e, SAML2_Const::NS_MD, 'md:Company', $this->Company); + } + if (isset($this->GivenName)) { + SAML2_Utils::addString($e, SAML2_Const::NS_MD, 'md:GivenName', $this->GivenName); + } + if (isset($this->SurName)) { + SAML2_Utils::addString($e, SAML2_Const::NS_MD, 'md:SurName', $this->SurName); + } + if (!empty($this->EmailAddress)) { + SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:EmailAddress', FALSE, $this->EmailAddress); + } + if (!empty($this->TelephoneNumber)) { + SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:TelephoneNumber', FALSE, $this->TelephoneNumber); + } + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/EndpointType.php b/lib/SAML2/XML/md/EndpointType.php new file mode 100644 index 0000000000000000000000000000000000000000..519a0a7d37f933a0d7d8c9973eb7d9fde11ee654 --- /dev/null +++ b/lib/SAML2/XML/md/EndpointType.php @@ -0,0 +1,87 @@ +<?php + +/** + * Class representing SAML 2 EndpointType. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_EndpointType { + + /** + * The binding for this endpoint. + * + * @var string + */ + public $Binding; + + + /** + * The URI to this endpoint. + * + * @var string + */ + public $Location; + + + /** + * The URI where responses can be delivered. + * + * @var string|NULL + */ + public $ResponseLocation = NULL; + + + /** + * Initialize an EndpointType. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + + if ($xml === NULL) { + return; + } + + if (!$xml->hasAttribute('Binding')) { + throw new Exception('Missing Binding on ' . $xml->tagName); + } + $this->Binding = $xml->getAttribute('Binding'); + + if (!$xml->hasAttribute('Location')) { + throw new Exception('Missing Location on ' . $xml->tagName); + } + $this->Location = $xml->getAttribute('Location'); + + if ($xml->hasAttribute('ResponseLocation')) { + $this->ResponseLocation = $xml->getAttribute('ResponseLocation'); + } + } + + + /** + * Add this endpoint to an XML element. + * + * @param DOMElement $parent The element we should append this endpoint to. + * @param string $name The name of the element we should create. + */ + public function toXML(DOMElement $parent, $name) { + assert('is_string($name)'); + assert('is_string($this->Binding)'); + assert('is_string($this->Location)'); + assert('is_null($this->ResponseLocation) || is_string($this->ResponseLocation)'); + + $e = $parent->ownerDocument->createElementNS(SAML2_Const::NS_MD, $name); + $parent->appendChild($e); + + $e->setAttribute('Binding', $this->Binding); + $e->setAttribute('Location', $this->Location); + + if (isset($this->ResponseLocation)) { + $e->setAttribute('ResponseLocation', $this->ResponseLocation); + } + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/EntitiesDescriptor.php b/lib/SAML2/XML/md/EntitiesDescriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..a6b66b88b520f1cfe2ba49ffaa26b5a3bbcc4587 --- /dev/null +++ b/lib/SAML2/XML/md/EntitiesDescriptor.php @@ -0,0 +1,141 @@ +<?php + +/** + * Class representing SAML 2 EntitiesDescriptor element. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_EntitiesDescriptor extends SAML2_SignedElementHelper { + + /** + * The ID of this element. + * + * @var string|NULL + */ + public $ID; + + + /** + * How long this element is valid, as a unix timestamp. + * + * @var int|NULL + */ + public $validUntil; + + + /** + * The length of time this element can be cached, as string. + * + * @var string|NULL + */ + public $cacheDuration; + + + /** + * The name of this entity collection. + * + * @var string|NULL + */ + public $Name; + + + /** + * Extensions on this element. + * + * Array of extension elements. + * + * @var array + */ + public $Extensions = array(); + + + /** + * Child EntityDescriptor and EntitiesDescriptor elements. + * + * @var array + */ + public $children = array(); + + + /** + * Initialize an EntitiesDescriptor. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + parent::__construct($xml); + + if ($xml === NULL) { + return; + } + + if ($xml->hasAttribute('ID')) { + $this->ID = $xml->getAttribute('ID'); + } + if ($xml->hasAttribute('validUntil')) { + $this->validUntil = SimpleSAML_Utilities::parseSAML2Time($xml->getAttribute('validUntil')); + } + if ($xml->hasAttribute('cacheDuration')) { + $this->cacheDuration = $xml->getAttribute('cacheDuration'); + } + if ($xml->hasAttribute('Name')) { + $this->Name = $xml->getAttribute('Name'); + } + + $this->Extensions = SAML2_XML_md_Extensions::getList($xml); + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:EntityDescriptor|./saml_metadata:EntitiesDescriptor') as $node) { + if ($node->localName === 'EntityDescriptor') { + $this->children[] = new SAML2_XML_md_EntityDescriptor($node); + } else { + $this->children[] = new SAML2_XML_md_EntitiesDescriptor($node); + } + } + } + + + /** + * Convert this EntitiesDescriptor to XML. + * + * @param DOMElement|NULL $parent The EntitiesDescriptor we should append this EntitiesDescriptor to. + */ + public function toXML(DOMElement $parent = NULL) { + assert('is_null($this->ID) || is_string($this->ID)'); + assert('is_null($this->validUntil) || is_int($this->validUntil)'); + assert('is_null($this->cacheDuration) || is_string($this->cacheDuration)'); + assert('is_array($this->Extensions)'); + assert('is_array($this->children)'); + + if ($parent === NULL) { + $doc = new DOMDocument(); + $e = $doc->createElementNS(SAML2_Const::NS_MD, 'md:EntitiesDescriptor'); + } else { + $e = $parent->ownerDocument->createElementNS(SAML2_Const::NS_MD, 'md:EntitiesDescriptor'); + $parent->appendChild($e); + } + + if (isset($this->ID)) { + $e->setAttribute('ID', $this->ID); + } + + if (isset($this->validUntil)) { + $e->setAttribute('validUntil', gmdate('Y-m-d\TH:i:s\Z', $this->validUntil)); + } + + if (isset($this->cacheDuration)) { + $e->setAttribute('cacheDuration', $this->cacheDuration); + } + + SAML2_XML_md_Extensions::addList($e, $this->Extensions); + + foreach ($this->children as $node) { + $node->toXML($e); + } + + $this->signElement($e, $e->firstChild); + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/EntityDescriptor.php b/lib/SAML2/XML/md/EntityDescriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..bd35f144ee545dbd5335e276eece343c93af65e7 --- /dev/null +++ b/lib/SAML2/XML/md/EntityDescriptor.php @@ -0,0 +1,251 @@ +<?php + +/** + * Class representing SAML 2 EntityDescriptor element. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_EntityDescriptor extends SAML2_SignedElementHelper { + + /** + * The entityID this EntityDescriptor represents. + * + * @var string + */ + public $entityID; + + + /** + * The ID of this element. + * + * @var string|NULL + */ + public $ID; + + + /** + * How long this element is valid, as a unix timestamp. + * + * @var int|NULL + */ + public $validUntil; + + + /** + * The length of time this element can be cached, as string. + * + * @var string|NULL + */ + public $cacheDuration; + + + /** + * Extensions on this element. + * + * Array of extension elements. + * + * @var array + */ + public $Extensions = array(); + + + /** + * Array with all roles for this entity. + * + * Array of SAML2_XML_md_RoleDescriptor objects (and subclasses of RoleDescriptor). + * + * @var array + */ + public $RoleDescriptor = array(); + + + /** + * AffiliationDescriptor of this entity. + * + * @var SAML2_XML_md_AffiliationDescriptor|NULL + */ + public $AffiliationDescriptor = NULL; + + + /** + * Organization of this entity. + * + * @var SAML2_XML_md_Organization|NULL + */ + public $Organization = NULL; + + + /** + * ContactPerson elements for this entity. + * + * @var array + */ + public $ContactPerson = array(); + + + /** + * AdditionalMetadataLocation elements for this entity. + * + * @var array + */ + public $AdditionalMetadataLocation = array(); + + + /** + * Initialize an EntitiyDescriptor. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + parent::__construct($xml); + + if ($xml === NULL) { + return; + } + + if (!$xml->hasAttribute('entityID')) { + throw new Exception('Missing required attribute entityID on EntityDescriptor.'); + } + $this->entityID = $xml->getAttribute('entityID'); + + if ($xml->hasAttribute('ID')) { + $this->ID = $xml->getAttribute('ID'); + } + if ($xml->hasAttribute('validUntil')) { + $this->validUntil = SimpleSAML_Utilities::parseSAML2Time($xml->getAttribute('validUntil')); + } + if ($xml->hasAttribute('cacheDuration')) { + $this->cacheDuration = $xml->getAttribute('cacheDuration'); + } + + $this->Extensions = SAML2_XML_md_Extensions::getList($xml); + + for ($node = $xml->firstChild; $node !== NULL; $node = $node->nextSibling) { + if (!($node instanceof DOMElement)) { + continue; + } + + if ($node->namespaceURI !== SAML2_Const::NS_MD) { + continue; + } + + switch ($node->localName) { + case 'RoleDescriptor': + $this->RoleDescriptor[] = new SAML2_XML_md_UnknownRoleDescriptor($node); + break; + case 'IDPSSODescriptor': + $this->RoleDescriptor[] = new SAML2_XML_md_IDPSSODescriptor($node); + break; + case 'SPSSODescriptor': + $this->RoleDescriptor[] = new SAML2_XML_md_SPSSODescriptor($node); + break; + case 'AuthnAuthorityDescriptor': + $this->RoleDescriptor[] = new SAML2_XML_md_AuthnAuthorityDescriptor($node); + break; + case 'AttributeAuthorityDescriptor': + $this->RoleDescriptor[] = new SAML2_XML_md_AttributeAuthorityDescriptor($node); + break; + case 'PDPDescriptor': + $this->RoleDescriptor[] = new SAML2_XML_md_PDPDescriptor($node); + break; + } + } + + $affiliationDescriptor = SAML2_Utils::xpQuery($xml, './saml_metadata:AffiliationDescriptor'); + if (count($affiliationDescriptor) > 1) { + throw new Exception('More than one AffiliationDescriptor in the entity.'); + } elseif (!empty($affiliationDescriptor)) { + $this->AffiliationDescriptor = new SAML2_XML_md_AffiliationDescriptor($affiliationDescriptor[0]); + } + + if (empty($this->RoleDescriptor) && is_null($this->AffiliationDescriptor)) { + throw new Exception('Must have either one of the RoleDescriptors or an AffiliationDescriptor in EntityDescriptor.'); + } elseif (!empty($this->RoleDescriptor) && !is_null($this->AffiliationDescriptor)) { + throw new Exception('AffiliationDescriptor cannot be combined with other RoleDescriptor elements in EntityDescriptor.'); + } + + $organization = SAML2_Utils::xpQuery($xml, './saml_metadata:Organization'); + if (count($organization) > 1) { + throw new Exception('More than one Organization in the entity.'); + } elseif (!empty($organization)) { + $this->Organization = new SAML2_XML_md_Organization($organization[0]); + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:ContactPerson') as $cp) { + $this->ContactPerson[] = new SAML2_XML_md_ContactPerson($cp); + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AdditionalMetadataLocation') as $aml) { + $this->AdditionalMetadataLocation[] = new SAML2_XML_md_AdditionalMetadataLocation($aml); + } + } + + + /** + * Create this EntityDescriptor. + * + * @param DOMElement|NULL $parent The EntitiesDescriptor we should append this EntityDescriptor to. + */ + public function toXML(DOMElement $parent = NULL) { + assert('is_string($this->entityID)'); + assert('is_null($this->ID) || is_string($this->ID)'); + assert('is_null($this->validUntil) || is_int($this->validUntil)'); + assert('is_null($this->cacheDuration) || is_string($this->cacheDuration)'); + assert('is_array($this->Extensions)'); + assert('is_array($this->RoleDescriptor)'); + assert('is_null($this->AffiliationDescriptor) || $this->AffiliationDescriptor instanceof SAML2_XML_md_AffiliationDescriptor'); + assert('is_null($this->Organization) || $this->Organization instanceof SAML2_XML_md_Organization'); + assert('is_array($this->ContactPerson)'); + assert('is_array($this->AdditionalMetadataLocation)'); + + if ($parent === NULL) { + $doc = new DOMDocument(); + $e = $doc->createElementNS(SAML2_Const::NS_MD, 'md:EntityDescriptor'); + } else { + $e = $parent->ownerDocument->createElementNS(SAML2_Const::NS_MD, 'md:EntityDescriptor'); + $parent->appendChild($e); + } + + $e->setAttribute('entityID', $this->entityID); + + if (isset($this->ID)) { + $e->setAttribute('ID', $this->ID); + } + + if (isset($this->validUntil)) { + $e->setAttribute('validUntil', gmdate('Y-m-d\TH:i:s\Z', $this->validUntil)); + } + + if (isset($this->cacheDuration)) { + $e->setAttribute('cacheDuration', $this->cacheDuration); + } + + SAML2_XML_md_Extensions::addList($e, $this->Extensions); + + foreach ($this->RoleDescriptor as $n) { + $n->toXML($e); + } + + if (isset($this->AffiliationDescriptor)) { + $this->AffiliationDescriptor->toXML($e); + } + + if (isset($this->Organization)) { + $this->Organization->toXML($e); + } + + foreach ($this->ContactPerson as $cp) { + $cp->toXML($e); + } + + foreach ($this->AdditionalMetadataLocation as $n) { + $n->toXML($e); + } + + $this->signElement($e, $e->firstChild); + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/Extensions.php b/lib/SAML2/XML/md/Extensions.php new file mode 100644 index 0000000000000000000000000000000000000000..2f5e9ae915bfe967ec8c5f32ed760220e3da0fcf --- /dev/null +++ b/lib/SAML2/XML/md/Extensions.php @@ -0,0 +1,54 @@ +<?php + +/** + * Class for handling SAML2 metadata extensions. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_Extensions { + + /** + * Get a list of Extensions in the given element. + * + * @param DOMElement $parent The element that may contain the md:Extensions element. + * @return array Array of extensions. + */ + public static function getList(DOMElement $parent) { + + $ret = array(); + foreach (SAML2_Utils::xpQuery($parent, './saml_metadata:Extensions/*') as $node) { + if ($node->namespaceURI === SAML2_XML_shibmd_Scope::NS && $node->localName === 'Scope') { + $ret[] = new SAML2_XML_shibmd_Scope($node); + } elseif ($node->namespaceURI === SAML2_XML_mdattr_EntityAttributes::NS && $node->localName === 'EntityAttributes') { + $ret[] = new SAML2_XML_mdattr_EntityAttributes($node); + } else { + $ret[] = new SAML2_XML_Chunk($node); + } + } + + return $ret; + } + + + /** + * Add a list of Extensions to the given element. + * + * @param DOMElement $parent The element we should add the extensions to. + * @param array $extensions List of extension objects. + */ + public static function addList(DOMElement $parent, array $extensions) { + + if (empty($extensions)) { + return; + } + + $extElement = $parent->ownerDocument->createElementNS(SAML2_Const::NS_MD, 'md:Extensions'); + $parent->appendChild($extElement); + + foreach ($extensions as $ext) { + $ext->toXML($extElement); + } + } + +} diff --git a/lib/SAML2/XML/md/IDPSSODescriptor.php b/lib/SAML2/XML/md/IDPSSODescriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..67116ced880851378fa257bf891bf01dfb9cc20a --- /dev/null +++ b/lib/SAML2/XML/md/IDPSSODescriptor.php @@ -0,0 +1,145 @@ +<?php + +/** + * Class representing SAML 2 IDPSSODescriptor. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_IDPSSODescriptor extends SAML2_XML_md_SSODescriptorType { + + /** + * Whether AuthnRequests sent to this IdP should be signed. + * + * @var bool|NULL + */ + public $WantAuthnRequestsSigned = NULL; + + + /** + * List of SingleSignOnService endpoints. + * + * Array with EndpointType objects. + * + * @var array + */ + public $SingleSignOnService = array(); + + + /** + * List of NameIDMappingService endpoints. + * + * Array with EndpointType objects. + * + * @var array + */ + public $NameIDMappingService = array(); + + + /** + * List of AssertionIDRequestService endpoints. + * + * Array with EndpointType objects. + * + * @var array + */ + public $AssertionIDRequestService = array(); + + + /** + * List of supported attribute profiles. + * + * Array with strings. + * + * @var array + */ + public $AttributeProfile = array(); + + + /** + * List of supported attributes. + * + * Array with SAML2_XML_saml_Attribute objects. + * + * @var array + */ + public $Attribute = array(); + + + /** + * Initialize an IDPSSODescriptor. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + parent::__construct('md:IDPSSODescriptor', $xml); + + if ($xml === NULL) { + return; + } + + $this->WantAuthnRequestsSigned = SAML2_Utils::parseBoolean($xml, 'WantAuthnRequestsSigned', NULL); + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:SingleSignOnService') as $ep) { + $this->SingleSignOnService[] = new SAML2_XML_md_EndpointType($ep); + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:NameIDMappingService') as $ep) { + $this->NameIDMappingService[] = new SAML2_XML_md_EndpointType($ep); + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AssertionIDRequestService') as $ep) { + $this->AssertionIDRequestService[] = new SAML2_XML_md_EndpointType($airs); + } + + $this->AttributeProfile = SAML2_Utils::extractStrings($xml, './saml_metadata:AttributeProfile'); + + foreach (SAML2_Utils::xpQuery($xml, './saml_assertion:Attribute') as $a) { + $this->Attribute[] = new SAML2_XML_saml_Attribute($a); + } + } + + + /** + * Add this IDPSSODescriptor to an EntityDescriptor. + * + * @param DOMElement $parent The EntityDescriptor we should append this IDPSSODescriptor to. + */ + public function toXML(DOMElement $parent) { + assert('is_null($this->WantAuthnRequestsSigned) || is_bool($this->WantAuthnRequestsSigned)'); + assert('is_array($this->SingleSignOnService)'); + assert('is_array($this->NameIDMappingService)'); + assert('is_array($this->AssertionIDRequestService)'); + assert('is_array($this->AttributeProfile)'); + assert('is_array($this->Attribute)'); + + $e = parent::toXML($parent); + + if ($this->WantAuthnRequestsSigned === TRUE) { + $e->setAttribute('WantAuthnRequestsSigned', 'true'); + } elseif ($this->WantAuthnRequestsSigned === FALSE) { + $e->setAttribute('WantAuthnRequestsSigned', 'false'); + } + + foreach ($this->SingleSignOnService as $ep) { + $ep->toXML($e, 'md:SingleSignOnService'); + } + + foreach ($this->NameIDMappingService as $ep) { + $ep->toXML($e, 'md:NameIDMappingService'); + } + + foreach ($this->AssertionIDRequestService as $ep) { + $ep->toXML($e, 'md:AssertionIDRequestService'); + } + + SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:AttributeProfile', FALSE, $this->AttributeProfile); + + foreach ($this->Attribute as $a) { + $a->toXML($e); + } + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/IndexedEndpointType.php b/lib/SAML2/XML/md/IndexedEndpointType.php new file mode 100644 index 0000000000000000000000000000000000000000..c0191525c945d2bcf935cbbad1747a31b48101ab --- /dev/null +++ b/lib/SAML2/XML/md/IndexedEndpointType.php @@ -0,0 +1,71 @@ +<?php + +/** + * Class representing SAML 2 IndexedEndpointType. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_IndexedEndpointType extends SAML2_XML_md_EndpointType { + + /** + * The index for this endpoint. + * + * @var int + */ + public $index; + + + /** + * Whether this endpoint is the default. + * + * @var bool|NULL + */ + public $isDefault = NULL; + + + /** + * Initialize an IndexedEndpointType. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + parent::__construct($xml); + + if ($xml === NULL) { + return; + } + + if (!$xml->hasAttribute('index')) { + throw new Exception('Missing index on ' . $xml->tagName); + } + $this->index = (int)$xml->getAttribute('index'); + + $this->isDefault = SAML2_Utils::parseBoolean($xml, 'isDefault', NULL); + } + + + /** + * Add this endpoint to an XML element. + * + * @param DOMElement $parent The element we should append this endpoint to. + * @param string $name The name of the element we should create. + */ + public function toXML(DOMElement $parent, $name) { + assert('is_string($name)'); + assert('is_int($this->index)'); + assert('is_null($this->isDefault) || is_bool($this->isDefault)'); + + $e = parent::toXML($parent, $name); + $e->setAttribute('index', (string)$this->index); + + if ($this->isDefault === TRUE) { + $e->setAttribute('isDefault', 'true'); + } elseif ($this->isDefault === FALSE) { + $e->setAttribute('isDefault', 'false'); + } + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/KeyDescriptor.php b/lib/SAML2/XML/md/KeyDescriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..aeaffe9a53cc424fd19c24cf475f6fa9c010a39d --- /dev/null +++ b/lib/SAML2/XML/md/KeyDescriptor.php @@ -0,0 +1,97 @@ +<?php + +/** + * Class representing a KeyDescriptor element. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_KeyDescriptor { + + /** + * What this key can be used for. + * + * 'encryption', 'signing' or NULL. + * + * @var string|NULL + */ + public $use; + + + /** + * The KeyInfo for this key. + * + * @var SAML2_XML_ds_KeyInfo + */ + public $KeyInfo; + + + /** + * Supported EncryptionMethods. + * + * Array of SAML2_XML_Chunk objects. + * + * @var array + */ + public $EncryptionMethod = array(); + + + /** + * Initialize an KeyDescriptor. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + + if ($xml === NULL) { + return; + } + + if ($xml->hasAttribute('use')) { + $this->use = $xml->getAttribute('use'); + } + + $keyInfo = SAML2_Utils::xpQuery($xml, './ds:KeyInfo'); + if (count($keyInfo) > 1) { + throw new Exception('More than one ds:KeyInfo in the KeyDescriptor.'); + } elseif (empty($keyInfo)) { + throw new Exception('No ds:KeyInfo in the KeyDescriptor.'); + } + $this->KeyInfo = new SAML2_XML_ds_KeyInfo($keyInfo[0]); + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:EncryptionMethod') as $em) { + $this->EncryptionMethod[] = new SAML2_XML_Chunk($em); + } + + } + + + /** + * Convert this KeyDescriptor to XML. + * + * @param DOMElement $parent The element we should append this KeyDescriptor to. + */ + public function toXML(DOMElement $parent) { + assert('is_null($this->use) || is_string($this->use)'); + assert('$this->KeyInfo instanceof SAML2_XML_ds_KeyInfo'); + assert('is_array($this->EncryptionMethod)'); + + $doc = $parent->ownerDocument; + + $e = $doc->createElementNS(SAML2_Const::NS_MD, 'md:KeyDescriptor'); + $parent->appendChild($e); + + if (isset($this->use)) { + $e->setAttribute('use', $this->use); + } + + $this->KeyInfo->toXML($e); + + foreach ($this->EncryptionMethod as $em) { + $em->toXML($e); + } + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/Organization.php b/lib/SAML2/XML/md/Organization.php new file mode 100644 index 0000000000000000000000000000000000000000..3869b042ae5ae7f04116e3a164d3f1ac51ed209f --- /dev/null +++ b/lib/SAML2/XML/md/Organization.php @@ -0,0 +1,105 @@ +<?php + +/** + * Class representing SAML 2 Organization element. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_Organization { + + /** + * Extensions on this element. + * + * Array of extension elements. + * + * @var array + */ + public $Extensions = array(); + + + /** + * The OrganizationName, as an array of language => translation. + * + * @var array + */ + public $OrganizationName = array(); + + + /** + * The OrganizationDisplayName, as an array of language => translation. + * + * @var array + */ + public $OrganizationDisplayName = array(); + + + /** + * The OrganizationURL, as an array of language => translation. + * + * @var array + */ + public $OrganizationURL = array(); + + + /** + * Initialize an Organization element. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + + if ($xml === NULL) { + return; + } + + $this->Extensions = SAML2_XML_md_Extensions::getList($xml); + + + $this->OrganizationName = SAML2_Utils::extractLocalizedStrings($xml, './saml_metadata:OrganizationName'); + if (empty($this->OrganizationName)) { + $this->OrganizationName = array('invalid' => ''); + } + + $this->OrganizationDisplayName = SAML2_Utils::extractLocalizedStrings($xml, './saml_metadata:OrganizationDisplayName'); + if (empty($this->OrganizationDisplayName)) { + $this->OrganizationDisplayName = array('invalid' => ''); + } + + $this->OrganizationURL = SAML2_Utils::extractLocalizedStrings($xml, './saml_metadata:OrganizationURL'); + if (empty($this->OrganizationURL)) { + $this->OrganizationURL = array('invalid' => ''); + } + } + + + /** + * Convert this Organization to XML. + * + * @param DOMElement $parent The element we should add this organization to. + * @return DOMElement This Organization-element. + */ + public function toXML(DOMElement $parent) { + assert('is_array($this->Extensions)'); + assert('is_array($this->OrganizationName)'); + assert('!empty($this->OrganizationName)'); + assert('is_array($this->OrganizationDisplayName)'); + assert('!empty($this->OrganizationDisplayName)'); + assert('is_array($this->OrganizationURL)'); + assert('!empty($this->OrganizationURL)'); + + $doc = $parent->ownerDocument; + + $e = $doc->createElementNS(SAML2_Const::NS_MD, 'md:Organization'); + $parent->appendChild($e); + + SAML2_XML_md_Extensions::addList($e, $this->Extensions); + + SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:OrganizationName', TRUE, $this->OrganizationName); + SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:OrganizationDisplayName', TRUE, $this->OrganizationDisplayName); + SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:OrganizationURL', TRUE, $this->OrganizationURL); + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/PDPDescriptor.php b/lib/SAML2/XML/md/PDPDescriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..e2765fdea3f76e0bbf2e9e2a7e788c8cab1fe8aa --- /dev/null +++ b/lib/SAML2/XML/md/PDPDescriptor.php @@ -0,0 +1,94 @@ +<?php + +/** + * Class representing SAML 2 metadata PDPDescriptor. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_PDPDescriptor extends SAML2_XML_md_RoleDescriptor { + + /** + * List of AuthzService endpoints. + * + * Array with EndpointType objects. + * + * @var array + */ + public $AuthzService = array(); + + + /** + * List of AssertionIDRequestService endpoints. + * + * Array with EndpointType objects. + * + * @var array + */ + public $AssertionIDRequestService = array(); + + + /** + * List of supported NameID formats. + * + * Array of strings. + * + * @var array + */ + public $NameIDFormat = array(); + + + /** + * Initialize an IDPSSODescriptor. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + parent::__construct('md:PDPDescriptor', $xml); + + if ($xml === NULL) { + return; + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AuthzService') as $ep) { + $this->AuthzService[] = new SAML2_XML_md_EndpointType($ep); + } + if (empty($this->AuthzService)) { + throw new Exception('Must have at least one AuthzService in PDPDescriptor.'); + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AssertionIDRequestService') as $ep) { + $this->AssertionIDRequestService[] = new SAML2_XML_md_EndpointType($airs); + } + + $this->NameIDFormat = SAML2_Utils::extractStrings($xml, './saml_metadata:NameIDFormat'); + } + + + /** + * Add this PDPDescriptor to an EntityDescriptor. + * + * @param DOMElement $parent The EntityDescriptor we should append this IDPSSODescriptor to. + */ + public function toXML(DOMElement $parent) { + assert('is_array($this->AuthzService)'); + assert('!empty($this->AuthzService)'); + assert('is_array($this->AssertionIDRequestService)'); + assert('is_array($this->NameIDFormat)'); + + $e = parent::toXML($parent); + + foreach ($this->AuthzService as $ep) { + $ep->toXML($e, 'md:AuthzService'); + } + + foreach ($this->AssertionIDRequestService as $ep) { + $ep->toXML($e, 'md:AssertionIDRequestService'); + } + + SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:NameIDFormat', FALSE, $this->NameIDFormat); + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/RequestedAttribute.php b/lib/SAML2/XML/md/RequestedAttribute.php new file mode 100644 index 0000000000000000000000000000000000000000..124a25daa2139633865241a4d6f9116dc54462e8 --- /dev/null +++ b/lib/SAML2/XML/md/RequestedAttribute.php @@ -0,0 +1,54 @@ +<?php + +/** + * Class representing SAML 2 metadata RequestedAttribute. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_RequestedAttribute extends SAML2_XML_saml_Attribute { + + /** + * Whether this attribute is required. + * + * @var bool|NULL + */ + public $isRequired = NULL; + + + /** + * Initialize an RequestedAttribute. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + parent::__construct($xml); + + if ($xml === NULL) { + return; + } + + $this->isRequired = SAML2_Utils::parseBoolean($xml, 'isRequired', NULL); + } + + + /** + * Convert this RequestedAttribute to XML. + * + * @param DOMElement $parent The element we should append this RequestedAttribute to. + */ + public function toXML(DOMElement $parent) { + assert('is_bool($this->isRequired) || is_null($this->isRequired)'); + + $e = $this->toXMLInternal($parent, SAML2_Const::NS_MD, 'md:RequestedAttribute'); + + if ($this->isRequired === TRUE) { + $e->setAttribute('isRequired', 'true'); + } elseif ($this->isRequired === FALSE) { + $e->setAttribute('isRequired', 'false'); + } + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/RoleDescriptor.php b/lib/SAML2/XML/md/RoleDescriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..346d34cc1376419d54f29c1c775dc212e24b93d0 --- /dev/null +++ b/lib/SAML2/XML/md/RoleDescriptor.php @@ -0,0 +1,208 @@ +<?php + +/** + * Class representing SAML 2 RoleDescriptor element. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_RoleDescriptor extends SAML2_SignedElementHelper { + + /** + * The name of this descriptor element. + * + * @var string + */ + private $elementName; + + + /** + * The ID of this element. + * + * @var string|NULL + */ + public $ID; + + + /** + * How long this element is valid, as a unix timestamp. + * + * @var int|NULL + */ + public $validUntil; + + + /** + * The length of time this element can be cached, as string. + * + * @var string|NULL + */ + public $cacheDuration; + + + /** + * List of supported protocols. + * + * @var array + */ + public $protocolSupportEnumeration = array(); + + + /** + * Error URL for this role. + * + * @var string|NULL + */ + public $errorURL; + + + /** + * Extensions on this element. + * + * Array of extension elements. + * + * @var array + */ + public $Extensions = array(); + + + /** + * KeyDescriptor elements. + * + * Array of SAML2_XML_md_KeyDescriptor elements. + * + * @var array + */ + public $KeyDescriptor = array(); + + + /** + * Organization of this role. + * + * @var SAML2_XML_md_Organization|NULL + */ + public $Organization = NULL; + + + /** + * ContactPerson elements for this role. + * + * Array of SAML2_XML_md_ContactPerson objects. + * + * @var array + */ + public $ContactPerson = array(); + + + /** + * Initialize a RoleDescriptor. + * + * @param string $elementName The name of this element. + * @param DOMElement|NULL $xml The XML element we should load. + */ + protected function __construct($elementName, DOMElement $xml = NULL) { + assert('is_string($elementName)'); + + parent::__construct($xml); + $this->elementName = $elementName; + + if ($xml === NULL) { + return; + } + + if ($xml->hasAttribute('ID')) { + $this->ID = $xml->getAttribute('ID'); + } + if ($xml->hasAttribute('validUntil')) { + $this->validUntil = SimpleSAML_Utilities::parseSAML2Time($xml->getAttribute('validUntil')); + } + if ($xml->hasAttribute('cacheDuration')) { + $this->cacheDuration = $xml->getAttribute('cacheDuration'); + } + + if (!$xml->hasAttribute('protocolSupportEnumeration')) { + throw new Exception('Missing protocolSupportEnumeration attribute on ' . $xml->localName); + } + $this->protocolSupportEnumeration = preg_split('/[\s]+/', $xml->getAttribute('protocolSupportEnumeration')); + + if ($xml->hasAttribute('errorURL')) { + $this->errorURL = $xml->getAttribute('errorURL'); + } + + + $this->Extensions = SAML2_XML_md_Extensions::getList($xml); + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:KeyDescriptor') as $kd) { + $this->KeyDescriptor[] = new SAML2_XML_md_KeyDescriptor($kd); + } + + $organization = SAML2_Utils::xpQuery($xml, './saml_metadata:Organization'); + if (count($organization) > 1) { + throw new Exception('More than one Organization in the entity.'); + } elseif (!empty($organization)) { + $this->Organization = new SAML2_XML_md_Organization($organization[0]); + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:ContactPerson') as $cp) { + $this->contactPersons[] = new SAML2_XML_md_ContactPerson($cp); + } + } + + + /** + * Add this RoleDescriptor to an EntityDescriptor. + * + * @param DOMElement $parent The EntityDescriptor we should append this endpoint to. + * @param string $name The name of the element we should create. + */ + protected function toXML(DOMElement $parent) { + assert('is_null($this->ID) || is_string($this->ID)'); + assert('is_null($this->validUntil) || is_int($this->validUntil)'); + assert('is_null($this->cacheDuration) || is_string($this->cacheDuration)'); + assert('is_array($this->protocolSupportEnumeration)'); + assert('is_null($this->errorURL) || is_string($this->errorURL)'); + assert('is_array($this->Extensions)'); + assert('is_array($this->KeyDescriptor)'); + assert('is_null($this->Organization) || $this->Organization instanceof SAML2_XML_md_Organization'); + assert('is_array($this->ContactPerson)'); + + $e = $parent->ownerDocument->createElementNS(SAML2_Const::NS_MD, $this->elementName); + $parent->appendChild($e); + + if (isset($this->ID)) { + $e->setAttribute('ID', $this->ID); + } + + if (isset($this->validUntil)) { + $e->setAttribute('validUntil', gmdate('Y-m-d\TH:i:s\Z', $this->validUntil)); + } + + if (isset($this->cacheDuration)) { + $e->setAttribute('cacheDuration', $this->cacheDuration); + } + + $e->setAttribute('protocolSupportEnumeration', implode(' ', $this->protocolSupportEnumeration)); + + if (isset($this->errorURL)) { + $e->setAttribute('errorURL', $this->errorURL); + } + + + SAML2_XML_md_Extensions::addList($e, $this->Extensions); + + foreach ($this->KeyDescriptor as $kd) { + $kd->toXML($e); + } + + if (isset($this->Organization)) { + $this->Organization->toXML($e); + } + + foreach ($this->ContactPerson as $cp) { + $cp->toXML($e); + } + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/SPSSODescriptor.php b/lib/SAML2/XML/md/SPSSODescriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..da7077e0028d6ff82d31314867d72141c2a5f72b --- /dev/null +++ b/lib/SAML2/XML/md/SPSSODescriptor.php @@ -0,0 +1,107 @@ +<?php + +/** + * Class representing SAML 2 SPSSODescriptor. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_SPSSODescriptor extends SAML2_XML_md_SSODescriptorType { + + /** + * Whether this SP signs authentication requests. + * + * @var bool|NULL + */ + public $AuthnRequestsSigned = NULL; + + + /** + * Whether this SP wants the Assertion elements to be signed. + * + * @var bool|NULL + */ + public $WantAssertionsSigned = NULL; + + + /** + * List of AssertionConsumerService endpoints for this SP. + * + * Array with IndexedEndpointType objects. + * + * @var array + */ + public $AssertionConsumerService = array(); + + + /** + * List of AttributeConsumingService descriptors for this SP. + * + * Array with SAML2_XML_md_AttribteConsumingService objects. + * + * @var array + */ + public $AttributeConsumingService = array(); + + + /** + * Initialize a SPSSODescriptor. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + parent::__construct('md:SPSSODescriptor', $xml); + + if ($xml === NULL) { + return; + } + + $this->AuthnRequestsSigned = SAML2_Utils::parseBoolean($xml, 'AuthnRequestsSigned', NULL); + $this->WantAssertionsSigned = SAML2_Utils::parseBoolean($xml, 'WantAssertionsSigned', NULL); + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AssertionConsumerService') as $ep) { + $this->AssertionConsumerService[] = new SAML2_XML_md_IndexedEndpointType($ep); + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AttributeConsumingService') as $acs) { + $this->AttributeConsumingService[] = new SAML2_XML_md_AttributeConsumingService($acs); + } + } + + + /** + * Add this SPSSODescriptor to an EntityDescriptor. + * + * @param DOMElement $parent The EntityDescriptor we should append this SPSSODescriptor to. + */ + public function toXML(DOMElement $parent) { + assert('is_null($this->AuthnRequestsSigned) || is_bool($this->AuthnRequestsSigned)'); + assert('is_null($this->WantAssertionsSigned) || is_bool($this->WantAssertionsSigned)'); + assert('is_array($this->AssertionConsumerService)'); + assert('is_array($this->AttributeConsumingService)'); + + $e = parent::toXML($parent); + + if ($this->AuthnRequestsSigned === TRUE) { + $e->setAttribute('AuthnRequestsSigned', 'true'); + } elseif ($this->AuthnRequestsSigned === FALSE) { + $e->setAttribute('AuthnRequestsSigned', 'false'); + } + + if ($this->WantAssertionsSigned === TRUE) { + $e->setAttribute('WantAssertionsSigned', 'true'); + } elseif ($this->WantAssertionsSigned === FALSE) { + $e->setAttribute('WantAssertionsSigned', 'false'); + } + + + foreach ($this->AssertionConsumerService as $ep) { + $ep->toXML($e, 'md:AssertionConsumerService'); + } + + foreach ($this->AttributeConsumingService as $acs) { + $acs->toXML($e); + } + } + +} diff --git a/lib/SAML2/XML/md/SSODescriptorType.php b/lib/SAML2/XML/md/SSODescriptorType.php new file mode 100644 index 0000000000000000000000000000000000000000..4ba5f5a5777076098b2dd53f57d9da33bc348766 --- /dev/null +++ b/lib/SAML2/XML/md/SSODescriptorType.php @@ -0,0 +1,114 @@ +<?php + +/** + * Class representing SAML 2 SSODescriptorType. + * + * @package simpleSAMLphp + * @version $Id$ + */ +abstract class SAML2_XML_md_SSODescriptorType extends SAML2_XML_md_RoleDescriptor { + + /** + * List of ArtifactResolutionService endpoints. + * + * Array with IndexedEndpointType objects. + * + * @var array + */ + public $ArtifactResolutionService = array(); + + + /** + * List of SingleLogoutService endpoints. + * + * Array with EndpointType objects. + * + * @var array + */ + public $SingleLogoutService = array(); + + + /** + * List of ManageNameIDService endpoints. + * + * Array with EndpointType objects. + * + * @var array + */ + public $ManageNameIDService = array(); + + + /** + * List of supported NameID formats. + * + * Array of strings. + * + * @var array + */ + public $NameIDFormat = array(); + + + /** + * Initialize a SSODescriptor. + * + * @param string $elementName The name of this element. + * @param DOMElement|NULL $xml The XML element we should load. + */ + protected function __construct($elementName, DOMElement $xml = NULL) { + assert('is_string($elementName)'); + + parent::__construct($elementName, $xml); + + if ($xml === NULL) { + return; + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:ArtifactResolutionService') as $ep) { + $this->ArtifactResolutionService[] = new SAML2_XML_md_IndexedEndpointType($ep); + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:SingleLogoutService') as $ep) { + $this->SingleLogoutService[] = new SAML2_XML_md_EndpointType($ep); + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:ManageNameIDService') as $ep) { + $this->ManageNameIDService[] = new SAML2_XML_md_EndpointType($ep); + } + + $this->NameIDFormat = SAML2_Utils::extractStrings($xml, './saml_metadata:NameIDFormat'); + } + + + /** + * Add this SSODescriptorType to an EntityDescriptor. + * + * @param DOMElement $parent The EntityDescriptor we should append this SSODescriptorType to. + * @param string $name The name of the element we should create. + * @return DOMElement The generated SSODescriptor DOMElement. + */ + protected function toXML(DOMElement $parent) { + assert('is_array($this->ArtifactResolutionService)'); + assert('is_array($this->SingleLogoutService)'); + assert('is_array($this->ManageNameIDService)'); + assert('is_array($this->NameIDFormat)'); + + $e = parent::toXML($parent); + + foreach ($this->ArtifactResolutionService as $ep) { + $ep->toXML($e, 'md:ArtifactResolutionService'); + } + + foreach ($this->SingleLogoutService as $ep) { + $ep->toXML($e, 'md:SingleLogoutService'); + } + + foreach ($this->ManageNameIDService as $ep) { + $ep->toXML($e, 'md:ManageNameIDService'); + } + + SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:NameIDFormat', FALSE, $this->NameIDFormat); + + return $e; + } + +} diff --git a/lib/SAML2/XML/md/UnknownRoleDescriptor.php b/lib/SAML2/XML/md/UnknownRoleDescriptor.php new file mode 100644 index 0000000000000000000000000000000000000000..66e3a7986a643a46ca52afbe9dfcb44b820ec529 --- /dev/null +++ b/lib/SAML2/XML/md/UnknownRoleDescriptor.php @@ -0,0 +1,41 @@ +<?php + +/** + * Class representing unknown RoleDescriptors. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_md_UnknownRoleDescriptor extends SAML2_XML_md_RoleDescriptor { + + /** + * This RoleDescriptor as XML + * + * @var SAML2_XML_Chunk + */ + private $xml; + + + /** + * Initialize an unknown RoleDescriptor. + * + * @param DOMElement $xml The XML element we should load. + */ + public function __construct(DOMElement $xml) { + parent::__construct('md:RoleDescriptor', $xml); + + $this->xml = new SAML2_XML_Chunk($xml); + } + + + /** + * Add this RoleDescriptor to an EntityDescriptor. + * + * @param DOMElement $parent The EntityDescriptor we should append this RoleDescriptor to. + */ + public function toXML(DOMElement $parent) { + + $this->xml->toXML($parent); + } + +} diff --git a/lib/SAML2/XML/mdattr/EntityAttributes.php b/lib/SAML2/XML/mdattr/EntityAttributes.php new file mode 100644 index 0000000000000000000000000000000000000000..a53056917631589c1c74c6425bbd1a509c79ced5 --- /dev/null +++ b/lib/SAML2/XML/mdattr/EntityAttributes.php @@ -0,0 +1,70 @@ +<?php + +/** + * Class for handling the EntityAttributes metadata extension. + * + * @link: http://docs.oasis-open.org/security/saml/Post2.0/sstc-metadata-attr-cs-01.pdf + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_mdattr_EntityAttributes { + + /** + * The namespace used for the EntityAttributes extension. + */ + const NS = 'urn:oasis:names:tc:SAML:metadata:attribute'; + + + /** + * Array with child elements. + * + * The elements can be SAML2_XML_saml_Attribute or SAML2_XML_Chunk elements. + * + * @var array + */ + public $children; + + + /** + * Create a EntityAttributes element. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + + if ($xml === NULL) { + return; + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_assertion:Attribute|./saml_assertion:Assertion') as $node) { + if ($node->localName === 'Attribute') { + $this->children[] = new SAML2_XML_saml_Attribute($node); + } else { + $this->children[] = new SAML2_XML_Chunk($node); + } + } + + } + + + /** + * Convert this EntityAttributes to XML. + * + * @param DOMElement $parent The element we should append to. + */ + public function toXML(DOMElement $parent) { + assert('is_array($this->children)'); + + $doc = $parent->ownerDocument; + + $e = $doc->createElementNS(SAML2_XML_mdattr_EntityAttributes::NS, 'mdattr:EntityAttributes'); + $parent->appendChild($e); + + foreach ($this->children as $child) { + $child->toXML($e); + } + + return $e; + } + +} diff --git a/lib/SAML2/XML/saml/Attribute.php b/lib/SAML2/XML/saml/Attribute.php new file mode 100644 index 0000000000000000000000000000000000000000..afbf0cbbe5f5c22a84813e6f53497604e31bc845 --- /dev/null +++ b/lib/SAML2/XML/saml/Attribute.php @@ -0,0 +1,121 @@ +<?php + +/** + * Class representing SAML 2 Attribute. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_saml_Attribute { + + /** + * The Name of this attribute. + * + * @var string + */ + public $Name; + + + /** + * The NameFormat of this attribute. + * + * @var string|NULL + */ + public $NameFormat; + + + /** + * The FriendlyName of this attribute. + * + * @var string|NULL + */ + public $FriendlyName = NULL; + + + /** + * List of attribute values. + * + * Array of SAML2_XML_saml_AttributeValue elements. + * + * @var array + */ + public $AttributeValue = array(); + + + /** + * Initialize an Attribute. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + + if ($xml === NULL) { + return; + } + + if (!$xml->hasAttribute('Name')) { + throw new Exception('Missing Name on Attribute.'); + } + $this->Name = $xml->getAttribute('Name'); + + if ($xml->hasAttribute('NameFormat')) { + $this->NameFormat = $xml->getAttribute('NameFormat'); + } + + if ($xml->hasAttribute('FriendlyName')) { + $this->FriendlyName = $xml->getAttribute('FriendlyName'); + } + + foreach (SAML2_Utils::xpQuery($xml, './saml_assertion:AttributeValue') as $av) { + $this->AttributeValue[] = new SAML2_XML_saml_AttributeValue($av); + } + } + + + /** + * Internal implementation of toXML. + * This function allows RequestedAttribute to specify the element name and namespace. + * + * @param DOMElement $parent The element we should append this Attribute to. + * @param string $namespace The namespace the element should be created in. + * @param string $name The name of the element. + */ + protected function toXMLInternal(DOMElement $parent, $namespace, $name) { + assert('is_string($namespace)'); + assert('is_string($name)'); + assert('is_string($this->Name)'); + assert('is_null($this->NameFormat) || is_string($this->NameFormat)'); + assert('is_null($this->FriendlyName) || is_string($this->FriendlyName)'); + assert('is_array($this->AttributeValue)'); + + $e = $parent->ownerDocument->createElementNS($namespace, $name); + $parent->appendChild($e); + + $e->setAttribute('Name', $this->Name); + + if (isset($this->NameFormat)) { + $e->setAttribute('NameFormat', $this->NameFormat); + } + + if (isset($this->FriendlyName)) { + $e->setAttribute('FriendlyName', $this->FriendlyName); + } + + foreach ($this->AttributeValue as $av) { + $av->toXML($e); + } + + return $e; + } + + + /** + * Convert this Attribute to XML. + * + * @param DOMElement $parent The element we should append this Attribute to. + */ + public function toXML(DOMElement $parent) { + return $this->toXMLInternal($parent, SAML2_Const::NS_SAML, 'saml:Attribute'); + } + +} diff --git a/lib/SAML2/XML/saml/AttributeValue.php b/lib/SAML2/XML/saml/AttributeValue.php new file mode 100644 index 0000000000000000000000000000000000000000..5351e999af43f344802669a59561f3316f9719dd --- /dev/null +++ b/lib/SAML2/XML/saml/AttributeValue.php @@ -0,0 +1,92 @@ +<?php + +/** + * Class representing an AttributeValue. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_saml_AttributeValue { + + /** + * The raw DOMElement representing this value. + * + * @var DOMElement + */ + public $element; + + + /** + * Create an AttributeValue. + * + * @param mixed $value + * The value of this element. + * Can be one of: + * - string Create an attribute value with a simple string. + * - DOMElement(AttributeValue) Create an attribute value of the given DOMElement. + * - DOMElement Create an attribute value with the given DOMElement as a child. + */ + public function __construct($value) { + assert('is_string($value) || $value instanceof DOMElement'); + + if (is_string($value)) { + $doc = new DOMDocument(); + $this->element = $doc->createElementNS(SAML2_Const::NS_SAML, 'saml:AttributeValue'); + $this->element->setAttributeNS(SAML2_Const::NS_XSI, 'xsi:type', 'xs:string'); + $this->element->appendChild($doc->createTextNode($value)); + + /* Make sure that the xs-namespace is available in the AttributeValue (for xs:string). */ + $this->element->setAttributeNS(SAML2_Const::NS_XS, 'xs:tmp', 'tmp'); + $this->element->removeAttributeNS(SAML2_Const::NS_XS, 'tmp'); + + return; + } + + if ($value->namespaceURI === SAML2_Const::NS_SAML && $value->localName === 'AttributeValue') { + $this->element = SAML2_Utils::copyElement($value); + return; + } + + $doc = new DOMDocument(); + $this->element = $doc->createElementNS(SAML2_Const::NS_SAML, 'saml:AttributeValue'); + SAML2_Utils::copyElement($value, $this->element); + } + + + /** + * Append this attribute value to an element. + * + * @param DOMElement $parent The element we should append this attribute value to. + * @return DOMElement The generated AttributeValue element. + */ + public function toXML(DOMElement $parent) { + assert('$this->element instanceof DOMElement'); + assert('$this->element->namespaceURI === SAML2_Const::NS_SAML && $this->element->localName === "AttributeValue"'); + + $v = SAML2_Utils::copyElement($this->element, $parent); + + return $v; + } + + + /** + * Convert this attribute value to a string. + * + * If this element contains XML data, that data vil be encoded as a string and returned. + * + * @return string This attribute value. + */ + public function __toString() { + assert('$this->element instanceof DOMElement'); + + $doc = $this->element->ownerDocument; + + $ret = ''; + foreach ($this->element->childNodes as $c) { + $ret .= $doc->saveXML($c); + } + + return $ret; + } + +} diff --git a/lib/SAML2/XML/shibmd/Scope.php b/lib/SAML2/XML/shibmd/Scope.php new file mode 100644 index 0000000000000000000000000000000000000000..f095ba3fe0d07d628d67c4d9145b3b61e5f2bc3f --- /dev/null +++ b/lib/SAML2/XML/shibmd/Scope.php @@ -0,0 +1,74 @@ +<?php + +/** + * Class which represents the Scope element found in Shibboleth metadata. + * + * @link https://spaces.internet2.edu/display/SHIB/ShibbolethMetadataProfile + * @package simpleSAMLphp + * @version $Id$ + */ +class SAML2_XML_shibmd_Scope { + + /** + * The namespace used for the Scope extension element. + */ + const NS = 'urn:mace:shibboleth:metadata:1.0'; + + + /** + * The scope. + * + * @var string + */ + public $scope; + + /** + * Whether this is a regexp scope. + * + * @var bool|NULL + */ + public $regexp = NULL; + + + /** + * Create a Scope. + * + * @param DOMElement|NULL $xml The XML element we should load. + */ + public function __construct(DOMElement $xml = NULL) { + + if ($xml === NULL) { + return; + } + + $this->scope = $xml->textContent; + $this->regexp = SAML2_Utils::parseBoolean($xml, 'regexp', NULL); + } + + + /** + * Convert this Scope to XML. + * + * @param DOMElement $parent The element we should append this Scope to. + */ + public function toXML(DOMElement $parent) { + assert('is_string($this->scope)'); + assert('is_bool($this->regexp) || is_null($this->regexp)'); + + $doc = $parent->ownerDocument; + + $e = $doc->createElementNS(SAML2_XML_shibmd_Scope::NS, 'shibmd:Scope'); + $parent->appendChild($e); + + $e->appendChild($doc->createTextNode($this->scope)); + + if ($this->regexp === TRUE) { + $e->setAttribute('regexp', 'true'); + } elseif ($this->regexp === FALSE) { + $e->setAttribute('regexp', 'false'); + } + + return $e; + } + +}