spDescriptors = array(); $this->idpDescriptors = array(); $e = $entityElement->toXML(); $e = $e->ownerDocument->saveXML($e); $this->entityDescriptor = base64_encode($e); $this->entityId = $entityElement->entityID; $expireTime = self::getExpireTime($entityElement, $maxExpireTime); $this->validators = $validators; $this->validators[] = $entityElement; /* Process Extensions element, if it exists. */ $ext = self::processExtensions($entityElement); $this->scopes = $ext['scopes']; $this->tags = $ext['tags']; /* Look over the RoleDescriptors. */ foreach ($entityElement->RoleDescriptor as $child) { if ($child instanceof SAML2_XML_md_SPSSODescriptor) { $this->processSPSSODescriptor($child, $expireTime); } elseif ($child instanceof SAML2_XML_md_IDPSSODescriptor) { $this->processIDPSSODescriptor($child, $expireTime); } elseif ($child instanceof SAML2_XML_md_AttributeAuthorityDescriptor) { $this->processAttributeAuthorityDescriptor($child, $expireTime); } } if ($entityElement->Organization) { $this->processOrganization($entityElement->Organization); } } /** * This function parses a file which contains XML encoded metadata. * * @param $file The path to the file which contains the metadata. * @return An instance of this class with the metadata loaded. */ public static function parseFile($file) { $doc = new DOMDocument(); $res = $doc->load($file); if($res !== TRUE) { throw new Exception('Failed to read XML from file: ' . $file); } return self::parseDocument($doc); } /** * This function parses a string which contains XML encoded metadata. * * @param $metadata A string which contains XML encoded metadata. * @return An instance of this class with the metadata loaded. */ public static function parseString($metadata) { $doc = new DOMDocument(); $res = $doc->loadXML($metadata); if($res !== TRUE) { throw new Exception('Failed to parse XML string.'); } return self::parseDocument($doc); } /** * This function parses a DOMDocument which is assumed to contain a single EntityDescriptor element. * * @param $document The DOMDocument which contains the EntityDescriptor element. * @return An instance of this class with the metadata loaded. */ public static function parseDocument($document) { assert('$document instanceof DOMDocument'); $entityElement = self::findEntityDescriptor($document); return self::parseElement($entityElement); } /** * This function parses a DOMElement which represents a EntityDescriptor element. * * @param $entityElement A DOMElement which represents a EntityDescriptor element. * @return An instance of this class with the metadata loaded. */ public static function parseElement($entityElement) { assert('$entityElement instanceof DOMElement'); return new SimpleSAML_Metadata_SAMLParser($entityElement, NULL); } /** * This function parses a file where the root node is either an EntityDescriptor element or an * EntitiesDescriptor element. In both cases it will return an associative array of SAMLParser instances. If * the file contains a single EntityDescriptorElement, then the array will contain a single SAMLParser * instance. * * @param $file The path to the file which contains the EntityDescriptor or EntitiesDescriptor element. * @return An array of SAMLParser instances. */ public static function parseDescriptorsFile($file) { if ($file === NULL) throw new Exception('Cannot open file NULL. File name not specified.'); $doc = new DOMDocument(); $res = $doc->load($file); if($res !== TRUE) { throw new Exception('Failed to read XML from file: ' . $file); } if ($doc->documentElement === NULL) throw new Exception('Opened file is not an XML document: ' . $file); return self::parseDescriptorsElement($doc->documentElement); } /** * This function parses a string with XML data. The root node of the XML data is expected to be either an * EntityDescriptor element or an EntitiesDescriptor element. It will return an associative array of * SAMLParser instances. * * @param $string The string with XML data. * @return An associative array of SAMLParser instances. The key of the array will be the entity id. */ public static function parseDescriptorsString($string) { $doc = new DOMDocument(); $res = $doc->loadXML($string); if($res !== TRUE) { throw new Exception('Failed to parse XML string.'); } return self::parseDescriptorsElement($doc->documentElement); } /** * This function parses a DOMElement which represents either an EntityDescriptor element or an * EntitiesDescriptor element. It will return an associative array of SAMLParser instances in both cases. * * @param DOMElement|NULL $element The DOMElement which contains the EntityDescriptor element or the EntitiesDescriptor element. * @return An associative array of SAMLParser instances. The key of the array will be the entity id. */ public static function parseDescriptorsElement(DOMElement $element = NULL) { if($element === NULL) { throw new Exception('Document was empty.'); } assert('$element instanceof DOMElement'); if(SimpleSAML_Utilities::isDOMElementOfType($element, 'EntityDescriptor', '@md') === TRUE) { return self::processDescriptorsElement(new SAML2_XML_md_EntityDescriptor($element)); } elseif(SimpleSAML_Utilities::isDOMElementOfType($element, 'EntitiesDescriptor', '@md') === TRUE) { return self::processDescriptorsElement(new SAML2_XML_md_EntitiesDescriptor($element)); } else { throw new Exception('Unexpected root node: [' . $element->namespaceURI . ']:' . $element->localName); } return $ret; } /** * * @param SAML2_XML_md_EntityDescriptor|SAML2_XML_md_EntitiesDescriptor $element The element we should process. * @param int|NULL $maxExpireTime The maximum expiration time of the entitites. * @param array $validators The parent-elements that may be signed. * @return array Array of SAMLParser instances. */ private static function processDescriptorsElement($element, $maxExpireTime = NULL, array $validators = array()) { assert('is_null($maxExpireTime) || is_int($maxExpireTime)'); if ($element instanceof SAML2_XML_md_EntityDescriptor) { $ret = new SimpleSAML_Metadata_SAMLParser($element, $maxExpireTime, $validators); return array($ret->getEntityId() => $ret); } assert('$element instanceof SAML2_XML_md_EntitiesDescriptor'); $expTime = self::getExpireTime($element, $maxExpireTime); $validators[] = $element; $ret = array(); foreach ($element->children as $child) { $ret += self::processDescriptorsElement($child, $expTime, $validators); } return $ret; } /** * Determine how long a given element can be cached. * * This function looks for the 'cacheDuration' and 'validUntil' attributes to determine * how long a given XML-element is valid. It returns this as na unix timestamp. * * If both the 'cacheDuration' and 'validUntil' attributes are present, the shorter of them * will be returned. * * @param mixed $element The element we should determine the expiry time of. * @param int|NULL $maxExpireTime The maximum expiration time. * @return int The unix timestamp for when the element should expire. Will be NULL if no * limit is set for the element. */ private static function getExpireTime($element, $maxExpireTime) { if ($element->cacheDuration !== NULL) { $expire = SimpleSAML_Utilities::parseDuration($element->cacheDuration, time()); if ($maxExpireTime !== NULL && $maxExpireTime < $expire) { $expire = $maxExpireTime; } } else { $expire = $maxExpireTime; } if ($element->validUntil !== NULL) { if ($expire === NULL || $expire > $element->validUntil) { $expire = $element->validUntil; } } return $expire; } /** * This function returns the entity id of this parsed entity. * * @return The entity id of this parsed entity. */ public function getEntityId() { return $this->entityId; } private function getMetadataCommon() { $ret = array(); $ret['entityid'] = $this->entityId; $ret['entityDescriptor'] = $this->entityDescriptor; /* * Add organizational metadata */ if (!empty($this->organizationName)) { $ret['name'] = $this->organizationName; $ret['description'] = $this->organizationName; $ret['OrganizationName'] = $this->organizationName; } if (!empty($this->organizationDisplayName)) { $ret['name'] = $this->organizationDisplayName; $ret['OrganizationDisplayName'] = $this->organizationDisplayName; } if (!empty($this->organizationURL)) { $ret['url'] = $this->organizationURL; $ret['OrganizationURL'] = $this->organizationURL; } return $ret; } /** * Add data parsed from extensions to metadata. * * @param array &$metadata The metadata that should be updated. * @param array $roleDescriptor The parsed role desciptor. */ private function addExtensions(array &$metadata, array $roleDescriptor) { assert('array_key_exists("scopes", $roleDescriptor)'); assert('array_key_exists("tags", $roleDescriptor)'); $scopes = array_merge($this->scopes, array_diff($roleDescriptor['scopes'], $this->scopes)); if (!empty($scopes)) { $metadata['scopes'] = $scopes; } $tags = array_merge($this->tags, array_diff($roleDescriptor['tags'], $this->tags)); if (!empty($tags)) { $metadata['tags'] = $tags; } } /** * This function returns the metadata for SAML 1.x SPs in the format simpleSAMLphp expects. * This is an associative array with the following fields: * - 'entityid': The entity id of the entity described in the metadata. * - 'AssertionConsumerService': String with the url of the assertion consumer service which supports * the browser-post binding. * - 'certData': X509Certificate for entity (if present). * * Metadata must be loaded with one of the parse functions before this function can be called. * * @return Associative array with metadata or NULL if we are unable to generate metadata for a SAML 1.x SP. */ public function getMetadata1xSP() { $ret = $this->getMetadataCommon(); $ret['metadata-set'] = 'shib13-sp-remote'; /* Find SP information which supports one of the SAML 1.x protocols. */ $spd = $this->getSPDescriptors(self::$SAML1xProtocols); if(count($spd) === 0) { return NULL; } /* We currently only look at the first SPDescriptor which supports SAML 1.x. */ $spd = $spd[0]; /* Add expire time to metadata. */ if (array_key_exists('expire', $spd)) { $ret['expire'] = $spd['expire']; } /* Find the assertion consumer service endpoints. */ $ret['AssertionConsumerService'] = $spd['AssertionConsumerService']; /* Add the list of attributes the SP should receive. */ if (array_key_exists('attributes', $spd)) { $ret['attributes'] = $spd['attributes']; } if (array_key_exists('attributes.NameFormat', $spd)) { $ret['attributes.NameFormat'] = $spd['attributes.NameFormat']; } /* Add name & description. */ if (array_key_exists('name', $spd)) { $ret['name'] = $spd['name']; } if (array_key_exists('description', $spd)) { $ret['description'] = $spd['description']; } /* Add certificate data. Only the first valid certificate will be added. */ foreach($spd['keys'] as $key) { if($key['type'] !== 'X509Certificate') { continue; } $certData = base64_decode($key['X509Certificate']); if($certData === FALSE) { /* Empty/invalid certificate. */ continue; } $ret['certData'] = preg_replace('/\s+/', '', str_replace(array("\r", "\n"), '', $key['X509Certificate'])); break; } /* Add extensions. */ $this->addExtensions($ret, $spd); return $ret; } /** * This function returns the metadata for SAML 2.0 IdPs in the format simpleSAMLphp expects. * This is an associative array with the following fields: * - 'entityid': The entity id of the entity described in the metadata. * - 'name': Autogenerated name for this entity. Currently set to the entity id. * - 'SingleSignOnService': String with the url of the SSO service which supports the redirect binding. * - 'SingleLogoutService': String with the url where we should send logout requests/responses. * - 'certData': X509Certificate for entity (if present). * - 'certFingerprint': Fingerprint of the X509Certificate from the metadata. * * Metadata must be loaded with one of the parse functions before this function can be called. * * @return Associative array with metadata or NULL if we are unable to generate metadata for a SAML 1.x IdP. */ public function getMetadata1xIdP() { $ret = $this->getMetadataCommon(); $ret['metadata-set'] = 'shib13-idp-remote'; /* Find IdP information which supports the SAML 1.x protocol. */ $idp = $this->getIdPDescriptors(self::$SAML1xProtocols); if(count($idp) === 0) { return NULL; } /* We currently only look at the first IDP descriptor which supports SAML 1.x. */ $idp = $idp[0]; /* Add expire time to metadata. */ if (array_key_exists('expire', $idp)) { $ret['expire'] = $idp['expire']; } /* Find the SSO service endpoints. */ $ret['SingleSignOnService'] = $idp['SingleSignOnService']; /* Find the ArtifactResolutionService endpoint. */ $ret['ArtifactResolutionService'] = $idp['ArtifactResolutionService']; /* Add certificate to metadata. Only the first valid certificate will be added. */ $ret['certFingerprint'] = array(); foreach($idp['keys'] as $key) { if($key['type'] !== 'X509Certificate') { continue; } $certData = base64_decode($key['X509Certificate']); if($certData === FALSE) { /* Empty/invalid certificate. */ continue; } /* Add the certificate data to the metadata. Only the first certificate will be added. */ $ret['certData'] = preg_replace('/\s+/', '', str_replace(array("\r", "\n"), '', $key['X509Certificate'])); $ret['certFingerprint'][] = sha1($certData); break; } /* Add extensions. */ $this->addExtensions($ret, $idp); return $ret; } /** * This function returns the metadata for SAML 2.0 SPs in the format simpleSAMLphp expects. * This is an associative array with the following fields: * - 'entityid': The entity id of the entity described in the metadata. * - 'AssertionConsumerService': String with the url of the assertion consumer service which supports * the browser-post binding. * - 'SingleLogoutService': String with the url where we should send logout requests/responses. * - 'NameIDFormat': The name ID format this SP expects. This may be unset. * - 'certData': X509Certificate for entity (if present). * * Metadata must be loaded with one of the parse functions before this function can be called. * * @return Associative array with metadata or NULL if we are unable to generate metadata for a SAML 2.x SP. */ public function getMetadata20SP() { $ret = $this->getMetadataCommon(); $ret['metadata-set'] = 'saml20-sp-remote'; /* Find SP information which supports the SAML 2.0 protocol. */ $spd = $this->getSPDescriptors(self::$SAML20Protocols); if(count($spd) === 0) { return NULL; } /* We currently only look at the first SPDescriptor which supports SAML 2.0. */ $spd = $spd[0]; /* Add expire time to metadata. */ if (array_key_exists('expire', $spd)) { $ret['expire'] = $spd['expire']; } /* Find the assertion consumer service endpoints. */ $ret['AssertionConsumerService'] = $spd['AssertionConsumerService']; /* Find the single logout service endpoint. */ $ret['SingleLogoutService'] = $spd['SingleLogoutService']; /* Find the NameIDFormat. This may not exists. */ if(count($spd['nameIDFormats']) > 0) { /* simpleSAMLphp currently only supports a single NameIDFormat pr. SP. We use the first one. */ $ret['NameIDFormat'] = $spd['nameIDFormats'][0]; } /* Add the list of attributes the SP should receive. */ if (array_key_exists('attributes', $spd)) { $ret['attributes'] = $spd['attributes']; } if (array_key_exists('attributes.NameFormat', $spd)) { $ret['attributes.NameFormat'] = $spd['attributes.NameFormat']; } /* Add name & description. */ if (array_key_exists('name', $spd)) { $ret['name'] = $spd['name']; } if (array_key_exists('description', $spd)) { $ret['description'] = $spd['description']; } /* Add certificate data. Only the first valid certificate will be added. */ foreach($spd['keys'] as $key) { if($key['type'] !== 'X509Certificate') { continue; } $certData = base64_decode($key['X509Certificate']); if($certData === FALSE) { /* Empty/invalid certificate. */ continue; } $ret['certData'] = preg_replace('/\s+/', '', str_replace(array("\r", "\n"), '', $key['X509Certificate'])); break; } /* Add extensions. */ $this->addExtensions($ret, $spd); return $ret; } /** * This function returns the metadata for SAML 2.0 IdPs in the format simpleSAMLphp expects. * This is an associative array with the following fields: * - 'entityid': The entity id of the entity described in the metadata. * - 'name': Autogenerated name for this entity. Currently set to the entity id. * - 'SingleSignOnService': String with the url of the SSO service which supports the redirect binding. * - 'SingleLogoutService': String with the url where we should send logout requests(/responses). * - 'SingleLogoutServiceResponse': String where we should send logout responses (if this is different from * the 'SingleLogoutService' endpoint. * - 'certData': X509Certificate for entity (if present). * - 'certFingerprint': Fingerprint of the X509Certificate from the metadata. * * Metadata must be loaded with one of the parse functions before this function can be called. * * @return Associative array with metadata or NULL if we are unable to generate metadata for a SAML 2.0 IdP. */ public function getMetadata20IdP() { $ret = $this->getMetadataCommon(); $ret['metadata-set'] = 'saml20-idp-remote'; /* Find IdP information which supports the SAML 2.0 protocol. */ $idp = $this->getIdPDescriptors(self::$SAML20Protocols); if(count($idp) === 0) { return NULL; } /* We currently only look at the first IDP descriptor which supports SAML 2.0. */ $idp = $idp[0]; /* Add expire time to metadata. */ if (array_key_exists('expire', $idp)) { $ret['expire'] = $idp['expire']; } /* Enable redirect.sign if WantAuthnRequestsSigned is enabled. */ if ($idp['WantAuthnRequestsSigned']) { $ret['redirect.sign'] = TRUE; } /* Find the SSO service endpoint. */ $ret['SingleSignOnService'] = $idp['SingleSignOnService']; /* Find the single logout service endpoint. */ $ret['SingleLogoutService'] = $idp['SingleLogoutService']; /* Find the ArtifactResolutionService endpoint. */ $ret['ArtifactResolutionService'] = $idp['ArtifactResolutionService']; /* Add certificate to metadata. Only the first valid certificate will be added. */ $ret['certFingerprint'] = array(); foreach($idp['keys'] as $key) { if($key['type'] !== 'X509Certificate') { continue; } $certData = base64_decode($key['X509Certificate']); if($certData === FALSE) { /* Empty/invalid certificate. */ continue; } /* Add the certificate data to the metadata. Only the first certificate will be added. */ $ret['certData'] = preg_replace('/\s+/', '', str_replace(array("\r", "\n"), '', $key['X509Certificate'])); $ret['certFingerprint'][] = sha1($certData); break; } /* Add extensions. */ $this->addExtensions($ret, $idp); return $ret; } /** * Retrieve AttributeAuthorities from the metadata. * * @return array Array of AttributeAuthorityDescriptor entries. */ public function getAttributeAuthorities() { return $this->attributeAuthorityDescriptors; } /** * Parse a RoleDescriptorType element. * * The returned associative array has the following elements: * - 'protocols': Array with the protocols supported. * - 'expire': Timestamp for when this descriptor expires. * - 'keys': Array of associative arrays with the elements from parseKeyDescriptor. * * @param SAML2_XML_md_RoleDescriptor $element The element we should extract metadata from. * @param int|NULL $expireTime The unix timestamp for when this element should expire, or * NULL if unknwon. * @return Associative array with metadata we have extracted from this element. */ private static function parseRoleDescriptorType(SAML2_XML_md_RoleDescriptor $element, $expireTime) { assert('is_null($expireTime) || is_int($expireTime)'); $ret = array(); $expireTime = self::getExpireTime($element, $expireTime); if ($expireTime !== NULL) { /* We have got an expire timestamp, either from this element, or one of the * parent elements. */ $ret['expire'] = $expireTime; } $ret['protocols'] = $element->protocolSupportEnumeration; /* Process KeyDescriptor elements. */ $ret['keys'] = array(); foreach ($element->KeyDescriptor as $kd) { $key = self::parseKeyDescriptor($kd); if($key !== NULL) { $ret['keys'][] = $key; } } $ext = self::processExtensions($element); $ret['scopes'] = $ext['scopes']; $ret['tags'] = $ext['tags']; return $ret; } /** * This function extracts metadata from a SSODescriptor element. * * The returned associative array has the following elements: * - 'protocols': Array with the protocols this SSODescriptor supports. * - 'SingleLogoutService': Array with the single logout service endpoints. Each endpoint is stored * as an associative array with the elements that parseGenericEndpoint returns. * - 'nameIDFormats': The NameIDFormats supported by this SSODescriptor. This may be an empty array. * - 'keys': Array of associative arrays with the elements from parseKeyDescriptor: * * @param SAML2_XML_md_SSODescriptorType $element The element we should extract metadata from. * @param int|NULL $expireTime The unix timestamp for when this element should expire, or * NULL if unknwon. * @return Associative array with metadata we have extracted from this element. */ private static function parseSSODescriptor(SAML2_XML_md_SSODescriptorType $element, $expireTime) { assert('is_null($expireTime) || is_int($expireTime)'); $sd = self::parseRoleDescriptorType($element, $expireTime); /* Find all SingleLogoutService elements. */ $sd['SingleLogoutService'] = self::extractEndpoints($element->SingleLogoutService); /* Find all ArtifactResolutionService elements. */ $sd['ArtifactResolutionService'] = self::extractEndpoints($element->ArtifactResolutionService); /* Process NameIDFormat elements. */ $sd['nameIDFormats'] = $element->NameIDFormat; return $sd; } /** * This function extracts metadata from a SPSSODescriptor element. * * @param SAML2_XML_md_SPSSODescriptor $element The element which should be parsed. * @param int|NULL $expireTime The unix timestamp for when this element should expire, or * NULL if unknwon. */ private function processSPSSODescriptor(SAML2_XML_md_SPSSODescriptor $element, $expireTime) { assert('is_null($expireTime) || is_int($expireTime)'); $sp = self::parseSSODescriptor($element, $expireTime); /* Find all AssertionConsumerService elements. */ $sp['AssertionConsumerService'] = self::extractEndpoints($element->AssertionConsumerService); /* Find all the attributes and SP name... */ $attcs = $element->AttributeConsumingService; if (count($attcs) > 0) { self::parseAttributeConsumerService($attcs[0], $sp); } $this->spDescriptors[] = $sp; } /** * This function extracts metadata from a IDPSSODescriptor element. * * @param SAML2_XML_md_IDPSSODescriptor $element The element which should be parsed. * @param int|NULL $expireTime The unix timestamp for when this element should expire, or * NULL if unknwon. */ private function processIDPSSODescriptor(SAML2_XML_md_IDPSSODescriptor $element, $expireTime) { assert('is_null($expireTime) || is_int($expireTime)'); $idp = self::parseSSODescriptor($element, $expireTime); /* Find all SingleSignOnService elements. */ $idp['SingleSignOnService'] = self::extractEndpoints($element->SingleSignOnService); if ($element->WantAuthnRequestsSigned) { $idp['WantAuthnRequestsSigned'] = TRUE; } else { $idp['WantAuthnRequestsSigned'] = FALSE; } $this->idpDescriptors[] = $idp; } /** * This function extracts metadata from a AttributeAuthorityDescriptor element. * * @param SAML2_XML_md_AttributeAuthorityDescriptor $element The element which should be parsed. * @param int|NULL $expireTime The unix timestamp for when this element should expire, or * NULL if unknwon. */ private function processAttributeAuthorityDescriptor(SAML2_XML_md_AttributeAuthorityDescriptor $element, $expireTime) { assert('is_null($expireTime) || is_int($expireTime)'); $aad = self::parseRoleDescriptorType($element, $expireTime); $aad['entityid'] = $this->entityId; $aad['metadata-set'] = 'attributeauthority-remote'; $aad['AttributeService'] = self::extractEndpoints($element->AttributeService); $aad['AssertionIDRequestService'] = self::extractEndpoints($element->AssertionIDRequestService); $aad['NameIDFormat'] = $element->NameIDFormat; $this->attributeAuthorityDescriptors[] = $aad; } /** * Parse an Extensions element. * * @param mixed $element The element which contains the Extensions element. */ private static function processExtensions($element) { $ret = array( 'scopes' => array(), 'tags' => array(), ); foreach ($element->Extensions as $e) { if ($e instanceof SAML2_XML_shibmd_Scope) { $ret['scopes'][] = $e->scope; continue; } if (!($e instanceof SAML2_XML_Chunk)) { continue; } if ($e->localName === 'Attribute' && $e->namespaceURI === SAML2_Const::NS_SAML) { $attribute = $e->getXML(); $name = $attribute->getAttribute('Name'); $values = array_map( array('SimpleSAML_Utilities', 'getDOMText'), SimpleSAML_Utilities::getDOMChildren($attribute, 'AttributeValue', '@saml2') ); if ($name === 'tags') { foreach ($values as $tagname) { if (!empty($tagname)) { $ret['tags'][] = $tagname; } } } } } return $ret; } /** * Parse and process a Organization element. * * @param SAML2_XML_md_Organization $element The Organization element. */ private function processOrganization(SAML2_XML_md_Organization $element) { $this->organizationName = $element->OrganizationName; $this->organizationDisplayName = $element->OrganizationDisplayName; $this->organizationURL = $element->OrganizationURL; } /** * This function parses AttributeConsumerService elements. */ private static function parseAttributeConsumerService(SAML2_XML_md_AttributeConsumingService $element, &$sp) { assert('is_array($sp)'); $sp['name'] = $element->ServiceName; $sp['description'] = $element->ServiceDescription; $format = NULL; $sp['attributes'] = array(); foreach ($element->RequestedAttribute AS $child) { $attrname = $child->Name; $sp['attributes'][] = $attrname; if ($child->NameFormat !== NULL) { $attrformat = $child->NameFormat; } else { $attrformat = SAML2_Const::NAMEFORMAT_UNSPECIFIED; } if ($format === NULL) { $format = $attrformat; } elseif ($format !== $attrformat) { $format = SAML2_Const::NAMEFORMAT_UNSPECIFIED; } } if (empty($sp['attributes'])) { /* * Really an invalid configuration - all AttributeConsumingServices * should have one or more attributes. */ unset($sp['attributes']); } if ($format !== SAML2_Const::NAMEFORMAT_UNSPECIFIED && $format !== NULL) { $sp['attributes.NameFormat'] = $format; } } /** * This function is a generic endpoint element parser. * * The returned associative array has the following elements: * - 'Binding': The binding this endpoint uses. * - 'Location': The URL to this endpoint. * - 'ResponseLocation': The URL where responses should be sent. This may not exist. * - 'index': The index of this endpoint. This attribute is only for indexed endpoints. * - 'isDefault': Whether this endpoint is the default endpoint for this type. This attribute may not exist. * * @param $element The element which should be parsed. * @return Associative array with the data we have extracted from the element. */ private static function parseGenericEndpoint(SAML2_XML_md_EndpointType $element) { $ep = array(); $ep['Binding'] = $element->Binding; $ep['Location'] = $element->Location; if ($element->ResponseLocation !== NULL) { $ep['ResponseLocation'] = $element->ResponseLocation; } if ($element instanceof SAML2_XML_md_IndexedEndpointType) { $ep['index'] = $element->index; if ($element->isDefault !== NULL) { $ep['isDefault'] = $element->isDefault; } } return $ep; } /** * Extract generic endpoints. * * @param array $endpoints The endpoints we should parse. * @return array Array of parsed endpoints. */ private static function extractEndpoints(array $endpoints) { $ret = array(); foreach ($endpoints as $ep) { $ret[] = self::parseGenericEndpoint($ep); } return $ret; } /** * This function parses a KeyDescriptor element. It currently only supports keys with a single * X509 certificate. * * The associative array for a key can contain: * - 'encryption': Indicates wheter this key can be used for encryption. * - 'signing': Indicates wheter this key can be used for signing. * - 'type: The type of the key. 'X509Certificate' is the only key type we support. * - 'X509Certificate': The contents of the first X509Certificate element (if the type is 'X509Certificate '). * * @param SAML2_XML_md_KeyDescriptor $kd The KeyDescriptor element. * @return Associative array describing the key, or NULL if this is an unsupported key. */ private static function parseKeyDescriptor(SAML2_XML_md_KeyDescriptor $kd) { $r = array(); if ($kd->use === 'encryption') { $r['encryption'] = TRUE; $r['signing'] = FALSE; } elseif($kd->use === 'signing') { $r['encryption'] = FALSE; $r['signing'] = TRUE; } else { $r['encryption'] = TRUE; $r['signing'] = TRUE; } $keyInfo = $kd->KeyInfo; foreach ($keyInfo->info as $i) { if ($i instanceof SAML2_XML_ds_X509Data) { foreach ($i->data as $d) { if ($d instanceof SAML2_XML_ds_X509Certificate) { $r['type'] = 'X509Certificate'; $r['X509Certificate'] = $d->certificate; return $r; } } } } return NULL; } /** * This function finds SP descriptors which supports one of the given protocols. * * @param $protocols Array with the protocols we accept. * @return Array with SP descriptors which supports one of the given protocols. */ private function getSPDescriptors($protocols) { assert('is_array($protocols)'); $ret = array(); foreach($this->spDescriptors as $spd) { $sharedProtocols = array_intersect($protocols, $spd['protocols']); if(count($sharedProtocols) > 0) { $ret[] = $spd; } } return $ret; } /** * This function finds IdP descriptors which supports one of the given protocols. * * @param $protocols Array with the protocols we accept. * @return Array with IdP descriptors which supports one of the given protocols. */ private function getIdPDescriptors($protocols) { assert('is_array($protocols)'); $ret = array(); foreach($this->idpDescriptors as $idpd) { $sharedProtocols = array_intersect($protocols, $idpd['protocols']); if(count($sharedProtocols) > 0) { $ret[] = $idpd; } } return $ret; } /** * This function locates the EntityDescriptor node in a DOMDocument. This node should * be the first (and only) node in the document. * * This function will throw an exception if it is unable to locate the node. * * @param $doc The DOMDocument where we should find the EntityDescriptor node. * @return The DOMEntity which represents the EntityDescriptor. */ private static function findEntityDescriptor($doc) { assert('$doc instanceof DOMDocument'); /* Find the EntityDescriptor DOMElement. This should be the first (and only) child of the * DOMDocument. */ $ed = $doc->documentElement; if($ed === NULL) { throw new Exception('Failed to load SAML metadata from empty XML document.'); } if(SimpleSAML_Utilities::isDOMElementOfType($ed, 'EntityDescriptor', '@md') === FALSE) { throw new Exception('Expected first element in the metadata document to be an EntityDescriptor element.'); } return new SAML2_XML_md_EntityDescriptor($ed); } /** * This function checks if this EntityDescriptor was signed with a certificate with the * given fingerprint. * * @param $fingerprint Fingerprint of the certificate which should have been used to sign this * EntityDescriptor. * @return TRUE if it was signed with the certificate with the given fingerprint, FALSE otherwise. */ public function validateFingerprint($fingerprint) { assert('is_string($fingerprint)'); $fingerprint = strtolower(str_replace(":", "", $fingerprint)); foreach ($this->validators as $validator) { foreach ($validator->getValidatingCertificates() as $cert) { $fp = strtolower(sha1(base64_decode($cert))); if ($fp === $fingerprint) { return TRUE; } } } return FALSE; } }