1<?php
2
3/**
4 * Configuration of the OneLogin PHP Toolkit
5 *
6 */
7
8class OneLogin_Saml2_Settings
9{
10    /**
11     * List of paths.
12     *
13     * @var array
14     */
15    private $_paths = array();
16
17    /**
18     * @var string
19     */
20    private $_baseurl;
21
22    /**
23     * Strict. If active, PHP Toolkit will reject unsigned or unencrypted messages
24     * if it expects them signed or encrypted. If not, the messages will be accepted
25     * and some security issues will be also relaxed.
26     *
27     * @var bool
28     */
29    private $_strict = true;
30
31    /**
32     * Activate debug mode
33     *
34     * @var bool
35     */
36    private $_debug = false;
37
38    /**
39     * SP data.
40     *
41     * @var array
42     */
43    private $_sp = array();
44
45    /**
46     * IdP data.
47     *
48     * @var array
49     */
50    private $_idp = array();
51
52    /**
53     * Compression settings that determine
54     * whether gzip compression should be used.
55     *
56     * @var array
57     */
58    private $_compress = array();
59
60    /**
61     * Security Info related to the SP.
62     *
63     * @var array
64     */
65    private $_security = array();
66
67    /**
68     * Setting contacts.
69     *
70     * @var array
71     */
72    private $_contacts = array();
73
74    /**
75     * Setting organization.
76     *
77     * @var array
78     */
79    private $_organization = array();
80
81    /**
82     * Setting errors.
83     *
84     * @var array
85     */
86    private $_errors = array();
87
88    /**
89     * Setting errors.
90     *
91     * @var bool
92     */
93    private $_spValidationOnly = false;
94
95    /**
96     * Initializes the settings:
97     * - Sets the paths of the different folders
98     * - Loads settings info from settings file or array/object provided
99     *
100     * @param array|object|null $settings SAML Toolkit Settings
101     * @param bool $spValidationOnly
102     *
103     * @throws OneLogin_Saml2_Error If any settings parameter is invalid
104     * @throws Exception If OneLogin_Saml2_Settings is incorrectly supplied
105     */
106    public function __construct($settings = null, $spValidationOnly = false)
107    {
108        $this->_spValidationOnly = $spValidationOnly;
109        $this->_loadPaths();
110
111        if (!isset($settings)) {
112            if (!$this->_loadSettingsFromFile()) {
113                throw new OneLogin_Saml2_Error(
114                    'Invalid file settings: %s',
115                    OneLogin_Saml2_Error::SETTINGS_INVALID,
116                    array(implode(', ', $this->_errors))
117                );
118            }
119            $this->_addDefaultValues();
120        } else if (is_array($settings)) {
121            if (!$this->_loadSettingsFromArray($settings)) {
122                throw new OneLogin_Saml2_Error(
123                    'Invalid array settings: %s',
124                    OneLogin_Saml2_Error::SETTINGS_INVALID,
125                    array(implode(', ', $this->_errors))
126                );
127            }
128        } else if ($settings instanceof OneLogin_Saml2_Settings) {
129            throw new OneLogin_Saml2_Error(
130                'Only instances of OneLogin_Saml_Settings are supported.',
131                OneLogin_Saml2_Error::UNSUPPORTED_SETTINGS_OBJECT,
132                array(implode(', ', $this->_errors))
133            );
134        } else {
135            if (!$this->_loadSettingsFromArray($settings->getValues())) {
136                throw new OneLogin_Saml2_Error(
137                    'Invalid array settings: %s',
138                    OneLogin_Saml2_Error::SETTINGS_INVALID,
139                    array(implode(', ', $this->_errors))
140                );
141            }
142        }
143
144        $this->formatIdPCert();
145        $this->formatSPCert();
146        $this->formatSPKey();
147        $this->formatSPCertNew();
148        $this->formatIdPCertMulti();
149    }
150
151    /**
152     * Sets the paths of the different folders
153     * @suppress PhanUndeclaredConstant
154     */
155    private function _loadPaths()
156    {
157        $basePath = dirname(dirname(__DIR__)).'/';
158        $this->_paths = array (
159            'base' => $basePath,
160            'config' => $basePath,
161            'cert' => $basePath.'certs/',
162            'lib' => $basePath.'lib/Saml2/',
163            'extlib' => $basePath.'extlib/'
164        );
165
166        if (defined('ONELOGIN_CUSTOMPATH')) {
167            $this->_paths['config'] = ONELOGIN_CUSTOMPATH;
168            $this->_paths['cert'] = ONELOGIN_CUSTOMPATH.'certs/';
169        }
170    }
171
172    /**
173     * Returns base path.
174     *
175     * @return string  The base toolkit folder path
176     */
177    public function getBasePath()
178    {
179        return $this->_paths['base'];
180    }
181
182    /**
183     * Returns cert path.
184     *
185     * @return string The cert folder path
186     */
187    public function getCertPath()
188    {
189        return $this->_paths['cert'];
190    }
191
192    /**
193     * Returns config path.
194     *
195     * @return string The config folder path
196     */
197    public function getConfigPath()
198    {
199        return $this->_paths['config'];
200    }
201
202    /**
203     * Returns lib path.
204     *
205     * @return string The library folder path
206     */
207    public function getLibPath()
208    {
209        return $this->_paths['lib'];
210    }
211
212    /**
213     * Returns external lib path.
214     *
215     * @return string  The external library folder path
216     */
217    public function getExtLibPath()
218    {
219        return $this->_paths['extlib'];
220    }
221
222    /**
223     * Returns schema path.
224     *
225     * @return string  The external library folder path
226     */
227    public function getSchemasPath()
228    {
229        if (isset($this->_paths['schemas'])) {
230            return $this->_paths['schemas'];
231        }
232        return __DIR__ . '/schemas/';
233    }
234
235    /**
236     * Set schemas path
237     *
238     * @param string $path
239     * @return $this
240     */
241    public function setSchemasPath($path)
242    {
243        $this->_paths['schemas'] = $path;
244    }
245
246    /**
247     * Loads settings info from a settings Array
248     *
249     * @param array $settings SAML Toolkit Settings
250     *
251     * @return bool True if the settings info is valid
252     */
253    private function _loadSettingsFromArray($settings)
254    {
255        if (isset($settings['sp'])) {
256            $this->_sp = $settings['sp'];
257        }
258        if (isset($settings['idp'])) {
259            $this->_idp = $settings['idp'];
260        }
261
262        $errors = $this->checkSettings($settings);
263        if (empty($errors)) {
264            $this->_errors = array();
265
266            if (isset($settings['strict'])) {
267                $this->_strict = $settings['strict'];
268            }
269            if (isset($settings['debug'])) {
270                $this->_debug = $settings['debug'];
271            }
272
273            if (isset($settings['baseurl'])) {
274                $this->_baseurl = $settings['baseurl'];
275            }
276
277            if (isset($settings['compress'])) {
278                $this->_compress = $settings['compress'];
279            }
280
281            if (isset($settings['security'])) {
282                $this->_security = $settings['security'];
283            }
284
285            if (isset($settings['contactPerson'])) {
286                $this->_contacts = $settings['contactPerson'];
287            }
288
289            if (isset($settings['organization'])) {
290                $this->_organization = $settings['organization'];
291            }
292
293            $this->_addDefaultValues();
294            return true;
295        } else {
296            $this->_errors = $errors;
297            return false;
298        }
299    }
300
301    /**
302     * Loads settings info from the settings file
303     *
304     * @return bool True if the settings info is valid
305     *
306     * @throws OneLogin_Saml2_Error
307     *
308     * @suppress PhanUndeclaredVariable
309     */
310    private function _loadSettingsFromFile()
311    {
312        $filename = $this->getConfigPath().'settings.php';
313
314        if (!file_exists($filename)) {
315            throw new OneLogin_Saml2_Error(
316                'Settings file not found: %s',
317                OneLogin_Saml2_Error::SETTINGS_FILE_NOT_FOUND,
318                array($filename)
319            );
320        }
321
322        /** @var array $settings */
323        include $filename;
324
325        // Add advance_settings if exists
326
327        $advancedFilename = $this->getConfigPath().'advanced_settings.php';
328
329        if (file_exists($advancedFilename)) {
330            /** @var array $advancedSettings */
331            include $advancedFilename;
332            $settings = array_merge($settings, $advancedSettings);
333        }
334
335
336        return $this->_loadSettingsFromArray($settings);
337    }
338
339    /**
340     * Add default values if the settings info is not complete
341     */
342    private function _addDefaultValues()
343    {
344        if (!isset($this->_sp['assertionConsumerService']['binding'])) {
345            $this->_sp['assertionConsumerService']['binding'] = OneLogin_Saml2_Constants::BINDING_HTTP_POST;
346        }
347        if (isset($this->_sp['singleLogoutService']) && !isset($this->_sp['singleLogoutService']['binding'])) {
348            $this->_sp['singleLogoutService']['binding'] = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT;
349        }
350
351        if (!isset($this->_compress['requests'])) {
352            $this->_compress['requests'] = true;
353        }
354
355        if (!isset($this->_compress['responses'])) {
356            $this->_compress['responses'] = true;
357        }
358
359        // Related to nameID
360        if (!isset($this->_sp['NameIDFormat'])) {
361            $this->_sp['NameIDFormat'] = OneLogin_Saml2_Constants::NAMEID_UNSPECIFIED;
362        }
363        if (!isset($this->_security['nameIdEncrypted'])) {
364            $this->_security['nameIdEncrypted'] = false;
365        }
366        if (!isset($this->_security['requestedAuthnContext'])) {
367            $this->_security['requestedAuthnContext'] = true;
368        }
369
370        // sign provided
371        if (!isset($this->_security['authnRequestsSigned'])) {
372            $this->_security['authnRequestsSigned'] = false;
373        }
374        if (!isset($this->_security['logoutRequestSigned'])) {
375            $this->_security['logoutRequestSigned'] = false;
376        }
377        if (!isset($this->_security['logoutResponseSigned'])) {
378            $this->_security['logoutResponseSigned'] = false;
379        }
380        if (!isset($this->_security['signMetadata'])) {
381            $this->_security['signMetadata'] = false;
382        }
383
384        // sign expected
385        if (!isset($this->_security['wantMessagesSigned'])) {
386            $this->_security['wantMessagesSigned'] = false;
387        }
388        if (!isset($this->_security['wantAssertionsSigned'])) {
389            $this->_security['wantAssertionsSigned'] = false;
390        }
391
392        // NameID element expected
393        if (!isset($this->_security['wantNameId'])) {
394            $this->_security['wantNameId'] = true;
395        }
396
397        // Relax Destination validation
398        if (!isset($this->_security['relaxDestinationValidation'])) {
399            $this->_security['relaxDestinationValidation'] = false;
400        }
401
402
403        // Strict Destination match validation
404        if (!isset($this->_security['destinationStrictlyMatches'])) {
405            $this->_security['destinationStrictlyMatches'] = false;
406        }
407
408        // InResponseTo
409        if (!isset($this->_security['rejectUnsolicitedResponsesWithInResponseTo'])) {
410            $this->_security['rejectUnsolicitedResponsesWithInResponseTo'] = false;
411        }
412
413        // encrypt expected
414        if (!isset($this->_security['wantAssertionsEncrypted'])) {
415            $this->_security['wantAssertionsEncrypted'] = false;
416        }
417        if (!isset($this->_security['wantNameIdEncrypted'])) {
418            $this->_security['wantNameIdEncrypted'] = false;
419        }
420
421        // XML validation
422        if (!isset($this->_security['wantXMLValidation'])) {
423            $this->_security['wantXMLValidation'] = true;
424        }
425
426        // SignatureAlgorithm
427        if (!isset($this->_security['signatureAlgorithm'])) {
428            $this->_security['signatureAlgorithm'] = XMLSecurityKey::RSA_SHA1;
429        }
430
431        // DigestAlgorithm
432        if (!isset($this->_security['digestAlgorithm'])) {
433            $this->_security['digestAlgorithm'] = XMLSecurityDSig::SHA1;
434        }
435
436        if (!isset($this->_security['lowercaseUrlencoding'])) {
437            $this->_security['lowercaseUrlencoding'] = false;
438        }
439
440        // Certificates / Private key /Fingerprint
441        if (!isset($this->_idp['x509cert'])) {
442            $this->_idp['x509cert'] = '';
443        }
444        if (!isset($this->_idp['certFingerprint'])) {
445            $this->_idp['certFingerprint'] = '';
446        }
447        if (!isset($this->_idp['certFingerprintAlgorithm'])) {
448            $this->_idp['certFingerprintAlgorithm'] = 'sha1';
449        }
450
451        if (!isset($this->_sp['x509cert'])) {
452            $this->_sp['x509cert'] = '';
453        }
454        if (!isset($this->_sp['privateKey'])) {
455            $this->_sp['privateKey'] = '';
456        }
457    }
458
459    /**
460     * Checks the settings info.
461     *
462     * @param array $settings Array with settings data
463     *
464     * @return array $errors  Errors found on the settings data
465     */
466    public function checkSettings($settings)
467    {
468        assert('is_array($settings)');
469
470        if (!is_array($settings) || empty($settings)) {
471            $errors = array('invalid_syntax');
472        } else {
473            $errors = array();
474            if (!$this->_spValidationOnly) {
475                $idpErrors = $this->checkIdPSettings($settings);
476                $errors = array_merge($idpErrors, $errors);
477            }
478            $spErrors = $this->checkSPSettings($settings);
479            $errors = array_merge($spErrors, $errors);
480
481            $compressErrors = $this->checkCompressionSettings($settings);
482            $errors = array_merge($compressErrors, $errors);
483        }
484
485        return $errors;
486    }
487
488    /**
489     * Checks the compression settings info.
490     *
491     * @param array $settings Array with settings data
492     *
493     * @return array $errors  Errors found on the settings data
494     */
495    public function checkCompressionSettings($settings)
496    {
497        $errors = array();
498
499        if (isset($settings['compress'])) {
500            if (!is_array($settings['compress'])) {
501                $errors[] = "invalid_syntax";
502            } else if (isset($settings['compress']['requests'])
503                && $settings['compress']['requests'] !== true
504                && $settings['compress']['requests'] !== false
505            ) {
506                $errors[] = "'compress'=>'requests' values must be true or false.";
507            } else if (isset($settings['compress']['responses'])
508                && $settings['compress']['responses'] !== true
509                && $settings['compress']['responses'] !== false
510            ) {
511                $errors[] = "'compress'=>'responses' values must be true or false.";
512            }
513        }
514        return $errors;
515    }
516
517    /**
518     * Checks the IdP settings info.
519     *
520     * @param array $settings Array with settings data
521     *
522     * @return array $errors  Errors found on the IdP settings data
523     */
524    public function checkIdPSettings($settings)
525    {
526        assert('is_array($settings)');
527
528        if (!is_array($settings) || empty($settings)) {
529            return array('invalid_syntax');
530        }
531
532        $errors = array();
533
534        if (!isset($settings['idp']) || empty($settings['idp'])) {
535            $errors[] = 'idp_not_found';
536        } else {
537            $idp = $settings['idp'];
538            if (!isset($idp['entityId']) || empty($idp['entityId'])) {
539                $errors[] = 'idp_entityId_not_found';
540            }
541
542            if (!isset($idp['singleSignOnService'])
543                || !isset($idp['singleSignOnService']['url'])
544                || empty($idp['singleSignOnService']['url'])
545            ) {
546                $errors[] = 'idp_sso_not_found';
547            } else if (!filter_var($idp['singleSignOnService']['url'], FILTER_VALIDATE_URL)) {
548                $errors[] = 'idp_sso_url_invalid';
549            }
550
551            if (isset($idp['singleLogoutService'])
552                && isset($idp['singleLogoutService']['url'])
553                && !empty($idp['singleLogoutService']['url'])
554                && !filter_var($idp['singleLogoutService']['url'], FILTER_VALIDATE_URL)
555            ) {
556                $errors[] = 'idp_slo_url_invalid';
557            }
558
559            if (isset($idp['singleLogoutService'])
560                && isset($idp['singleLogoutService']['responseUrl'])
561                && !empty($idp['singleLogoutService']['responseUrl'])
562                && !filter_var($idp['singleLogoutService']['responseUrl'], FILTER_VALIDATE_URL)
563            ) {
564                $errors[] = 'idp_slo_response_url_invalid';
565            }
566
567            if (isset($settings['security'])) {
568                $security = $settings['security'];
569
570                $existsX509 = isset($idp['x509cert']) && !empty($idp['x509cert']);
571                $existsMultiX509Sign = isset($idp['x509certMulti']) && isset($idp['x509certMulti']['signing']) && !empty($idp['x509certMulti']['signing']);
572                $existsMultiX509Enc = isset($idp['x509certMulti']) && isset($idp['x509certMulti']['encryption']) && !empty($idp['x509certMulti']['encryption']);
573
574                $existsFingerprint = isset($idp['certFingerprint']) && !empty($idp['certFingerprint']);
575                if (!($existsX509 || $existsFingerprint || $existsMultiX509Sign)
576                ) {
577                    $errors[] = 'idp_cert_or_fingerprint_not_found_and_required';
578                }
579                if ((isset($security['nameIdEncrypted']) && $security['nameIdEncrypted'] == true)
580                    && !($existsX509 || $existsMultiX509Enc)
581                ) {
582                    $errors[] = 'idp_cert_not_found_and_required';
583                }
584            }
585        }
586
587        return $errors;
588    }
589
590    /**
591     * Checks the SP settings info.
592     *
593     * @param array $settings Array with settings data
594     *
595     * @return array $errors  Errors found on the SP settings data
596     */
597    public function checkSPSettings($settings)
598    {
599        assert('is_array($settings)');
600
601        if (!is_array($settings) || empty($settings)) {
602            return array('invalid_syntax');
603        }
604
605        $errors = array();
606
607        if (!isset($settings['sp']) || empty($settings['sp'])) {
608            $errors[] = 'sp_not_found';
609        } else {
610            $sp = $settings['sp'];
611            $security = array();
612            if (isset($settings['security'])) {
613                $security = $settings['security'];
614            }
615
616            if (!isset($sp['entityId']) || empty($sp['entityId'])) {
617                $errors[] = 'sp_entityId_not_found';
618            }
619
620            if (!isset($sp['assertionConsumerService'])
621                || !isset($sp['assertionConsumerService']['url'])
622                || empty($sp['assertionConsumerService']['url'])
623            ) {
624                $errors[] = 'sp_acs_not_found';
625            } else if (!filter_var($sp['assertionConsumerService']['url'], FILTER_VALIDATE_URL)) {
626                $errors[] = 'sp_acs_url_invalid';
627            }
628
629            if (isset($sp['singleLogoutService'])
630                && isset($sp['singleLogoutService']['url'])
631                && !filter_var($sp['singleLogoutService']['url'], FILTER_VALIDATE_URL)
632            ) {
633                $errors[] = 'sp_sls_url_invalid';
634            }
635
636            if (isset($security['signMetadata']) && is_array($security['signMetadata'])) {
637                if ((!isset($security['signMetadata']['keyFileName'])
638                    || !isset($security['signMetadata']['certFileName'])) &&
639                    (!isset($security['signMetadata']['privateKey'])
640                    || !isset($security['signMetadata']['x509cert']))
641                ) {
642                    $errors[] = 'sp_signMetadata_invalid';
643                }
644            }
645
646            if (((isset($security['authnRequestsSigned']) && $security['authnRequestsSigned'] == true)
647                || (isset($security['logoutRequestSigned']) && $security['logoutRequestSigned'] == true)
648                || (isset($security['logoutResponseSigned']) && $security['logoutResponseSigned'] == true)
649                || (isset($security['wantAssertionsEncrypted']) && $security['wantAssertionsEncrypted'] == true)
650                || (isset($security['wantNameIdEncrypted']) && $security['wantNameIdEncrypted'] == true))
651                && !$this->checkSPCerts()
652            ) {
653                $errors[] = 'sp_certs_not_found_and_required';
654            }
655        }
656
657        if (isset($settings['contactPerson'])) {
658            $types = array_keys($settings['contactPerson']);
659            $validTypes = array('technical', 'support', 'administrative', 'billing', 'other');
660            foreach ($types as $type) {
661                if (!in_array($type, $validTypes)) {
662                    $errors[] = 'contact_type_invalid';
663                    break;
664                }
665            }
666
667            foreach ($settings['contactPerson'] as $type => $contact) {
668                if (!isset($contact['givenName']) || empty($contact['givenName'])
669                    || !isset($contact['emailAddress']) || empty($contact['emailAddress'])
670                ) {
671                    $errors[] = 'contact_not_enought_data';
672                    break;
673                }
674            }
675        }
676
677        if (isset($settings['organization'])) {
678            foreach ($settings['organization'] as $organization) {
679                if (!isset($organization['name']) || empty($organization['name'])
680                    || !isset($organization['displayname']) || empty($organization['displayname'])
681                    || !isset($organization['url']) || empty($organization['url'])
682                ) {
683                    $errors[] = 'organization_not_enought_data';
684                    break;
685                }
686            }
687        }
688
689        return $errors;
690    }
691
692    /**
693     * Checks if the x509 certs of the SP exists and are valid.
694     *
695     * @return bool
696     */
697    public function checkSPCerts()
698    {
699        $key = $this->getSPkey();
700        $cert = $this->getSPcert();
701        return (!empty($key) && !empty($cert));
702    }
703
704    /**
705     * Returns the x509 private key of the SP.
706     *
707     * @return string SP private key
708     */
709    public function getSPkey()
710    {
711        $key = null;
712        if (isset($this->_sp['privateKey']) && !empty($this->_sp['privateKey'])) {
713            $key = $this->_sp['privateKey'];
714        } else {
715            $keyFile = $this->_paths['cert'].'sp.key';
716
717            if (file_exists($keyFile)) {
718                $key = file_get_contents($keyFile);
719            }
720        }
721        return $key;
722    }
723
724    /**
725     * Returns the x509 public cert of the SP.
726     *
727     * @return string SP public cert
728     */
729    public function getSPcert()
730    {
731        $cert = null;
732
733        if (isset($this->_sp['x509cert']) && !empty($this->_sp['x509cert'])) {
734            $cert = $this->_sp['x509cert'];
735        } else {
736            $certFile = $this->_paths['cert'].'sp.crt';
737
738            if (file_exists($certFile)) {
739                $cert = file_get_contents($certFile);
740            }
741        }
742        return $cert;
743    }
744
745    /**
746     * Returns the x509 public of the SP that is
747     * planed to be used soon instead the other
748     * public cert
749     * @return string SP public cert New
750     */
751    public function getSPcertNew()
752    {
753        $cert = null;
754
755        if (isset($this->_sp['x509certNew']) && !empty($this->_sp['x509certNew'])) {
756            $cert = $this->_sp['x509certNew'];
757        } else {
758            $certFile = $this->_paths['cert'].'sp_new.crt';
759
760            if (file_exists($certFile)) {
761                $cert = file_get_contents($certFile);
762            }
763        }
764        return $cert;
765    }
766
767    /**
768     * Gets the IdP data.
769     *
770     * @return array  IdP info
771     */
772    public function getIdPData()
773    {
774        return $this->_idp;
775    }
776
777    /**
778     * Gets the SP data.
779     *
780     * @return array  SP info
781     */
782    public function getSPData()
783    {
784        return $this->_sp;
785    }
786
787    /**
788     * Gets security data.
789     *
790     * @return array  SP info
791     */
792    public function getSecurityData()
793    {
794        return $this->_security;
795    }
796
797    /**
798     * Gets contact data.
799     *
800     * @return array  SP info
801     */
802    public function getContacts()
803    {
804        return $this->_contacts;
805    }
806
807    /**
808     * Gets organization data.
809     *
810     * @return array  SP info
811     */
812    public function getOrganization()
813    {
814        return $this->_organization;
815    }
816
817    /**
818    * Should SAML requests be compressed?
819    *
820    * @return bool Yes/No as True/False
821    */
822    public function shouldCompressRequests()
823    {
824        return $this->_compress['requests'];
825    }
826
827    /**
828    * Should SAML responses be compressed?
829    *
830    * @return bool Yes/No as True/False
831    */
832    public function shouldCompressResponses()
833    {
834        return $this->_compress['responses'];
835    }
836
837    /**
838     * Gets the SP metadata. The XML representation.
839     *
840     * @param bool $alwaysPublishEncryptionCert When 'true', the returned metadata
841     *   will always include an 'encryption' KeyDescriptor. Otherwise, the 'encryption'
842     *   KeyDescriptor will only be included if $advancedSettings['security']['wantNameIdEncrypted']
843     *   or $advancedSettings['security']['wantAssertionsEncrypted'] are enabled.
844     * @param DateTime|null $validUntil    Metadata's valid time
845     * @param int|null      $cacheDuration Duration of the cache in seconds
846     *
847     * @return string  SP metadata (xml)
848     *
849     * @throws Exception
850     * @throws OneLogin_Saml2_Error
851     */
852    public function getSPMetadata($alwaysPublishEncryptionCert = false, $validUntil = null, $cacheDuration = null)
853    {
854        $metadata = OneLogin_Saml2_Metadata::builder($this->_sp, $this->_security['authnRequestsSigned'], $this->_security['wantAssertionsSigned'], $validUntil, $cacheDuration, $this->getContacts(), $this->getOrganization());
855
856        $certNew = $this->getSPcertNew();
857        if (!empty($certNew)) {
858            $metadata = OneLogin_Saml2_Metadata::addX509KeyDescriptors(
859                $metadata,
860                $certNew,
861                $alwaysPublishEncryptionCert || $this->_security['wantNameIdEncrypted'] || $this->_security['wantAssertionsEncrypted']
862            );
863        }
864
865        $cert = $this->getSPcert();
866        if (!empty($cert)) {
867            $metadata = OneLogin_Saml2_Metadata::addX509KeyDescriptors(
868                $metadata,
869                $cert,
870                $alwaysPublishEncryptionCert || $this->_security['wantNameIdEncrypted'] || $this->_security['wantAssertionsEncrypted']
871            );
872        }
873
874        //Sign Metadata
875        if (isset($this->_security['signMetadata']) && $this->_security['signMetadata'] !== false) {
876            if ($this->_security['signMetadata'] === true) {
877                $keyMetadata = $this->getSPkey();
878                $certMetadata = $cert;
879                if (!$keyMetadata) {
880                    throw new OneLogin_Saml2_Error(
881                        'SP Private key not found.',
882                        OneLogin_Saml2_Error::PRIVATE_KEY_FILE_NOT_FOUND
883                    );
884                }
885                if (!$certMetadata) {
886                    throw new OneLogin_Saml2_Error(
887                        'SP Public cert not found.',
888                        OneLogin_Saml2_Error::PUBLIC_CERT_FILE_NOT_FOUND
889                    );
890                }
891            } else if (isset($this->_security['signMetadata']['keyFileName']) &&
892                isset($this->_security['signMetadata']['certFileName'])) {
893                $keyFileName = $this->_security['signMetadata']['keyFileName'];
894                $certFileName = $this->_security['signMetadata']['certFileName'];
895                $keyMetadataFile = $this->_paths['cert'].$keyFileName;
896                $certMetadataFile = $this->_paths['cert'].$certFileName;
897                if (!file_exists($keyMetadataFile)) {
898                    throw new OneLogin_Saml2_Error(
899                        'SP Private key file not found: %s',
900                        OneLogin_Saml2_Error::PRIVATE_KEY_FILE_NOT_FOUND,
901                        array($keyMetadataFile)
902                    );
903                }
904                if (!file_exists($certMetadataFile)) {
905                    throw new OneLogin_Saml2_Error(
906                        'SP Public cert file not found: %s',
907                        OneLogin_Saml2_Error::PUBLIC_CERT_FILE_NOT_FOUND,
908                        array($certMetadataFile)
909                    );
910                }
911                $keyMetadata = file_get_contents($keyMetadataFile);
912                $certMetadata = file_get_contents($certMetadataFile);
913            } else if (isset($this->_security['signMetadata']['privateKey']) &&
914                isset($this->_security['signMetadata']['x509cert'])) {
915                $keyMetadata = OneLogin_Saml2_Utils::formatPrivateKey($this->_security['signMetadata']['privateKey']);
916                $certMetadata = OneLogin_Saml2_Utils::formatCert($this->_security['signMetadata']['x509cert']);
917                if (!$keyMetadata) {
918                    throw new OneLogin_Saml2_Error(
919                        'Private key not found.',
920                        OneLogin_Saml2_Error::PRIVATE_KEY_FILE_NOT_FOUND
921                    );
922                }
923                if (!$certMetadata) {
924                    throw new OneLogin_Saml2_Error(
925                        'Public cert not found.',
926                        OneLogin_Saml2_Error::PUBLIC_CERT_FILE_NOT_FOUND
927                    );
928                }
929            } else {
930                throw new OneLogin_Saml2_Error(
931                    'Invalid Setting: signMetadata value of the sp is not valid',
932                    OneLogin_Saml2_Error::SETTINGS_INVALID_SYNTAX
933                );
934            }
935
936            $signatureAlgorithm = $this->_security['signatureAlgorithm'];
937            $digestAlgorithm = $this->_security['digestAlgorithm'];
938            $metadata = OneLogin_Saml2_Metadata::signMetadata($metadata, $keyMetadata, $certMetadata, $signatureAlgorithm, $digestAlgorithm);
939        }
940        return $metadata;
941    }
942
943    /**
944     * Validates an XML SP Metadata.
945     *
946     * @param string $xml Metadata's XML that will be validate
947     *
948     * @return Array The list of found errors
949     *
950     * @throws Exception
951     */
952    public function validateMetadata($xml)
953    {
954        assert('is_string($xml)');
955
956        $errors = array();
957        $res = OneLogin_Saml2_Utils::validateXML($xml, 'saml-schema-metadata-2.0.xsd', $this->_debug, $this->getSchemasPath());
958        if (!$res instanceof DOMDocument) {
959            $errors[] = $res;
960        } else {
961            $dom = $res;
962            $element = $dom->documentElement;
963            if ($element->tagName !== 'md:EntityDescriptor') {
964                $errors[] = 'noEntityDescriptor_xml';
965            } else {
966                $validUntil = $cacheDuration = $expireTime = null;
967
968                if ($element->hasAttribute('validUntil')) {
969                    $validUntil = OneLogin_Saml2_Utils::parseSAML2Time($element->getAttribute('validUntil'));
970                }
971                if ($element->hasAttribute('cacheDuration')) {
972                    $cacheDuration = $element->getAttribute('cacheDuration');
973                }
974
975                $expireTime = OneLogin_Saml2_Utils::getExpireTime($cacheDuration, $validUntil);
976                if (isset($expireTime) && time() > $expireTime) {
977                    $errors[] = 'expired_xml';
978                }
979            }
980        }
981
982        // TODO: Support Metadata Sign Validation
983
984        return $errors;
985    }
986
987    /**
988     * Formats the IdP cert.
989     */
990    public function formatIdPCert()
991    {
992        if (isset($this->_idp['x509cert'])) {
993            $this->_idp['x509cert'] = OneLogin_Saml2_Utils::formatCert($this->_idp['x509cert']);
994        }
995    }
996
997    /**
998     * Formats the Multple IdP certs.
999     */
1000    public function formatIdPCertMulti()
1001    {
1002        if (isset($this->_idp['x509certMulti'])) {
1003            if (isset($this->_idp['x509certMulti']['signing'])) {
1004                foreach ($this->_idp['x509certMulti']['signing'] as $i => $cert) {
1005                    $this->_idp['x509certMulti']['signing'][$i] = OneLogin_Saml2_Utils::formatCert($cert);
1006                }
1007            }
1008            if (isset($this->_idp['x509certMulti']['encryption'])) {
1009                foreach ($this->_idp['x509certMulti']['encryption'] as $i => $cert) {
1010                    $this->_idp['x509certMulti']['encryption'][$i] = OneLogin_Saml2_Utils::formatCert($cert);
1011                }
1012            }
1013        }
1014    }
1015
1016    /**
1017     * Formats the SP cert.
1018     */
1019    public function formatSPCert()
1020    {
1021        if (isset($this->_sp['x509cert'])) {
1022            $this->_sp['x509cert'] = OneLogin_Saml2_Utils::formatCert($this->_sp['x509cert']);
1023        }
1024    }
1025
1026    /**
1027     * Formats the SP cert.
1028     */
1029    public function formatSPCertNew()
1030    {
1031        if (isset($this->_sp['x509certNew'])) {
1032            $this->_sp['x509certNew'] = OneLogin_Saml2_Utils::formatCert($this->_sp['x509certNew']);
1033        }
1034    }
1035
1036    /**
1037     * Formats the SP private key.
1038     */
1039    public function formatSPKey()
1040    {
1041        if (isset($this->_sp['privateKey'])) {
1042            $this->_sp['privateKey'] = OneLogin_Saml2_Utils::formatPrivateKey($this->_sp['privateKey']);
1043        }
1044    }
1045
1046    /**
1047     * Returns an array with the errors, the array is empty when the settings is ok.
1048     *
1049     * @return array Errors
1050     */
1051    public function getErrors()
1052    {
1053        return $this->_errors;
1054    }
1055
1056    /**
1057     * Activates or deactivates the strict mode.
1058     *
1059     * @param bool $value Strict parameter
1060     *
1061     * @throws Exception
1062     */
1063    public function setStrict($value)
1064    {
1065        if (!is_bool($value)) {
1066            throw new Exception('Invalid value passed to setStrict()');
1067        }
1068
1069        $this->_strict = $value;
1070    }
1071
1072    /**
1073     * Returns if the 'strict' mode is active.
1074     *
1075     * @return bool Strict parameter
1076     */
1077    public function isStrict()
1078    {
1079        return $this->_strict;
1080    }
1081
1082    /**
1083     * Returns if the debug is active.
1084     *
1085     * @return bool Debug parameter
1086     */
1087    public function isDebugActive()
1088    {
1089        return $this->_debug;
1090    }
1091
1092    /**
1093     * Set a baseurl value.
1094     *
1095     * @param $baseurl
1096     */
1097    public function setBaseURL($baseurl)
1098    {
1099        $this->_baseurl = $baseurl;
1100    }
1101
1102    /**
1103     * Returns the baseurl set on the settings if any.
1104     *
1105     * @return null|string The baseurl
1106     */
1107    public function getBaseURL()
1108    {
1109        return $this->_baseurl;
1110    }
1111
1112    /**
1113     * Sets the IdP certificate.
1114     *
1115     * @param string $cert IdP certificate
1116     */
1117    public function setIdPCert($cert)
1118    {
1119        $this->_idp['x509cert'] = $cert;
1120        $this->formatIdPCert();
1121    }
1122}
1123