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