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 RobRichards\XMLSecLibs\XMLSecurityKey;
19
20use Exception;
21
22/**
23 * Main class of OneLogin's PHP Toolkit
24 */
25class Auth
26{
27    /**
28     * Settings data.
29     *
30     * @var Settings
31     */
32    private $_settings;
33
34    /**
35     * User attributes data.
36     *
37     * @var array
38     */
39    private $_attributes = array();
40
41    /**
42     * User attributes data with FriendlyName index.
43     *
44     * @var array
45     */
46    private $_attributesWithFriendlyName = array();
47
48    /**
49     * NameID
50     *
51     * @var string
52     */
53    private $_nameid;
54
55    /**
56     * NameID Format
57     *
58     * @var string
59     */
60    private $_nameidFormat;
61
62    /**
63     * NameID NameQualifier
64     *
65     * @var string
66     */
67    private $_nameidNameQualifier;
68
69    /**
70     * NameID SP NameQualifier
71     *
72     * @var string
73     */
74    private $_nameidSPNameQualifier;
75
76    /**
77     * If user is authenticated.
78     *
79     * @var bool
80     */
81    private $_authenticated = false;
82
83
84    /**
85     * SessionIndex. When the user is logged, this stored it
86     * from the AuthnStatement of the SAML Response
87     *
88     * @var string
89     */
90    private $_sessionIndex;
91
92    /**
93     * SessionNotOnOrAfter. When the user is logged, this stored it
94     * from the AuthnStatement of the SAML Response
95     *
96     * @var int|null
97     */
98    private $_sessionExpiration;
99
100    /**
101     * The ID of the last message processed
102     *
103     * @var string
104     */
105    private $_lastMessageId;
106
107    /**
108     * The ID of the last assertion processed
109     *
110     * @var string
111     */
112    private $_lastAssertionId;
113
114    /**
115     * The NotOnOrAfter value of the valid SubjectConfirmationData
116     * node (if any) of the last assertion processed
117     *
118     * @var int
119     */
120    private $_lastAssertionNotOnOrAfter;
121
122    /**
123     * If any error.
124     *
125     * @var array
126     */
127    private $_errors = array();
128
129    /**
130     * Last error object.
131     *
132     * @var Error|null
133     */
134    private $_lastErrorException;
135
136    /**
137     * Last error.
138     *
139     * @var string|null
140     */
141    private $_lastError;
142
143    /**
144     * Last AuthNRequest ID or LogoutRequest ID generated by this Service Provider
145     *
146     * @var string
147     */
148    private $_lastRequestID;
149
150    /**
151     * The most recently-constructed/processed XML SAML request
152     * (AuthNRequest, LogoutRequest)
153     *
154     * @var string
155     */
156    private $_lastRequest;
157
158    /**
159     * The most recently-constructed/processed XML SAML response
160     * (SAMLResponse, LogoutResponse). If the SAMLResponse was
161     * encrypted, by default tries to return the decrypted XML
162     *
163     * @var string|\DomDocument|null
164     */
165    private $_lastResponse;
166
167    /**
168     * Initializes the SP SAML instance.
169     *
170     * @param array|null $settings Setting data
171     *
172     * @throws Exception
173     * @throws Error
174     */
175    public function __construct(array $settings = null)
176    {
177        $this->_settings = new Settings($settings);
178    }
179
180    /**
181     * Returns the settings info
182     *
183     * @return Settings The settings data.
184     */
185    public function getSettings()
186    {
187        return $this->_settings;
188    }
189
190    /**
191     * Set the strict mode active/disable
192     *
193     * @param bool $value Strict parameter
194     *
195     * @throws Error
196     */
197    public function setStrict($value)
198    {
199        if (!is_bool($value)) {
200            throw new Error(
201                'Invalid value passed to setStrict()',
202                Error::SETTINGS_INVALID_SYNTAX
203            );
204        }
205
206        $this->_settings->setStrict($value);
207    }
208
209    /**
210     * Set schemas path
211     *
212     * @param string $path
213     * @return $this
214     */
215    public function setSchemasPath($path)
216    {
217        $this->_paths['schemas'] = $path;
218    }
219
220    /**
221     * Process the SAML Response sent by the IdP.
222     *
223     * @param string|null $requestId The ID of the AuthNRequest sent by this SP to the IdP
224     *
225     * @throws Error
226     * @throws ValidationError
227     */
228    public function processResponse($requestId = null)
229    {
230        $this->_errors = array();
231        $this->_lastError = $this->_lastErrorException = null;
232        if (isset($_POST['SAMLResponse'])) {
233            // AuthnResponse -- HTTP_POST Binding
234            $response = new Response($this->_settings, $_POST['SAMLResponse']);
235            $this->_lastResponse = $response->getXMLDocument();
236
237            if ($response->isValid($requestId)) {
238                $this->_attributes = $response->getAttributes();
239                $this->_attributesWithFriendlyName = $response->getAttributesWithFriendlyName();
240                $this->_nameid = $response->getNameId();
241                $this->_nameidFormat = $response->getNameIdFormat();
242                $this->_nameidNameQualifier = $response->getNameIdNameQualifier();
243                $this->_nameidSPNameQualifier = $response->getNameIdSPNameQualifier();
244                $this->_authenticated = true;
245                $this->_sessionIndex = $response->getSessionIndex();
246                $this->_sessionExpiration = $response->getSessionNotOnOrAfter();
247                $this->_lastMessageId = $response->getId();
248                $this->_lastAssertionId = $response->getAssertionId();
249                $this->_lastAssertionNotOnOrAfter = $response->getAssertionNotOnOrAfter();
250            } else {
251                $this->_errors[] = 'invalid_response';
252                $this->_lastErrorException = $response->getErrorException();
253                $this->_lastError = $response->getError();
254            }
255        } else {
256            $this->_errors[] = 'invalid_binding';
257            throw new Error(
258                'SAML Response not found, Only supported HTTP_POST Binding',
259                Error::SAML_RESPONSE_NOT_FOUND
260            );
261        }
262    }
263
264    /**
265     * Process the SAML Logout Response / Logout Request sent by the IdP.
266     *
267     * @param bool        $keepLocalSession             When false will destroy the local session, otherwise will keep it
268     * @param string|null $requestId                    The ID of the LogoutRequest sent by this SP to the IdP
269     * @param bool        $retrieveParametersFromServer True if we want to use parameters from $_SERVER to validate the signature
270     * @param callable    $cbDeleteSession              Callback to be executed to delete session
271     * @param bool        $stay                         True if we want to stay (returns the url string) False to redirect
272     *
273     * @return string|null
274     *
275     * @throws Error
276     */
277    public function processSLO($keepLocalSession = false, $requestId = null, $retrieveParametersFromServer = false, $cbDeleteSession = null, $stay = false)
278    {
279        $this->_errors = array();
280        $this->_lastError = $this->_lastErrorException = null;
281        if (isset($_GET['SAMLResponse'])) {
282            $logoutResponse = new LogoutResponse($this->_settings, $_GET['SAMLResponse']);
283            $this->_lastResponse = $logoutResponse->getXML();
284            if (!$logoutResponse->isValid($requestId, $retrieveParametersFromServer)) {
285                $this->_errors[] = 'invalid_logout_response';
286                $this->_lastErrorException = $logoutResponse->getErrorException();
287                $this->_lastError = $logoutResponse->getError();
288
289            } else if ($logoutResponse->getStatus() !== Constants::STATUS_SUCCESS) {
290                $this->_errors[] = 'logout_not_success';
291            } else {
292                $this->_lastMessageId = $logoutResponse->id;
293                if (!$keepLocalSession) {
294                    if ($cbDeleteSession === null) {
295                        Utils::deleteLocalSession();
296                    } else {
297                        call_user_func($cbDeleteSession);
298                    }
299                }
300            }
301        } else if (isset($_GET['SAMLRequest'])) {
302            $logoutRequest = new LogoutRequest($this->_settings, $_GET['SAMLRequest']);
303            $this->_lastRequest = $logoutRequest->getXML();
304            if (!$logoutRequest->isValid($retrieveParametersFromServer)) {
305                $this->_errors[] = 'invalid_logout_request';
306                $this->_lastErrorException = $logoutRequest->getErrorException();
307                $this->_lastError = $logoutRequest->getError();
308            } else {
309                if (!$keepLocalSession) {
310                    if ($cbDeleteSession === null) {
311                        Utils::deleteLocalSession();
312                    } else {
313                        call_user_func($cbDeleteSession);
314                    }
315                }
316                $inResponseTo = $logoutRequest->id;
317                $this->_lastMessageId = $logoutRequest->id;
318                $responseBuilder = new LogoutResponse($this->_settings);
319                $responseBuilder->build($inResponseTo);
320                $this->_lastResponse = $responseBuilder->getXML();
321
322                $logoutResponse = $responseBuilder->getResponse();
323
324                $parameters = array('SAMLResponse' => $logoutResponse);
325                if (isset($_GET['RelayState'])) {
326                    $parameters['RelayState'] = $_GET['RelayState'];
327                }
328
329                $security = $this->_settings->getSecurityData();
330                if (isset($security['logoutResponseSigned']) && $security['logoutResponseSigned']) {
331                    $signature = $this->buildResponseSignature($logoutResponse, isset($parameters['RelayState'])? $parameters['RelayState']: null, $security['signatureAlgorithm']);
332                    $parameters['SigAlg'] = $security['signatureAlgorithm'];
333                    $parameters['Signature'] = $signature;
334                }
335
336                return $this->redirectTo($this->getSLOResponseUrl(), $parameters, $stay);
337            }
338        } else {
339            $this->_errors[] = 'invalid_binding';
340            throw new Error(
341                'SAML LogoutRequest/LogoutResponse not found. Only supported HTTP_REDIRECT Binding',
342                Error::SAML_LOGOUTMESSAGE_NOT_FOUND
343            );
344        }
345    }
346
347    /**
348     * Redirects the user to the url past by parameter
349     * or to the url that we defined in our SSO Request.
350     *
351     * @param string $url        The target URL to redirect the user.
352     * @param array  $parameters Extra parameters to be passed as part of the url
353     * @param bool   $stay       True if we want to stay (returns the url string) False to redirect
354     *
355     * @return string|null
356     */
357    public function redirectTo($url = '', array $parameters = array(), $stay = false)
358    {
359        assert(is_string($url));
360
361        if (empty($url) && isset($_REQUEST['RelayState'])) {
362            $url = $_REQUEST['RelayState'];
363        }
364
365        return Utils::redirect($url, $parameters, $stay);
366    }
367
368    /**
369     * Checks if the user is authenticated or not.
370     *
371     * @return bool  True if the user is authenticated
372     */
373    public function isAuthenticated()
374    {
375        return $this->_authenticated;
376    }
377
378    /**
379     * Returns the set of SAML attributes.
380     *
381     * @return array  Attributes of the user.
382     */
383    public function getAttributes()
384    {
385        return $this->_attributes;
386    }
387
388
389    /**
390     * Returns the set of SAML attributes indexed by FriendlyName
391     *
392     * @return array  Attributes of the user.
393     */
394    public function getAttributesWithFriendlyName()
395    {
396        return $this->_attributesWithFriendlyName;
397    }
398
399    /**
400     * Returns the nameID
401     *
402     * @return string  The nameID of the assertion
403     */
404    public function getNameId()
405    {
406        return $this->_nameid;
407    }
408
409    /**
410     * Returns the nameID Format
411     *
412     * @return string  The nameID Format of the assertion
413     */
414    public function getNameIdFormat()
415    {
416        return $this->_nameidFormat;
417    }
418
419    /**
420     * Returns the nameID NameQualifier
421     *
422     * @return string  The nameID NameQualifier of the assertion
423     */
424    public function getNameIdNameQualifier()
425    {
426        return $this->_nameidNameQualifier;
427    }
428
429    /**
430     * Returns the nameID SP NameQualifier
431     *
432     * @return string  The nameID SP NameQualifier of the assertion
433     */
434    public function getNameIdSPNameQualifier()
435    {
436        return $this->_nameidSPNameQualifier;
437    }
438
439    /**
440     * Returns the SessionIndex
441     *
442     * @return string|null  The SessionIndex of the assertion
443     */
444    public function getSessionIndex()
445    {
446        return $this->_sessionIndex;
447    }
448
449    /**
450     * Returns the SessionNotOnOrAfter
451     *
452     * @return int|null  The SessionNotOnOrAfter of the assertion
453     */
454    public function getSessionExpiration()
455    {
456        return $this->_sessionExpiration;
457    }
458
459    /**
460     * Returns if there were any error
461     *
462     * @return array  Errors
463     */
464    public function getErrors()
465    {
466        return $this->_errors;
467    }
468
469    /**
470     * Returns the reason for the last error
471     *
472     * @return string|null  Error reason
473     */
474    public function getLastErrorReason()
475    {
476        return $this->_lastError;
477    }
478
479
480    /**
481     * Returns the last error
482     *
483     * @return Exception|null Error
484     */
485    public function getLastErrorException()
486    {
487        return $this->_lastErrorException;
488    }
489
490    /**
491     * Returns the requested SAML attribute
492     *
493     * @param string $name The requested attribute of the user.
494     *
495     * @return array|null Requested SAML attribute ($name).
496     */
497    public function getAttribute($name)
498    {
499        assert(is_string($name));
500
501        $value = null;
502        if (isset($this->_attributes[$name])) {
503            return $this->_attributes[$name];
504        }
505        return $value;
506    }
507
508    /**
509     * Returns the requested SAML attribute indexed by FriendlyName
510     *
511     * @param string $friendlyName The requested attribute of the user.
512     *
513     * @return array|null Requested SAML attribute ($friendlyName).
514     */
515    public function getAttributeWithFriendlyName($friendlyName)
516    {
517        assert(is_string($friendlyName));
518        $value = null;
519        if (isset($this->_attributesWithFriendlyName[$friendlyName])) {
520            return $this->_attributesWithFriendlyName[$friendlyName];
521        }
522        return $value;
523    }
524
525    /**
526     * Initiates the SSO process.
527     *
528     * @param string|null $returnTo        The target URL the user should be returned to after login.
529     * @param array       $parameters      Extra parameters to be added to the GET
530     * @param bool        $forceAuthn      When true the AuthNRequest will set the ForceAuthn='true'
531     * @param bool        $isPassive       When true the AuthNRequest will set the Ispassive='true'
532     * @param bool        $stay            True if we want to stay (returns the url string) False to redirect
533     * @param bool        $setNameIdPolicy When true the AuthNRequest will set a nameIdPolicy element
534     * @param string      $nameIdValueReq  Indicates to the IdP the subject that should be authenticated
535     *
536     * @return string|null If $stay is True, it return a string with the SLO URL + LogoutRequest + parameters
537     *
538     * @throws Error
539     */
540    public function login($returnTo = null, array $parameters = array(), $forceAuthn = false, $isPassive = false, $stay = false, $setNameIdPolicy = true, $nameIdValueReq = null)
541    {
542        $authnRequest = $this->buildAuthnRequest($this->_settings, $forceAuthn, $isPassive, $setNameIdPolicy, $nameIdValueReq);
543
544        $this->_lastRequest = $authnRequest->getXML();
545        $this->_lastRequestID = $authnRequest->getId();
546
547        $samlRequest = $authnRequest->getRequest();
548        $parameters['SAMLRequest'] = $samlRequest;
549
550        if (!empty($returnTo)) {
551            $parameters['RelayState'] = $returnTo;
552        } else {
553            $parameters['RelayState'] = Utils::getSelfRoutedURLNoQuery();
554        }
555
556        $security = $this->_settings->getSecurityData();
557        if (isset($security['authnRequestsSigned']) && $security['authnRequestsSigned']) {
558            $signature = $this->buildRequestSignature($samlRequest, $parameters['RelayState'], $security['signatureAlgorithm']);
559            $parameters['SigAlg'] = $security['signatureAlgorithm'];
560            $parameters['Signature'] = $signature;
561        }
562        return $this->redirectTo($this->getSSOurl(), $parameters, $stay);
563    }
564
565    /**
566     * Initiates the SLO process.
567     *
568     * @param string|null $returnTo            The target URL the user should be returned to after logout.
569     * @param array       $parameters          Extra parameters to be added to the GET
570     * @param string|null $nameId              The NameID that will be set in the LogoutRequest.
571     * @param string|null $sessionIndex        The SessionIndex (taken from the SAML Response in the SSO process).
572     * @param bool        $stay                True if we want to stay (returns the url string) False to redirect
573     * @param string|null $nameIdFormat        The NameID Format will be set in the LogoutRequest.
574     * @param string|null $nameIdNameQualifier The NameID NameQualifier will be set in the LogoutRequest.
575     *
576     * @return string|null If $stay is True, it return a string with the SLO URL + LogoutRequest + parameters
577     *
578     * @throws Error
579     */
580    public function logout($returnTo = null, array $parameters = array(), $nameId = null, $sessionIndex = null, $stay = false, $nameIdFormat = null, $nameIdNameQualifier = null, $nameIdSPNameQualifier = null)
581    {
582        $sloUrl = $this->getSLOurl();
583        if (empty($sloUrl)) {
584            throw new Error(
585                'The IdP does not support Single Log Out',
586                Error::SAML_SINGLE_LOGOUT_NOT_SUPPORTED
587            );
588        }
589
590        if (empty($nameId) && !empty($this->_nameid)) {
591            $nameId = $this->_nameid;
592        }
593        if (empty($nameIdFormat) && !empty($this->_nameidFormat)) {
594            $nameIdFormat = $this->_nameidFormat;
595        }
596
597        $logoutRequest = new LogoutRequest($this->_settings, null, $nameId, $sessionIndex, $nameIdFormat, $nameIdNameQualifier, $nameIdSPNameQualifier);
598
599        $this->_lastRequest = $logoutRequest->getXML();
600        $this->_lastRequestID = $logoutRequest->id;
601
602        $samlRequest = $logoutRequest->getRequest();
603
604        $parameters['SAMLRequest'] = $samlRequest;
605        if (!empty($returnTo)) {
606            $parameters['RelayState'] = $returnTo;
607        } else {
608            $parameters['RelayState'] = Utils::getSelfRoutedURLNoQuery();
609        }
610
611        $security = $this->_settings->getSecurityData();
612        if (isset($security['logoutRequestSigned']) && $security['logoutRequestSigned']) {
613            $signature = $this->buildRequestSignature($samlRequest, $parameters['RelayState'], $security['signatureAlgorithm']);
614            $parameters['SigAlg'] = $security['signatureAlgorithm'];
615            $parameters['Signature'] = $signature;
616        }
617
618        return $this->redirectTo($sloUrl, $parameters, $stay);
619    }
620
621    /**
622     * Gets the SSO url.
623     *
624     * @return string The url of the Single Sign On Service
625     */
626    public function getSSOurl()
627    {
628        $idpData = $this->_settings->getIdPData();
629        return $idpData['singleSignOnService']['url'];
630    }
631
632    /**
633     * Gets the SLO url.
634     *
635     * @return string|null The url of the Single Logout Service
636     */
637    public function getSLOurl()
638    {
639        $url = null;
640        $idpData = $this->_settings->getIdPData();
641        if (isset($idpData['singleLogoutService']) && isset($idpData['singleLogoutService']['url'])) {
642            $url = $idpData['singleLogoutService']['url'];
643        }
644        return $url;
645    }
646
647    /**
648     * Gets the SLO response url.
649     *
650     * @return string|null The response url of the Single Logout Service
651     */
652    public function getSLOResponseUrl()
653    {
654        $idpData = $this->_settings->getIdPData();
655        if (isset($idpData['singleLogoutService']) && isset($idpData['singleLogoutService']['responseUrl'])) {
656            return $idpData['singleLogoutService']['responseUrl'];
657        }
658        return $this->getSLOurl();
659    }
660
661    /**
662     * Gets the ID of the last AuthNRequest or LogoutRequest generated by the Service Provider.
663     *
664     * @return string The ID of the Request SAML message.
665     */
666    public function getLastRequestID()
667    {
668        return $this->_lastRequestID;
669    }
670
671    /**
672     * Creates an AuthnRequest
673     *
674     * @param Settings $settings        Setting data
675     * @param bool     $forceAuthn      When true the AuthNRequest will set the ForceAuthn='true'
676     * @param bool     $isPassive       When true the AuthNRequest will set the Ispassive='true'
677     * @param bool     $setNameIdPolicy When true the AuthNRequest will set a nameIdPolicy element
678     * @param string   $nameIdValueReq  Indicates to the IdP the subject that should be authenticated
679     *
680     * @return AuthnRequest The AuthnRequest object
681     */
682    public function buildAuthnRequest($settings, $forceAuthn, $isPassive, $setNameIdPolicy, $nameIdValueReq = null)
683    {
684        return new AuthnRequest($settings, $forceAuthn, $isPassive, $setNameIdPolicy, $nameIdValueReq);
685    }
686
687    /**
688     * Generates the Signature for a SAML Request
689     *
690     * @param string $samlRequest   The SAML Request
691     * @param string $relayState    The RelayState
692     * @param string $signAlgorithm Signature algorithm method
693     *
694     * @return string A base64 encoded signature
695     *
696     * @throws Exception
697     * @throws Error
698     */
699    public function buildRequestSignature($samlRequest, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA256)
700    {
701        return $this->buildMessageSignature($samlRequest, $relayState, $signAlgorithm, "SAMLRequest");
702    }
703
704    /**
705     * Generates the Signature for a SAML Response
706     *
707     * @param string $samlResponse  The SAML Response
708     * @param string $relayState    The RelayState
709     * @param string $signAlgorithm Signature algorithm method
710     *
711     * @return string A base64 encoded signature
712     *
713     * @throws Exception
714     * @throws Error
715     */
716    public function buildResponseSignature($samlResponse, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA256)
717    {
718        return $this->buildMessageSignature($samlResponse, $relayState, $signAlgorithm, "SAMLResponse");
719    }
720
721    /**
722     * Generates the Signature for a SAML Message
723     *
724     * @param string $samlMessage   The SAML Message
725     * @param string $relayState    The RelayState
726     * @param string $signAlgorithm Signature algorithm method
727     * @param string $type          "SAMLRequest" or "SAMLResponse"
728     *
729     * @return string A base64 encoded signature
730     *
731     * @throws Exception
732     * @throws Error
733     */
734    private function buildMessageSignature($samlMessage, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA256, $type = "SAMLRequest")
735    {
736        $key = $this->_settings->getSPkey();
737        if (empty($key)) {
738            if ($type == "SAMLRequest") {
739                $errorMsg = "Trying to sign the SAML Request but can't load the SP private key";
740            } else {
741                $errorMsg = "Trying to sign the SAML Response but can't load the SP private key";
742            }
743
744            throw new Error($errorMsg, Error::PRIVATE_KEY_NOT_FOUND);
745        }
746
747        $objKey = new XMLSecurityKey($signAlgorithm, array('type' => 'private'));
748        $objKey->loadKey($key, false);
749
750        $security = $this->_settings->getSecurityData();
751        if ($security['lowercaseUrlencoding']) {
752            $msg = $type.'='.rawurlencode($samlMessage);
753            if (isset($relayState)) {
754                $msg .= '&RelayState='.rawurlencode($relayState);
755            }
756            $msg .= '&SigAlg=' . rawurlencode($signAlgorithm);
757        } else {
758            $msg = $type.'='.urlencode($samlMessage);
759            if (isset($relayState)) {
760                $msg .= '&RelayState='.urlencode($relayState);
761            }
762            $msg .= '&SigAlg=' . urlencode($signAlgorithm);
763        }
764        $signature = $objKey->signData($msg);
765        return base64_encode($signature);
766    }
767
768    /**
769     * @return string The ID of the last message processed
770     */
771    public function getLastMessageId()
772    {
773        return $this->_lastMessageId;
774    }
775
776    /**
777     * @return string The ID of the last assertion processed
778     */
779    public function getLastAssertionId()
780    {
781        return $this->_lastAssertionId;
782    }
783
784    /**
785     * @return int The NotOnOrAfter value of the valid
786     *         SubjectConfirmationData node (if any)
787     *         of the last assertion processed
788     */
789    public function getLastAssertionNotOnOrAfter()
790    {
791        return $this->_lastAssertionNotOnOrAfter;
792    }
793
794    /**
795     * Returns the most recently-constructed/processed
796     * XML SAML request (AuthNRequest, LogoutRequest)
797     *
798     * @return string|null The Request XML
799     */
800    public function getLastRequestXML()
801    {
802        return $this->_lastRequest;
803    }
804
805    /**
806     * Returns the most recently-constructed/processed
807     * XML SAML response (SAMLResponse, LogoutResponse).
808     * If the SAMLResponse was encrypted, by default tries
809     * to return the decrypted XML.
810     *
811     * @return string|null The Response XML
812     */
813    public function getLastResponseXML()
814    {
815        $response = null;
816        if (isset($this->_lastResponse)) {
817            if (is_string($this->_lastResponse)) {
818                $response = $this->_lastResponse;
819            } else {
820                $response = $this->_lastResponse->saveXML();
821            }
822        }
823
824        return $response;
825    }
826}
827