1<?php 2 3/** 4 * IdP Metadata Parser of OneLogin PHP Toolkit 5 * 6 */ 7 8class OneLogin_Saml2_IdPMetadataParser 9{ 10 /** 11 * Get IdP Metadata Info from URL 12 * 13 * @param string $url URL where the IdP metadata is published 14 * @param string $entityId Entity Id of the desired IdP, if no 15 * entity Id is provided and the XML 16 * metadata contains more than one 17 * IDPSSODescriptor, the first is returned 18 * @param string $desiredNameIdFormat If available on IdP metadata, use that nameIdFormat 19 * @param string $desiredSSOBinding Parse specific binding SSO endpoint. 20 * @param string $desiredSLOBinding Parse specific binding SLO endpoint. 21 * 22 * @return array metadata info in php-saml settings format 23 */ 24 public static function parseRemoteXML($url, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT) 25 { 26 $metadataInfo = array(); 27 28 try { 29 $ch = curl_init($url); 30 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET"); 31 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 32 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); 33 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); 34 curl_setopt($ch, CURLOPT_FAILONERROR, 1); 35 36 $xml = curl_exec($ch); 37 if ($xml !== false) { 38 $metadataInfo = self::parseXML($xml, $entityId, $desiredNameIdFormat, $desiredSSOBinding, $desiredSLOBinding); 39 } else { 40 throw new Exception(curl_error($ch), curl_errno($ch)); 41 } 42 } catch (Exception $e) { 43 throw new Exception('Error on parseRemoteXML. '.$e->getMessage()); 44 } 45 return $metadataInfo; 46 } 47 48 /** 49 * Get IdP Metadata Info from File 50 * 51 * @param string $filepath File path 52 * @param string $entityId Entity Id of the desired IdP, if no 53 * entity Id is provided and the XML 54 * metadata contains more than one 55 * IDPSSODescriptor, the first is returned 56 * @param string $desiredNameIdFormat If available on IdP metadata, use that nameIdFormat 57 * @param string $desiredSSOBinding Parse specific binding SSO endpoint. 58 * @param string $desiredSLOBinding Parse specific binding SLO endpoint. 59 * 60 * @return array metadata info in php-saml settings format 61 */ 62 public static function parseFileXML($filepath, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT) 63 { 64 $metadataInfo = array(); 65 66 try { 67 if (file_exists($filepath)) { 68 $data = file_get_contents($filepath); 69 $metadataInfo = self::parseXML($data, $entityId, $desiredNameIdFormat, $desiredSSOBinding, $desiredSLOBinding); 70 } 71 } catch (Exception $e) { 72 throw new Exception('Error on parseFileXML. '.$e->getMessage()); 73 } 74 return $metadataInfo; 75 } 76 77 /** 78 * Get IdP Metadata Info from URL 79 * 80 * @param string $xml XML that contains IdP metadata 81 * @param string $entityId Entity Id of the desired IdP, if no 82 * entity Id is provided and the XML 83 * metadata contains more than one 84 * IDPSSODescriptor, the first is returned 85 * @param string $desiredNameIdFormat If available on IdP metadata, use that nameIdFormat 86 * @param string $desiredSSOBinding Parse specific binding SSO endpoint. 87 * @param string $desiredSLOBinding Parse specific binding SLO endpoint. 88 * 89 * @return array metadata info in php-saml settings format 90 * 91 * @throws Exception 92 */ 93 public static function parseXML($xml, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT) 94 { 95 $metadataInfo = array(); 96 97 $dom = new DOMDocument(); 98 $dom->preserveWhiteSpace = false; 99 $dom->formatOutput = true; 100 try { 101 $dom = OneLogin_Saml2_Utils::loadXML($dom, $xml); 102 if (!$dom) { 103 throw new Exception('Error parsing metadata'); 104 } 105 106 $customIdPStr = ''; 107 if (!empty($entityId)) { 108 $customIdPStr = '[@entityID="' . $entityId . '"]'; 109 } 110 $idpDescryptorXPath = '//md:EntityDescriptor' . $customIdPStr . '/md:IDPSSODescriptor'; 111 112 $idpDescriptorNodes = OneLogin_Saml2_Utils::query($dom, $idpDescryptorXPath); 113 114 if (isset($idpDescriptorNodes) && $idpDescriptorNodes->length > 0) { 115 $metadataInfo['idp'] = array(); 116 117 $idpDescriptor = $idpDescriptorNodes->item(0); 118 119 if (empty($entityId) && $idpDescriptor->parentNode->hasAttribute('entityID')) { 120 $entityId = $idpDescriptor->parentNode->getAttribute('entityID'); 121 } 122 123 if (!empty($entityId)) { 124 $metadataInfo['idp']['entityId'] = $entityId; 125 } 126 127 $ssoNodes = OneLogin_Saml2_Utils::query($dom, './md:SingleSignOnService[@Binding="'.$desiredSSOBinding.'"]', $idpDescriptor); 128 if ($ssoNodes->length < 1) { 129 $ssoNodes = OneLogin_Saml2_Utils::query($dom, './md:SingleSignOnService', $idpDescriptor); 130 } 131 if ($ssoNodes->length > 0) { 132 $metadataInfo['idp']['singleSignOnService'] = array( 133 'url' => $ssoNodes->item(0)->getAttribute('Location'), 134 'binding' => $ssoNodes->item(0)->getAttribute('Binding') 135 ); 136 } 137 138 $sloNodes = OneLogin_Saml2_Utils::query($dom, './md:SingleLogoutService[@Binding="'.$desiredSLOBinding.'"]', $idpDescriptor); 139 if ($sloNodes->length < 1) { 140 $sloNodes = OneLogin_Saml2_Utils::query($dom, './md:SingleLogoutService', $idpDescriptor); 141 } 142 if ($sloNodes->length > 0) { 143 $metadataInfo['idp']['singleLogoutService'] = array( 144 'url' => $sloNodes->item(0)->getAttribute('Location'), 145 'binding' => $sloNodes->item(0)->getAttribute('Binding') 146 ); 147 148 if ($sloNodes->item(0)->hasAttribute('ResponseLocation')) { 149 $metadataInfo['idp']['singleLogoutService']['responseUrl'] = $sloNodes->item(0)->getAttribute('ResponseLocation'); 150 } 151 } 152 153 $keyDescriptorCertSigningNodes = OneLogin_Saml2_Utils::query($dom, './md:KeyDescriptor[not(contains(@use, "encryption"))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate', $idpDescriptor); 154 155 $keyDescriptorCertEncryptionNodes = OneLogin_Saml2_Utils::query($dom, './md:KeyDescriptor[not(contains(@use, "signing"))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate', $idpDescriptor); 156 157 if (!empty($keyDescriptorCertSigningNodes) || !empty($keyDescriptorCertEncryptionNodes)) { 158 $metadataInfo['idp']['x509certMulti'] = array(); 159 if (!empty($keyDescriptorCertSigningNodes)) { 160 foreach ($keyDescriptorCertSigningNodes as $keyDescriptorCertSigningNode) { 161 $metadataInfo['idp']['x509certMulti']['signing'][] = OneLogin_Saml2_Utils::formatCert($keyDescriptorCertSigningNode->nodeValue, false); 162 } 163 } 164 if (!empty($keyDescriptorCertEncryptionNodes)) { 165 foreach ($keyDescriptorCertEncryptionNodes as $keyDescriptorCertEncryptionNode) { 166 $metadataInfo['idp']['x509certMulti']['encryption'][] = OneLogin_Saml2_Utils::formatCert($keyDescriptorCertEncryptionNode->nodeValue, false); 167 } 168 } 169 170 $idpCertdata = $metadataInfo['idp']['x509certMulti']; 171 if ((count($idpCertdata) == 1 and 172 ((isset($idpCertdata['signing']) and count($idpCertdata['signing']) == 1) or (isset($idpCertdata['encryption']) and count($idpCertdata['encryption']) == 1))) or 173 ((isset($idpCertdata['signing']) && count($idpCertdata['signing']) == 1) && isset($idpCertdata['encryption']) && count($idpCertdata['encryption']) == 1 && strcmp($idpCertdata['signing'][0], $idpCertdata['encryption'][0]) == 0)) { 174 if (isset($metadataInfo['idp']['x509certMulti']['signing'][0])) { 175 $metadataInfo['idp']['x509cert'] = $metadataInfo['idp']['x509certMulti']['signing'][0]; 176 } else { 177 $metadataInfo['idp']['x509cert'] = $metadataInfo['idp']['x509certMulti']['encryption'][0]; 178 } 179 unset($metadataInfo['idp']['x509certMulti']); 180 } 181 } 182 183 $nameIdFormatNodes = OneLogin_Saml2_Utils::query($dom, './md:NameIDFormat', $idpDescriptor); 184 if ($nameIdFormatNodes->length > 0) { 185 $metadataInfo['sp']['NameIDFormat'] = $nameIdFormatNodes->item(0)->nodeValue; 186 if (!empty($desiredNameIdFormat)) { 187 foreach ($nameIdFormatNodes as $nameIdFormatNode) { 188 if (strcmp($nameIdFormatNode->nodeValue, $desiredNameIdFormat) == 0) { 189 $metadataInfo['sp']['NameIDFormat'] = $nameIdFormatNode->nodeValue; 190 break; 191 } 192 } 193 } 194 } 195 } 196 } catch (Exception $e) { 197 throw new Exception('Error parsing metadata. '.$e->getMessage()); 198 } 199 200 return $metadataInfo; 201 } 202 203 /** 204 * Inject metadata info into php-saml settings array 205 * 206 * @param array $settings php-saml settings array 207 * @param array $metadataInfo array metadata info 208 * 209 * @return array settings 210 */ 211 public static function injectIntoSettings($settings, $metadataInfo) 212 { 213 if (isset($metadataInfo['idp']) && isset($settings['idp'])) { 214 if (isset($metadataInfo['idp']['x509certMulti']) && !empty($metadataInfo['idp']['x509certMulti']) && isset($settings['idp']['x509cert'])) { 215 unset($settings['idp']['x509cert']); 216 } 217 218 if (isset($metadataInfo['idp']['x509cert']) && !empty($metadataInfo['idp']['x509cert']) && isset($settings['idp']['x509certMulti'])) { 219 unset($settings['idp']['x509certMulti']); 220 } 221 } 222 223 return array_replace_recursive($settings, $metadataInfo); 224 } 225} 226