3 * This file is part of php-saml.
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
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
16 namespace OneLogin\Saml2;
22 * IdP Metadata Parser of OneLogin PHP Toolkit
24 class IdPMetadataParser
27 * Get IdP Metadata Info from URL
29 * This class does not validate in any way the URL that is introduced,
30 * make sure to validate it properly before use it in the parseRemoteXML
31 * method in order to avoid security issues like SSRF attacks.
33 * @param string $url URL where the IdP metadata is published
34 * @param string $entityId Entity Id of the desired IdP, if no
35 * entity Id is provided and the XML
36 * metadata contains more than one
37 * IDPSSODescriptor, the first is returned
38 * @param string $desiredNameIdFormat If available on IdP metadata, use that nameIdFormat
39 * @param string $desiredSSOBinding Parse specific binding SSO endpoint
40 * @param string $desiredSLOBinding Parse specific binding SLO endpoint
42 * @return array metadata info in php-saml settings format
44 public static function parseRemoteXML($url, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = Constants::BINDING_HTTP_REDIRECT)
46 $metadataInfo = array();
49 $ch = curl_init($url);
50 curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
51 curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
52 curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
53 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
54 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
55 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
56 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
57 curl_setopt($ch, CURLOPT_FAILONERROR, 1);
59 $xml = curl_exec($ch);
61 $metadataInfo = self::parseXML($xml, $entityId, $desiredNameIdFormat, $desiredSSOBinding, $desiredSLOBinding);
63 throw new Exception(curl_error($ch), curl_errno($ch));
65 } catch (Exception $e) {
66 throw new Exception('Error on parseRemoteXML. '.$e->getMessage());
72 * Get IdP Metadata Info from File
74 * @param string $filepath File path
75 * @param string $entityId Entity Id of the desired IdP, if no
76 * entity Id is provided and the XML
77 * metadata contains more than one
78 * IDPSSODescriptor, the first is returned
79 * @param string $desiredNameIdFormat If available on IdP metadata, use that nameIdFormat
80 * @param string $desiredSSOBinding Parse specific binding SSO endpoint
81 * @param string $desiredSLOBinding Parse specific binding SLO endpoint
83 * @return array metadata info in php-saml settings format
85 public static function parseFileXML($filepath, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = Constants::BINDING_HTTP_REDIRECT)
87 $metadataInfo = array();
90 if (file_exists($filepath)) {
91 $data = file_get_contents($filepath);
92 $metadataInfo = self::parseXML($data, $entityId, $desiredNameIdFormat, $desiredSSOBinding, $desiredSLOBinding);
94 } catch (Exception $e) {
95 throw new Exception('Error on parseFileXML. '.$e->getMessage());
101 * Get IdP Metadata Info from URL
103 * @param string $xml XML that contains IdP metadata
104 * @param string $entityId Entity Id of the desired IdP, if no
105 * entity Id is provided and the XML
106 * metadata contains more than one
107 * IDPSSODescriptor, the first is returned
108 * @param string $desiredNameIdFormat If available on IdP metadata, use that nameIdFormat
109 * @param string $desiredSSOBinding Parse specific binding SSO endpoint
110 * @param string $desiredSLOBinding Parse specific binding SLO endpoint
112 * @return array metadata info in php-saml settings format
116 public static function parseXML($xml, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = Constants::BINDING_HTTP_REDIRECT)
118 $metadataInfo = array();
120 $dom = new DOMDocument();
121 $dom->preserveWhiteSpace = false;
122 $dom->formatOutput = true;
124 $dom = Utils::loadXML($dom, $xml);
126 throw new Exception('Error parsing metadata');
130 if (!empty($entityId)) {
131 $customIdPStr = '[@entityID="' . $entityId . '"]';
133 $idpDescryptorXPath = '//md:EntityDescriptor' . $customIdPStr . '/md:IDPSSODescriptor';
135 $idpDescriptorNodes = Utils::query($dom, $idpDescryptorXPath);
137 if (isset($idpDescriptorNodes) && $idpDescriptorNodes->length > 0) {
138 $metadataInfo['idp'] = array();
140 $idpDescriptor = $idpDescriptorNodes->item(0);
142 if (empty($entityId) && $idpDescriptor->parentNode->hasAttribute('entityID')) {
143 $entityId = $idpDescriptor->parentNode->getAttribute('entityID');
146 if (!empty($entityId)) {
147 $metadataInfo['idp']['entityId'] = $entityId;
150 $ssoNodes = Utils::query($dom, './md:SingleSignOnService[@Binding="'.$desiredSSOBinding.'"]', $idpDescriptor);
151 if ($ssoNodes->length < 1) {
152 $ssoNodes = Utils::query($dom, './md:SingleSignOnService', $idpDescriptor);
154 if ($ssoNodes->length > 0) {
155 $metadataInfo['idp']['singleSignOnService'] = array(
156 'url' => $ssoNodes->item(0)->getAttribute('Location'),
157 'binding' => $ssoNodes->item(0)->getAttribute('Binding')
161 $sloNodes = Utils::query($dom, './md:SingleLogoutService[@Binding="'.$desiredSLOBinding.'"]', $idpDescriptor);
162 if ($sloNodes->length < 1) {
163 $sloNodes = Utils::query($dom, './md:SingleLogoutService', $idpDescriptor);
165 if ($sloNodes->length > 0) {
166 $metadataInfo['idp']['singleLogoutService'] = array(
167 'url' => $sloNodes->item(0)->getAttribute('Location'),
168 'binding' => $sloNodes->item(0)->getAttribute('Binding')
171 if ($sloNodes->item(0)->hasAttribute('ResponseLocation')) {
172 $metadataInfo['idp']['singleLogoutService']['responseUrl'] = $sloNodes->item(0)->getAttribute('ResponseLocation');
176 $keyDescriptorCertSigningNodes = Utils::query($dom, './md:KeyDescriptor[not(contains(@use, "encryption"))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate', $idpDescriptor);
178 $keyDescriptorCertEncryptionNodes = Utils::query($dom, './md:KeyDescriptor[not(contains(@use, "signing"))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate', $idpDescriptor);
180 if (!empty($keyDescriptorCertSigningNodes) || !empty($keyDescriptorCertEncryptionNodes)) {
181 $metadataInfo['idp']['x509certMulti'] = array();
182 if (!empty($keyDescriptorCertSigningNodes)) {
183 $idpInfo['x509certMulti']['signing'] = array();
184 foreach ($keyDescriptorCertSigningNodes as $keyDescriptorCertSigningNode) {
185 $metadataInfo['idp']['x509certMulti']['signing'][] = Utils::formatCert($keyDescriptorCertSigningNode->nodeValue, false);
188 if (!empty($keyDescriptorCertEncryptionNodes)) {
189 $idpInfo['x509certMulti']['encryption'] = array();
190 foreach ($keyDescriptorCertEncryptionNodes as $keyDescriptorCertEncryptionNode) {
191 $metadataInfo['idp']['x509certMulti']['encryption'][] = Utils::formatCert($keyDescriptorCertEncryptionNode->nodeValue, false);
195 $idpCertdata = $metadataInfo['idp']['x509certMulti'];
196 if ((count($idpCertdata) == 1 and
197 ((isset($idpCertdata['signing']) and count($idpCertdata['signing']) == 1) or (isset($idpCertdata['encryption']) and count($idpCertdata['encryption']) == 1))) or
198 ((isset($idpCertdata['signing']) && count($idpCertdata['signing']) == 1) && isset($idpCertdata['encryption']) && count($idpCertdata['encryption']) == 1 && strcmp($idpCertdata['signing'][0], $idpCertdata['encryption'][0]) == 0)) {
199 if (isset($metadataInfo['idp']['x509certMulti']['signing'][0])) {
200 $metadataInfo['idp']['x509cert'] = $metadataInfo['idp']['x509certMulti']['signing'][0];
202 $metadataInfo['idp']['x509cert'] = $metadataInfo['idp']['x509certMulti']['encryption'][0];
204 unset($metadataInfo['idp']['x509certMulti']);
208 $nameIdFormatNodes = Utils::query($dom, './md:NameIDFormat', $idpDescriptor);
209 if ($nameIdFormatNodes->length > 0) {
210 $metadataInfo['sp']['NameIDFormat'] = $nameIdFormatNodes->item(0)->nodeValue;
211 if (!empty($desiredNameIdFormat)) {
212 foreach ($nameIdFormatNodes as $nameIdFormatNode) {
213 if (strcmp($nameIdFormatNode->nodeValue, $desiredNameIdFormat) == 0) {
214 $metadataInfo['sp']['NameIDFormat'] = $nameIdFormatNode->nodeValue;
221 } catch (Exception $e) {
222 throw new Exception('Error parsing metadata. '.$e->getMessage());
225 return $metadataInfo;
229 * Inject metadata info into php-saml settings array
231 * @param array $settings php-saml settings array
232 * @param array $metadataInfo array metadata info
234 * @return array settings
236 public static function injectIntoSettings($settings, $metadataInfo)
238 if (isset($metadataInfo['idp']) && isset($settings['idp'])) {
239 if (isset($metadataInfo['idp']['x509certMulti']) && !empty($metadataInfo['idp']['x509certMulti']) && isset($settings['idp']['x509cert'])) {
240 unset($settings['idp']['x509cert']);
243 if (isset($metadataInfo['idp']['x509cert']) && !empty($metadataInfo['idp']['x509cert']) && isset($settings['idp']['x509certMulti'])) {
244 unset($settings['idp']['x509certMulti']);
248 return array_replace_recursive($settings, $metadataInfo);