[saml] Update Composer dependency ahead of release
[friendica-addons.git/.git] / saml / vendor / onelogin / php-saml / src / Saml2 / IdPMetadataParser.php
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
16 namespace OneLogin\Saml2;
17
18 use DOMDocument;
19 use Exception;
20
21 /**
22  * IdP Metadata Parser of OneLogin PHP Toolkit
23  */
24 class IdPMetadataParser
25 {
26     /**
27      * Get IdP Metadata Info from URL
28      *
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.
32      *
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
41      *
42      * @return array metadata info in php-saml settings format
43      */
44     public static function parseRemoteXML($url, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = Constants::BINDING_HTTP_REDIRECT)
45     {
46         $metadataInfo = array();
47
48         try {
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);
58
59             $xml = curl_exec($ch);
60             if ($xml !== false) {
61                 $metadataInfo = self::parseXML($xml, $entityId, $desiredNameIdFormat, $desiredSSOBinding, $desiredSLOBinding);
62             } else {
63                 throw new Exception(curl_error($ch), curl_errno($ch));
64             }
65         } catch (Exception $e) {
66             throw new Exception('Error on parseRemoteXML. '.$e->getMessage());
67         }
68         return $metadataInfo;
69     }
70
71     /**
72      * Get IdP Metadata Info from File
73      *
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
82      *
83      * @return array metadata info in php-saml settings format
84      */
85     public static function parseFileXML($filepath, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = Constants::BINDING_HTTP_REDIRECT)
86     {
87         $metadataInfo = array();
88
89         try {
90             if (file_exists($filepath)) {
91                 $data = file_get_contents($filepath);
92                 $metadataInfo = self::parseXML($data, $entityId, $desiredNameIdFormat, $desiredSSOBinding, $desiredSLOBinding);
93             }
94         } catch (Exception $e) {
95             throw new Exception('Error on parseFileXML. '.$e->getMessage());
96         }
97         return $metadataInfo;
98     }
99
100     /**
101      * Get IdP Metadata Info from URL
102      *
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
111      *
112      * @return array metadata info in php-saml settings format
113      *
114      * @throws Exception
115      */
116     public static function parseXML($xml, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = Constants::BINDING_HTTP_REDIRECT)
117     {
118         $metadataInfo = array();
119
120         $dom = new DOMDocument();
121         $dom->preserveWhiteSpace = false;
122         $dom->formatOutput = true;
123         try {
124             $dom = Utils::loadXML($dom, $xml);
125             if (!$dom) {
126                 throw new Exception('Error parsing metadata');
127             }
128
129             $customIdPStr = '';
130             if (!empty($entityId)) {
131                 $customIdPStr = '[@entityID="' . $entityId . '"]';
132             }
133             $idpDescryptorXPath = '//md:EntityDescriptor' . $customIdPStr . '/md:IDPSSODescriptor';
134
135             $idpDescriptorNodes = Utils::query($dom, $idpDescryptorXPath);
136
137             if (isset($idpDescriptorNodes) && $idpDescriptorNodes->length > 0) {
138                 $metadataInfo['idp'] = array();
139
140                 $idpDescriptor = $idpDescriptorNodes->item(0);
141
142                 if (empty($entityId) && $idpDescriptor->parentNode->hasAttribute('entityID')) {
143                     $entityId = $idpDescriptor->parentNode->getAttribute('entityID');
144                 }
145
146                 if (!empty($entityId)) {
147                     $metadataInfo['idp']['entityId'] = $entityId;
148                 }
149
150                 $ssoNodes = Utils::query($dom, './md:SingleSignOnService[@Binding="'.$desiredSSOBinding.'"]', $idpDescriptor);
151                 if ($ssoNodes->length < 1) {
152                     $ssoNodes = Utils::query($dom, './md:SingleSignOnService', $idpDescriptor);
153                 }
154                 if ($ssoNodes->length > 0) {
155                     $metadataInfo['idp']['singleSignOnService'] = array(
156                         'url' => $ssoNodes->item(0)->getAttribute('Location'),
157                         'binding' => $ssoNodes->item(0)->getAttribute('Binding')
158                     );
159                 }
160
161                 $sloNodes = Utils::query($dom, './md:SingleLogoutService[@Binding="'.$desiredSLOBinding.'"]', $idpDescriptor);
162                 if ($sloNodes->length < 1) {
163                     $sloNodes = Utils::query($dom, './md:SingleLogoutService', $idpDescriptor);
164                 }
165                 if ($sloNodes->length > 0) {
166                     $metadataInfo['idp']['singleLogoutService'] = array(
167                         'url' => $sloNodes->item(0)->getAttribute('Location'),
168                         'binding' => $sloNodes->item(0)->getAttribute('Binding')
169                     );
170
171                     if ($sloNodes->item(0)->hasAttribute('ResponseLocation')) {
172                         $metadataInfo['idp']['singleLogoutService']['responseUrl'] = $sloNodes->item(0)->getAttribute('ResponseLocation');
173                     }
174                 }
175
176                 $keyDescriptorCertSigningNodes = Utils::query($dom, './md:KeyDescriptor[not(contains(@use, "encryption"))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate', $idpDescriptor);
177
178                 $keyDescriptorCertEncryptionNodes = Utils::query($dom, './md:KeyDescriptor[not(contains(@use, "signing"))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate', $idpDescriptor);
179
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);
186                         }
187                     }
188                     if (!empty($keyDescriptorCertEncryptionNodes)) {
189                         $idpInfo['x509certMulti']['encryption'] = array();
190                         foreach ($keyDescriptorCertEncryptionNodes as $keyDescriptorCertEncryptionNode) {
191                             $metadataInfo['idp']['x509certMulti']['encryption'][] = Utils::formatCert($keyDescriptorCertEncryptionNode->nodeValue, false);
192                         }
193                     }
194
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];
201                         } else {
202                             $metadataInfo['idp']['x509cert'] = $metadataInfo['idp']['x509certMulti']['encryption'][0];
203                         }
204                         unset($metadataInfo['idp']['x509certMulti']);
205                     }
206                 }
207
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;
215                                 break;
216                             }
217                         }
218                     }
219                 }
220             }
221         } catch (Exception $e) {
222             throw new Exception('Error parsing metadata. '.$e->getMessage());
223         }
224
225         return $metadataInfo;
226     }
227
228     /**
229      * Inject metadata info into php-saml settings array
230      *
231      * @param array $settings     php-saml settings array
232      * @param array $metadataInfo array metadata info
233      *
234      * @return array settings
235      */
236     public static function injectIntoSettings($settings, $metadataInfo)
237     {
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']);
241             }
242
243             if (isset($metadataInfo['idp']['x509cert']) && !empty($metadataInfo['idp']['x509cert']) && isset($settings['idp']['x509certMulti'])) {
244                 unset($settings['idp']['x509certMulti']);
245             }
246         }
247
248         return array_replace_recursive($settings, $metadataInfo);
249     }
250 }