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