3 * This file is part of php-saml.
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
11 * @author OneLogin Inc <saml-info@onelogin.com>
12 * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE
13 * @link https://github.com/onelogin/php-saml
16 namespace OneLogin\Saml2;
18 use RobRichards\XMLSecLibs\XMLSecurityKey;
23 * Main class of OneLogin's PHP Toolkit
35 * User attributes data.
39 private $_attributes = array();
42 * User attributes data with FriendlyName index.
46 private $_attributesWithFriendlyName = array();
60 private $_nameidFormat;
63 * NameID NameQualifier
67 private $_nameidNameQualifier;
70 * NameID SP NameQualifier
74 private $_nameidSPNameQualifier;
77 * If user is authenticated.
81 private $_authenticated = false;
85 * SessionIndex. When the user is logged, this stored it
86 * from the AuthnStatement of the SAML Response
90 private $_sessionIndex;
93 * SessionNotOnOrAfter. When the user is logged, this stored it
94 * from the AuthnStatement of the SAML Response
98 private $_sessionExpiration;
101 * The ID of the last message processed
105 private $_lastMessageId;
108 * The ID of the last assertion processed
112 private $_lastAssertionId;
115 * The NotOnOrAfter value of the valid SubjectConfirmationData
116 * node (if any) of the last assertion processed
120 private $_lastAssertionNotOnOrAfter;
127 private $_errors = array();
134 private $_lastErrorException;
144 * Last AuthNRequest ID or LogoutRequest ID generated by this Service Provider
148 private $_lastRequestID;
151 * The most recently-constructed/processed XML SAML request
152 * (AuthNRequest, LogoutRequest)
156 private $_lastRequest;
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
163 * @var string|\DomDocument|null
165 private $_lastResponse;
168 * Initializes the SP SAML instance.
170 * @param array|null $settings Setting data
175 public function __construct(array $settings = null)
177 $this->_settings = new Settings($settings);
181 * Returns the settings info
183 * @return Settings The settings data.
185 public function getSettings()
187 return $this->_settings;
191 * Set the strict mode active/disable
193 * @param bool $value Strict parameter
197 public function setStrict($value)
199 if (!is_bool($value)) {
201 'Invalid value passed to setStrict()',
202 Error::SETTINGS_INVALID_SYNTAX
206 $this->_settings->setStrict($value);
212 * @param string $path
215 public function setSchemasPath($path)
217 $this->_paths['schemas'] = $path;
221 * Process the SAML Response sent by the IdP.
223 * @param string|null $requestId The ID of the AuthNRequest sent by this SP to the IdP
226 * @throws ValidationError
228 public function processResponse($requestId = null)
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();
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();
251 $this->_errors[] = 'invalid_response';
252 $this->_lastErrorException = $response->getErrorException();
253 $this->_lastError = $response->getError();
254 $this->_errors[] = $this->_lastError;
257 $this->_errors[] = 'invalid_binding';
259 'SAML Response not found, Only supported HTTP_POST Binding',
260 Error::SAML_RESPONSE_NOT_FOUND
266 * Process the SAML Logout Response / Logout Request sent by the IdP.
268 * @param bool $keepLocalSession When false will destroy the local session, otherwise will keep it
269 * @param string|null $requestId The ID of the LogoutRequest sent by this SP to the IdP
270 * @param bool $retrieveParametersFromServer True if we want to use parameters from $_SERVER to validate the signature
271 * @param callable $cbDeleteSession Callback to be executed to delete session
272 * @param bool $stay True if we want to stay (returns the url string) False to redirect
274 * @return string|null
278 public function processSLO($keepLocalSession = false, $requestId = null, $retrieveParametersFromServer = false, $cbDeleteSession = null, $stay = false)
280 $this->_errors = array();
281 $this->_lastError = $this->_lastErrorException = null;
282 if (isset($_GET['SAMLResponse'])) {
283 $logoutResponse = new LogoutResponse($this->_settings, $_GET['SAMLResponse']);
284 $this->_lastResponse = $logoutResponse->getXML();
285 if (!$logoutResponse->isValid($requestId, $retrieveParametersFromServer)) {
286 $this->_errors[] = 'invalid_logout_response';
287 $this->_lastErrorException = $logoutResponse->getErrorException();
288 $this->_lastError = $logoutResponse->getError();
290 } else if ($logoutResponse->getStatus() !== Constants::STATUS_SUCCESS) {
291 $this->_errors[] = 'logout_not_success';
293 $this->_lastMessageId = $logoutResponse->id;
294 if (!$keepLocalSession) {
295 if ($cbDeleteSession === null) {
296 Utils::deleteLocalSession();
298 call_user_func($cbDeleteSession);
302 } else if (isset($_GET['SAMLRequest'])) {
303 $logoutRequest = new LogoutRequest($this->_settings, $_GET['SAMLRequest']);
304 $this->_lastRequest = $logoutRequest->getXML();
305 if (!$logoutRequest->isValid($retrieveParametersFromServer)) {
306 $this->_errors[] = 'invalid_logout_request';
307 $this->_lastErrorException = $logoutRequest->getErrorException();
308 $this->_lastError = $logoutRequest->getError();
310 if (!$keepLocalSession) {
311 if ($cbDeleteSession === null) {
312 Utils::deleteLocalSession();
314 call_user_func($cbDeleteSession);
317 $inResponseTo = $logoutRequest->id;
318 $this->_lastMessageId = $logoutRequest->id;
319 $responseBuilder = new LogoutResponse($this->_settings);
320 $responseBuilder->build($inResponseTo);
321 $this->_lastResponse = $responseBuilder->getXML();
323 $logoutResponse = $responseBuilder->getResponse();
325 $parameters = array('SAMLResponse' => $logoutResponse);
326 if (isset($_GET['RelayState'])) {
327 $parameters['RelayState'] = $_GET['RelayState'];
330 $security = $this->_settings->getSecurityData();
331 if (isset($security['logoutResponseSigned']) && $security['logoutResponseSigned']) {
332 $signature = $this->buildResponseSignature($logoutResponse, isset($parameters['RelayState'])? $parameters['RelayState']: null, $security['signatureAlgorithm']);
333 $parameters['SigAlg'] = $security['signatureAlgorithm'];
334 $parameters['Signature'] = $signature;
337 return $this->redirectTo($this->getSLOResponseUrl(), $parameters, $stay);
340 $this->_errors[] = 'invalid_binding';
342 'SAML LogoutRequest/LogoutResponse not found. Only supported HTTP_REDIRECT Binding',
343 Error::SAML_LOGOUTMESSAGE_NOT_FOUND
349 * Redirects the user to the url past by parameter
350 * or to the url that we defined in our SSO Request.
352 * @param string $url The target URL to redirect the user.
353 * @param array $parameters Extra parameters to be passed as part of the url
354 * @param bool $stay True if we want to stay (returns the url string) False to redirect
356 * @return string|null
358 public function redirectTo($url = '', array $parameters = array(), $stay = false)
360 assert(is_string($url));
362 if (empty($url) && isset($_REQUEST['RelayState'])) {
363 $url = $_REQUEST['RelayState'];
366 return Utils::redirect($url, $parameters, $stay);
370 * Checks if the user is authenticated or not.
372 * @return bool True if the user is authenticated
374 public function isAuthenticated()
376 return $this->_authenticated;
380 * Returns the set of SAML attributes.
382 * @return array Attributes of the user.
384 public function getAttributes()
386 return $this->_attributes;
391 * Returns the set of SAML attributes indexed by FriendlyName
393 * @return array Attributes of the user.
395 public function getAttributesWithFriendlyName()
397 return $this->_attributesWithFriendlyName;
403 * @return string The nameID of the assertion
405 public function getNameId()
407 return $this->_nameid;
411 * Returns the nameID Format
413 * @return string The nameID Format of the assertion
415 public function getNameIdFormat()
417 return $this->_nameidFormat;
421 * Returns the nameID NameQualifier
423 * @return string The nameID NameQualifier of the assertion
425 public function getNameIdNameQualifier()
427 return $this->_nameidNameQualifier;
431 * Returns the nameID SP NameQualifier
433 * @return string The nameID SP NameQualifier of the assertion
435 public function getNameIdSPNameQualifier()
437 return $this->_nameidSPNameQualifier;
441 * Returns the SessionIndex
443 * @return string|null The SessionIndex of the assertion
445 public function getSessionIndex()
447 return $this->_sessionIndex;
451 * Returns the SessionNotOnOrAfter
453 * @return int|null The SessionNotOnOrAfter of the assertion
455 public function getSessionExpiration()
457 return $this->_sessionExpiration;
461 * Returns if there were any error
463 * @return array Errors
465 public function getErrors()
467 return $this->_errors;
471 * Returns the reason for the last error
473 * @return string|null Error reason
475 public function getLastErrorReason()
477 return $this->_lastError;
482 * Returns the last error
484 * @return Exception|null Error
486 public function getLastErrorException()
488 return $this->_lastErrorException;
492 * Returns the requested SAML attribute
494 * @param string $name The requested attribute of the user.
496 * @return array|null Requested SAML attribute ($name).
498 public function getAttribute($name)
500 assert(is_string($name));
503 if (isset($this->_attributes[$name])) {
504 return $this->_attributes[$name];
510 * Returns the requested SAML attribute indexed by FriendlyName
512 * @param string $friendlyName The requested attribute of the user.
514 * @return array|null Requested SAML attribute ($friendlyName).
516 public function getAttributeWithFriendlyName($friendlyName)
518 assert(is_string($friendlyName));
520 if (isset($this->_attributesWithFriendlyName[$friendlyName])) {
521 return $this->_attributesWithFriendlyName[$friendlyName];
527 * Initiates the SSO process.
529 * @param string|null $returnTo The target URL the user should be returned to after login.
530 * @param array $parameters Extra parameters to be added to the GET
531 * @param bool $forceAuthn When true the AuthNRequest will set the ForceAuthn='true'
532 * @param bool $isPassive When true the AuthNRequest will set the Ispassive='true'
533 * @param bool $stay True if we want to stay (returns the url string) False to redirect
534 * @param bool $setNameIdPolicy When true the AuthNRequest will set a nameIdPolicy element
535 * @param string $nameIdValueReq Indicates to the IdP the subject that should be authenticated
537 * @return string|null If $stay is True, it return a string with the SLO URL + LogoutRequest + parameters
541 public function login($returnTo = null, array $parameters = array(), $forceAuthn = false, $isPassive = false, $stay = false, $setNameIdPolicy = true, $nameIdValueReq = null)
543 $authnRequest = $this->buildAuthnRequest($this->_settings, $forceAuthn, $isPassive, $setNameIdPolicy, $nameIdValueReq);
545 $this->_lastRequest = $authnRequest->getXML();
546 $this->_lastRequestID = $authnRequest->getId();
548 $samlRequest = $authnRequest->getRequest();
549 $parameters['SAMLRequest'] = $samlRequest;
551 if (!empty($returnTo)) {
552 $parameters['RelayState'] = $returnTo;
554 $parameters['RelayState'] = Utils::getSelfRoutedURLNoQuery();
557 $security = $this->_settings->getSecurityData();
558 if (isset($security['authnRequestsSigned']) && $security['authnRequestsSigned']) {
559 $signature = $this->buildRequestSignature($samlRequest, $parameters['RelayState'], $security['signatureAlgorithm']);
560 $parameters['SigAlg'] = $security['signatureAlgorithm'];
561 $parameters['Signature'] = $signature;
563 return $this->redirectTo($this->getSSOurl(), $parameters, $stay);
567 * Initiates the SLO process.
569 * @param string|null $returnTo The target URL the user should be returned to after logout.
570 * @param array $parameters Extra parameters to be added to the GET
571 * @param string|null $nameId The NameID that will be set in the LogoutRequest.
572 * @param string|null $sessionIndex The SessionIndex (taken from the SAML Response in the SSO process).
573 * @param bool $stay True if we want to stay (returns the url string) False to redirect
574 * @param string|null $nameIdFormat The NameID Format will be set in the LogoutRequest.
575 * @param string|null $nameIdNameQualifier The NameID NameQualifier will be set in the LogoutRequest.
577 * @return string|null If $stay is True, it return a string with the SLO URL + LogoutRequest + parameters
581 public function logout($returnTo = null, array $parameters = array(), $nameId = null, $sessionIndex = null, $stay = false, $nameIdFormat = null, $nameIdNameQualifier = null, $nameIdSPNameQualifier = null)
583 $sloUrl = $this->getSLOurl();
584 if (empty($sloUrl)) {
586 'The IdP does not support Single Log Out',
587 Error::SAML_SINGLE_LOGOUT_NOT_SUPPORTED
591 if (empty($nameId) && !empty($this->_nameid)) {
592 $nameId = $this->_nameid;
594 if (empty($nameIdFormat) && !empty($this->_nameidFormat)) {
595 $nameIdFormat = $this->_nameidFormat;
598 $logoutRequest = new LogoutRequest($this->_settings, null, $nameId, $sessionIndex, $nameIdFormat, $nameIdNameQualifier, $nameIdSPNameQualifier);
600 $this->_lastRequest = $logoutRequest->getXML();
601 $this->_lastRequestID = $logoutRequest->id;
603 $samlRequest = $logoutRequest->getRequest();
605 $parameters['SAMLRequest'] = $samlRequest;
606 if (!empty($returnTo)) {
607 $parameters['RelayState'] = $returnTo;
609 $parameters['RelayState'] = Utils::getSelfRoutedURLNoQuery();
612 $security = $this->_settings->getSecurityData();
613 if (isset($security['logoutRequestSigned']) && $security['logoutRequestSigned']) {
614 $signature = $this->buildRequestSignature($samlRequest, $parameters['RelayState'], $security['signatureAlgorithm']);
615 $parameters['SigAlg'] = $security['signatureAlgorithm'];
616 $parameters['Signature'] = $signature;
619 return $this->redirectTo($sloUrl, $parameters, $stay);
623 * Gets the IdP SSO url.
625 * @return string The url of the IdP Single Sign On Service
627 public function getSSOurl()
629 return $this->_settings->getIdPSSOUrl();
633 * Gets the IdP SLO url.
635 * @return string|null The url of the IdP Single Logout Service
637 public function getSLOurl()
639 return $this->_settings->getIdPSLOUrl();
643 * Gets the IdP SLO response url.
645 * @return string|null The response url of the IdP Single Logout Service
647 public function getSLOResponseUrl()
649 return $this->_settings->getIdPSLOResponseUrl();
654 * Gets the ID of the last AuthNRequest or LogoutRequest generated by the Service Provider.
656 * @return string The ID of the Request SAML message.
658 public function getLastRequestID()
660 return $this->_lastRequestID;
664 * Creates an AuthnRequest
666 * @param Settings $settings Setting data
667 * @param bool $forceAuthn When true the AuthNRequest will set the ForceAuthn='true'
668 * @param bool $isPassive When true the AuthNRequest will set the Ispassive='true'
669 * @param bool $setNameIdPolicy When true the AuthNRequest will set a nameIdPolicy element
670 * @param string $nameIdValueReq Indicates to the IdP the subject that should be authenticated
672 * @return AuthnRequest The AuthnRequest object
674 public function buildAuthnRequest($settings, $forceAuthn, $isPassive, $setNameIdPolicy, $nameIdValueReq = null)
676 return new AuthnRequest($settings, $forceAuthn, $isPassive, $setNameIdPolicy, $nameIdValueReq);
680 * Generates the Signature for a SAML Request
682 * @param string $samlRequest The SAML Request
683 * @param string $relayState The RelayState
684 * @param string $signAlgorithm Signature algorithm method
686 * @return string A base64 encoded signature
691 public function buildRequestSignature($samlRequest, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA256)
693 return $this->buildMessageSignature($samlRequest, $relayState, $signAlgorithm, "SAMLRequest");
697 * Generates the Signature for a SAML Response
699 * @param string $samlResponse The SAML Response
700 * @param string $relayState The RelayState
701 * @param string $signAlgorithm Signature algorithm method
703 * @return string A base64 encoded signature
708 public function buildResponseSignature($samlResponse, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA256)
710 return $this->buildMessageSignature($samlResponse, $relayState, $signAlgorithm, "SAMLResponse");
714 * Generates the Signature for a SAML Message
716 * @param string $samlMessage The SAML Message
717 * @param string $relayState The RelayState
718 * @param string $signAlgorithm Signature algorithm method
719 * @param string $type "SAMLRequest" or "SAMLResponse"
721 * @return string A base64 encoded signature
726 private function buildMessageSignature($samlMessage, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA256, $type = "SAMLRequest")
728 $key = $this->_settings->getSPkey();
730 if ($type == "SAMLRequest") {
731 $errorMsg = "Trying to sign the SAML Request but can't load the SP private key";
733 $errorMsg = "Trying to sign the SAML Response but can't load the SP private key";
736 throw new Error($errorMsg, Error::PRIVATE_KEY_NOT_FOUND);
739 $objKey = new XMLSecurityKey($signAlgorithm, array('type' => 'private'));
740 $objKey->loadKey($key, false);
742 $security = $this->_settings->getSecurityData();
743 if ($security['lowercaseUrlencoding']) {
744 $msg = $type.'='.rawurlencode($samlMessage);
745 if (isset($relayState)) {
746 $msg .= '&RelayState='.rawurlencode($relayState);
748 $msg .= '&SigAlg=' . rawurlencode($signAlgorithm);
750 $msg = $type.'='.urlencode($samlMessage);
751 if (isset($relayState)) {
752 $msg .= '&RelayState='.urlencode($relayState);
754 $msg .= '&SigAlg=' . urlencode($signAlgorithm);
756 $signature = $objKey->signData($msg);
757 return base64_encode($signature);
761 * @return string The ID of the last message processed
763 public function getLastMessageId()
765 return $this->_lastMessageId;
769 * @return string The ID of the last assertion processed
771 public function getLastAssertionId()
773 return $this->_lastAssertionId;
777 * @return int The NotOnOrAfter value of the valid
778 * SubjectConfirmationData node (if any)
779 * of the last assertion processed
781 public function getLastAssertionNotOnOrAfter()
783 return $this->_lastAssertionNotOnOrAfter;
787 * Returns the most recently-constructed/processed
788 * XML SAML request (AuthNRequest, LogoutRequest)
790 * @return string|null The Request XML
792 public function getLastRequestXML()
794 return $this->_lastRequest;
798 * Returns the most recently-constructed/processed
799 * XML SAML response (SAMLResponse, LogoutResponse).
800 * If the SAMLResponse was encrypted, by default tries
801 * to return the decrypted XML.
803 * @return string|null The Response XML
805 public function getLastResponseXML()
808 if (isset($this->_lastResponse)) {
809 if (is_string($this->_lastResponse)) {
810 $response = $this->_lastResponse;
812 $response = $this->_lastResponse->saveXML();