From b74fdfb55143d5a9059a5a9f841c03247a0bcffd Mon Sep 17 00:00:00 2001 From: Davide Porrovecchio Date: Sat, 5 Oct 2019 12:20:36 +0200 Subject: [PATCH 1/4] Add clockSkewTolerance setting --- advanced_settings_example.php | 4 ++++ lib/Saml2/Settings.php | 5 +++++ tests/src/OneLogin/Saml2/SettingsTest.php | 1 + 3 files changed, 10 insertions(+) diff --git a/advanced_settings_example.php b/advanced_settings_example.php index 99d0c044..2e3326e5 100644 --- a/advanced_settings_example.php +++ b/advanced_settings_example.php @@ -81,6 +81,10 @@ // (In order to validate the xml, 'strict' and 'wantXMLValidation' must be true). 'wantXMLValidation' => true, + // The clock skew tolerance (in seconds) for the validation of the + // IssueInstant attribute in the received responses. + 'clockSkewTolerance' => 180, + // If true, SAMLResponses with an empty value at its Destination // attribute will not be rejected for this fact. 'relaxDestinationValidation' => false, diff --git a/lib/Saml2/Settings.php b/lib/Saml2/Settings.php index 67410cf0..b2649a59 100644 --- a/lib/Saml2/Settings.php +++ b/lib/Saml2/Settings.php @@ -398,6 +398,11 @@ private function _addDefaultValues() $this->_security['wantXMLValidation'] = true; } + // Clock skew tolerance + if (!isset($this->_security['clockSkewTolerance'])) { + $this->_security['clockSkewTolerance'] = 0; + } + // SignatureAlgorithm if (!isset($this->_security['signatureAlgorithm'])) { $this->_security['signatureAlgorithm'] = XMLSecurityKey::RSA_SHA1; diff --git a/tests/src/OneLogin/Saml2/SettingsTest.php b/tests/src/OneLogin/Saml2/SettingsTest.php index 00e3d903..d3f99045 100644 --- a/tests/src/OneLogin/Saml2/SettingsTest.php +++ b/tests/src/OneLogin/Saml2/SettingsTest.php @@ -918,6 +918,7 @@ public function testGetSecurityData() $this->assertArrayHasKey('wantNameIdEncrypted', $security); $this->assertArrayHasKey('requestedAuthnContext', $security); $this->assertArrayHasKey('wantXMLValidation', $security); + $this->assertArrayHasKey('clockSkewTolerance', $security); $this->assertArrayHasKey('wantNameId', $security); } From 5a4d237a509de4933ce42116e2c045177b647c58 Mon Sep 17 00:00:00 2001 From: Davide Porrovecchio Date: Sat, 5 Oct 2019 12:23:54 +0200 Subject: [PATCH 2/4] Add issueInstant property to requests --- lib/Saml2/AuthnRequest.php | 17 +++++++++++++++++ lib/Saml2/LogoutRequest.php | 17 +++++++++++++++++ tests/src/OneLogin/Saml2/AuthnRequestTest.php | 17 +++++++++++++++++ tests/src/OneLogin/Saml2/LogoutRequestTest.php | 17 +++++++++++++++++ 4 files changed, 68 insertions(+) diff --git a/lib/Saml2/AuthnRequest.php b/lib/Saml2/AuthnRequest.php index 2da1046e..46f81896 100644 --- a/lib/Saml2/AuthnRequest.php +++ b/lib/Saml2/AuthnRequest.php @@ -25,6 +25,12 @@ class OneLogin_Saml2_AuthnRequest */ private $_id; + /** + * SAML AuthNRequest IssueInstant. + * @var string + */ + private $_issueInstant; + /** * Constructs the AuthnRequest object. * @@ -146,6 +152,7 @@ public function __construct(OneLogin_Saml2_Settings $settings, $forceAuthn = fal $this->_id = $id; $this->_authnRequest = $request; + $this->_issueInstant = $issueInstant; } /** @@ -181,6 +188,16 @@ public function getId() return $this->_id; } + /** + * Returns the AuthNRequest IssueInstant. + * + * @return string + */ + public function getIssueInstant() + { + return $this->_issueInstant; + } + /** * Returns the XML that will be sent as part of the request * diff --git a/lib/Saml2/LogoutRequest.php b/lib/Saml2/LogoutRequest.php index e1523903..68795bfc 100644 --- a/lib/Saml2/LogoutRequest.php +++ b/lib/Saml2/LogoutRequest.php @@ -30,6 +30,12 @@ class OneLogin_Saml2_LogoutRequest */ private $_error; + /** + * SAML LogoutRequest IssueInstant. + * @var string + */ + private $_issueInstant; + /** * Constructs the Logout Request object. * @@ -61,6 +67,7 @@ public function __construct(OneLogin_Saml2_Settings $settings, $request = null, $this->id = $id; $issueInstant = OneLogin_Saml2_Utils::parseTime2SAML(time()); + $this->_issueInstant = $issueInstant; $cert = null; if (isset($security['nameIdEncrypted']) && $security['nameIdEncrypted']) { @@ -423,6 +430,16 @@ public function getError() return $this->_error; } + /** + * Returns the LogoutRequest IssueInstant. + * + * @return string + */ + public function getIssueInstant() + { + return $this->_issueInstant; + } + /** * Returns the XML that will be sent as part of the request * or that was received at the SP diff --git a/tests/src/OneLogin/Saml2/AuthnRequestTest.php b/tests/src/OneLogin/Saml2/AuthnRequestTest.php index 7d1ab056..cfb130a6 100644 --- a/tests/src/OneLogin/Saml2/AuthnRequestTest.php +++ b/tests/src/OneLogin/Saml2/AuthnRequestTest.php @@ -353,4 +353,21 @@ public function testGetXML() $xml = $authnRequest->getXML(); $this->assertRegExp('#^getIssueInstant(); + $this->assertNotNull($issueInstant); + } } diff --git a/tests/src/OneLogin/Saml2/LogoutRequestTest.php b/tests/src/OneLogin/Saml2/LogoutRequestTest.php index 211f41ed..6c11d90e 100644 --- a/tests/src/OneLogin/Saml2/LogoutRequestTest.php +++ b/tests/src/OneLogin/Saml2/LogoutRequestTest.php @@ -893,4 +893,21 @@ public function testGetIDException() $xml = $logoutRequest->getXML(); $id1 = OneLogin_Saml2_LogoutRequest::getID($xml.''); } + + /** + * Tests that we can get the IssueInstant attribute + * + * @covers OneLogin_Saml2_LogoutRequest::getIssueInstant() + */ + public function testGetIssueInstant() + { + $settingsDir = TEST_ROOT .'/settings/'; + include $settingsDir.'settings1.php'; + + $settings = new OneLogin_Saml2_Settings($settingsInfo); + $logoutRequest = new OneLogin_Saml2_LogoutRequest($settings); + + $issueInstant = $logoutRequest->getIssueInstant(); + $this->assertNotNull($issueInstant); + } } From fb67560d9c9bbf831f9869bc53fdef5e029b8555 Mon Sep 17 00:00:00 2001 From: Davide Porrovecchio Date: Sat, 5 Oct 2019 12:25:08 +0200 Subject: [PATCH 3/4] Add IssueInstant check to responses --- lib/Saml2/Error.php | 1 + lib/Saml2/LogoutResponse.php | 15 ++++++- lib/Saml2/Response.php | 15 ++++++- .../src/OneLogin/Saml2/LogoutResponseTest.php | 42 +++++++++++++++++++ tests/src/OneLogin/Saml2/ResponseTest.php | 41 ++++++++++++++++++ 5 files changed, 112 insertions(+), 2 deletions(-) diff --git a/lib/Saml2/Error.php b/lib/Saml2/Error.php index a3d9bd40..30d9b504 100644 --- a/lib/Saml2/Error.php +++ b/lib/Saml2/Error.php @@ -100,6 +100,7 @@ class OneLogin_Saml2_ValidationError extends Exception const NOT_SUPPORTED = 46; const KEY_ALGORITHM_ERROR = 47; const MISSING_ENCRYPTED_ELEMENT = 48; + const INVALID_ISSUEINSTANT = 49; /** diff --git a/lib/Saml2/LogoutResponse.php b/lib/Saml2/LogoutResponse.php index 20f582a1..50773f09 100644 --- a/lib/Saml2/LogoutResponse.php +++ b/lib/Saml2/LogoutResponse.php @@ -116,7 +116,7 @@ public function getStatus() * * @return bool Returns if the SAML LogoutResponse is or not valid */ - public function isValid($requestId = null, $retrieveParametersFromServer = false) + public function isValid($requestId = null, $retrieveParametersFromServer = false, $requestIssueInstant = null) { $this->_error = null; try { @@ -147,6 +147,19 @@ public function isValid($requestId = null, $retrieveParametersFromServer = false } } + // Check if the IssueInstant of the Logout Response is later than the one of the Logout Request if provided (considering a configured clock skew) + if (isset($requestIssueInstant)) { + $issueInstant = $this->document->documentElement->getAttribute('IssueInstant'); + $issueInstantTime = OneLogin_Saml2_Utils::parseSAML2Time($issueInstant); + $requestIssueInstantTime = OneLogin_Saml2_Utils::parseSAML2Time($requestIssueInstant); + if ($requestIssueInstantTime > $issueInstantTime + $security['clockSkewTolerance']) { + throw new OneLogin_Saml2_ValidationError( + "The IssueInstant of the Logout Response: $issueInstant is not equal or later than the IssueInstant of the Logout Request: $requestIssueInstant (considering the configured clock skew)", + OneLogin_Saml2_ValidationError::INVALID_ISSUEINSTANT + ); + } + } + // Check issuer $issuer = $this->getIssuer(); if (!empty($issuer) && $issuer != $idPEntityId) { diff --git a/lib/Saml2/Response.php b/lib/Saml2/Response.php index 55e480d6..aba0126d 100644 --- a/lib/Saml2/Response.php +++ b/lib/Saml2/Response.php @@ -96,7 +96,7 @@ public function __construct(OneLogin_Saml2_Settings $settings, $response) * * @return bool Validate the document */ - public function isValid($requestId = null) + public function isValid($requestId = null, $requestIssueInstant = null) { $this->_error = null; try { @@ -178,6 +178,19 @@ public function isValid($requestId = null) ); } + // Check if the IssueInstant of the Response is not earlier than the one of the AuthNRequest if provided (considering a configured clock skew) + if (isset($requestIssueInstant)) { + $issueInstant = $this->document->documentElement->getAttribute('IssueInstant'); + $issueInstantTime = OneLogin_Saml2_Utils::parseSAML2Time($issueInstant); + $requestIssueInstantTime = OneLogin_Saml2_Utils::parseSAML2Time($requestIssueInstant); + if ($requestIssueInstantTime > $issueInstantTime + $security['clockSkewTolerance']) { + throw new OneLogin_Saml2_ValidationError( + "The IssueInstant of the Response: $issueInstant is not equal or later than the IssueInstant of the Request: $requestIssueInstant (considering the configured clock skew)", + OneLogin_Saml2_ValidationError::INVALID_ISSUEINSTANT + ); + } + } + if (!$this->encrypted && $security['wantAssertionsEncrypted']) { throw new OneLogin_Saml2_ValidationError( "The assertion of the Response is not encrypted and the SP requires it", diff --git a/tests/src/OneLogin/Saml2/LogoutResponseTest.php b/tests/src/OneLogin/Saml2/LogoutResponseTest.php index 5cc5d45b..5b5eeb7e 100644 --- a/tests/src/OneLogin/Saml2/LogoutResponseTest.php +++ b/tests/src/OneLogin/Saml2/LogoutResponseTest.php @@ -145,6 +145,48 @@ public function testIsInValidRequestId() $this->assertContains('The InResponseTo of the Logout Response:', $response2->getError()); } + /** + * Tests the isValid method of the OneLogin_Saml2_LogoutResponse class + * Case invalid IssueInstant + * + * @covers OneLogin_Saml2_LogoutResponse::isValid + */ + public function testIsInvalidIssueInstant() + { + $_SERVER['HTTP_HOST'] = 'stuff.com'; + $_SERVER['HTTPS'] = 'off'; + $_SERVER['REQUEST_URI'] = '/endpoints/endpoints/sls.php'; + + $message = file_get_contents(TEST_ROOT . '/data/logout_responses/logout_response.xml.base64'); + + // Not strict, always valid + $response = new OneLogin_Saml2_LogoutResponse($this->_settings, $message); + $this->assertTrue($response->isValid(null, false, '2013-12-10T04:39:31Z')); + $this->assertTrue($response->isValid(null, false, '2013-12-10T04:42:31Z')); + + // Strict, valid/invalid with 0 seconds (default) of clock skew tolerance + $this->_settings->setStrict(true); + $response2 = new OneLogin_Saml2_LogoutResponse($this->_settings, $message); + $this->assertTrue($response2->isValid(null, false, '2013-12-10T04:39:30Z')); + $this->assertTrue($response2->isValid(null, false, '2013-12-10T04:39:31Z')); + $this->assertFalse($response2->isValid(null, false, '2013-12-10T04:39:32Z')); + $this->assertContains('The IssueInstant of the Logout Response', $response2->getError()); + + // Strict, valid/invalid with 180 seconds of configured clock skew tolerance + include TEST_ROOT .'/settings/settings1.php'; + $settingsInfo['strict'] = true; + $settingsInfo['security']['clockSkewTolerance'] = 180; + $settings = new OneLogin_Saml2_Settings($settingsInfo); + $response3 = new OneLogin_Saml2_LogoutResponse($settings, $message); + $this->assertTrue($response3->isValid(null, false, '2013-12-10T04:42:31Z')); + $this->assertFalse($response3->isValid(null, false, '2013-12-10T04:42:32Z')); + $this->assertContains('The IssueInstant of the Logout Response', $response3->getError()); + + unset($_SERVER['HTTP_HOST']); + unset($_SERVER['HTTPS']); + unset($_SERVER['REQUEST_URI']); + } + /** * Tests the isValid method of the OneLogin_Saml2_LogoutResponse * Case invalid Issuer diff --git a/tests/src/OneLogin/Saml2/ResponseTest.php b/tests/src/OneLogin/Saml2/ResponseTest.php index 54097a42..1ab3b513 100644 --- a/tests/src/OneLogin/Saml2/ResponseTest.php +++ b/tests/src/OneLogin/Saml2/ResponseTest.php @@ -1280,6 +1280,47 @@ public function testIsInValidRequestId() $this->assertContains('No Signature found. SAML Response rejected', $response2->getError()); } + /** + * Tests the isValid method of the OneLogin_Saml2_Response class + * Case invalid IssueInstant + * + * @covers OneLogin_Saml2_Response::isValid + */ + public function testIsInvalidIssueInstant() + { + $_SERVER['HTTP_HOST'] = 'pitbulk.no-ip.org'; + $_SERVER['HTTPS'] = 'https'; + $_SERVER['REQUEST_URI'] = '/newonelogin/demo1/index.php?acs'; + + $xml = file_get_contents(TEST_ROOT . '/data/responses/valid_response.xml.base64'); + + // Not strict, always valid + $response = new OneLogin_Saml2_Response($this->_settings, $xml); + $this->assertTrue($response->isValid(null, '2014-02-19T01:37:01Z')); + $this->assertTrue($response->isValid(null, '2014-02-19T01:39:01Z')); + + // Strict, valid/invalid with 0 seconds (default) of clock skew tolerance + $this->_settings->setStrict(true); + $response2 = new OneLogin_Saml2_Response($this->_settings, $xml); + $this->assertTrue($response2->isValid(null, '2014-02-19T01:37:00Z')); + $this->assertTrue($response2->isValid(null, '2014-02-19T01:37:01Z')); + $this->assertFalse($response2->isValid(null, '2014-02-19T01:37:02Z')); + $this->assertContains('The IssueInstant of the Response', $response2->getError()); + + // Strict, valid/invalid with 180 seconds of configured clock skew tolerance + include TEST_ROOT .'/settings/settings1.php'; + $settingsInfo['strict'] = true; + $settingsInfo['security']['clockSkewTolerance'] = 180; + $settings = new OneLogin_Saml2_Settings($settingsInfo); + $response3 = new OneLogin_Saml2_Response($settings, $xml); + $this->assertTrue($response3->isValid(null, '2014-02-19T01:40:01Z')); + $this->assertFalse($response3->isValid(null, '2014-02-19T01:40:02Z')); + $this->assertContains('The IssueInstant of the Response', $response3->getError()); + + unset($_SERVER['HTTP_HOST']); + unset($_SERVER['HTTPS']); + unset($_SERVER['REQUEST_URI']); + } /** * Tests the isValid method of the OneLogin_Saml2_Response class From c06041b99cb6f90140f89cca8b85e50a22fb59c7 Mon Sep 17 00:00:00 2001 From: Davide Porrovecchio Date: Sat, 5 Oct 2019 12:39:52 +0200 Subject: [PATCH 4/4] Add support to IssueInstant attribute check --- README.md | 40 +++++++++++++++++++++++++-- demo1/index.php | 32 +++++++++++++++++++-- lib/Saml2/Auth.php | 27 +++++++++++++++--- tests/src/OneLogin/Saml2/AuthTest.php | 16 +++++++++++ 4 files changed, 106 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 12289672..7e0b80e2 100644 --- a/README.md +++ b/README.md @@ -675,6 +675,17 @@ header('Location: ' . $ssoBuiltUrl); exit(); ``` +If a check on the future SAMLResponse IssueInstant and the AuthNRequest IssueInstant to be sent is required, that AuthNRequest IssueInstant must to be extracted and saved. + +```php +$ssoBuiltUrl = $auth->login(null, array(), false, false, true); +$_SESSION['AuthNRequestIssueInstant'] = $auth->getLastRequestIssueInstant(); +header('Pragma: no-cache'); +header('Cache-Control: no-cache, must-revalidate'); +header('Location: ' . $ssoBuiltUrl); +exit(); +```` + #### The SP Endpoints #### Related to the SP there are three important views: The metadata view, the ACS view and the SLS view. The toolkit @@ -743,8 +754,15 @@ if (isset($_SESSION) && isset($_SESSION['AuthNRequestID'])) { $requestID = null; } -$auth->processResponse($requestID); +if (isset($_SESSION) && isset($_SESSION['AuthNRequestIssueInstant'])) { + $requestIssueInstant = $_SESSION['AuthNRequestIssueInstant']; +} else { + $requestIssueInstant = null; +} + +$auth->processResponse($requestID, $requestIssueInstant); unset($_SESSION['AuthNRequestID']); +unset($_SESSION['AuthNRequestIssueInstant']); $errors = $auth->getErrors(); @@ -884,8 +902,13 @@ if (isset($_SESSION) && isset($_SESSION['LogoutRequestID'])) { } else { $requestID = null; } +if (isset($_SESSION) && isset($_SESSION['LogoutRequestIssueInstant'])) { + $requestIssueInstant = $_SESSION['LogoutRequestIssueInstant']; +} else { + $requestIssueInstant = null; +} -$auth->processSLO(false, $requestID); +$auth->processSLO(false, $requestID, false, null, false, $requestIssueInstant); $errors = $auth->getErrors(); @@ -1058,6 +1081,17 @@ header('Location: ' . $sloBuiltUrl); exit(); ``` +If a check on the future LogoutResponse IssueInstant and the LogoutRequest IssueInstant to be sent is required, that LogoutRequest IssueInstant must to be extracted and saved. + +```php +$sloBuiltUrl = $auth->logout(null, $paramters, $nameId, $sessionIndex, true); +$_SESSION['LogoutRequestIssueInstant'] = $auth->getLastRequestIssueInstant(); +header('Pragma: no-cache'); +header('Cache-Control: no-cache, must-revalidate'); +header('Location: ' . $sloBuiltUrl); +exit(); +```` + #### Example of a view that initiates the SSO request and handles the response (is the acs target) #### We can code a unique file that initiates the SSO process, handle the response, get the attributes, initiate @@ -1310,12 +1344,12 @@ Main class of OneLogin PHP Toolkit * `getErrors` - Returns if there were any error * `getSSOurl` - Gets the SSO url. * `getSLOurl` - Gets the SLO url. - * `getLastRequestID` - The ID of the last Request SAML message generated. * `buildRequestSignature` - Generates the Signature for a SAML Request * `buildResponseSignature` - Generates the Signature for a SAML Response * `getSettings` - Returns the settings info * `setStrict` - Set the strict mode active/disable * `getLastRequestID` - Gets the ID of the last AuthNRequest or LogoutRequest generated by the Service Provider. + * `getLastRequestIssueInstant` - Gets the IssueInstant attribute of the last AuthNRequest or LogoutRequest generated by the Service Provider. * `getLastRequestXML` - Returns the most recently-constructed/processed XML SAML request (AuthNRequest, LogoutRequest) * `getLastResponseXML` - Returns the most recently-constructed/processed XML SAML response (SAMLResponse, LogoutResponse). If the SAMLResponse had an encrypted assertion, decrypts it. diff --git a/demo1/index.php b/demo1/index.php index 2400451f..ac93f58b 100644 --- a/demo1/index.php +++ b/demo1/index.php @@ -22,6 +22,14 @@ # header('Location: ' . $ssoBuiltUrl); # exit(); + # If AuthNRequest IssueInstant need to be saved in order to later validate it, do instead + # $ssoBuiltUrl = $auth->login(null, array(), false, false, true); + # $_SESSION['AuthNRequestIssueInstant'] = $auth->getLastRequestIssueInstant(); + # header('Pragma: no-cache'); + # header('Cache-Control: no-cache, must-revalidate'); + # header('Location: ' . $ssoBuiltUrl); + # exit(); + } else if (isset($_GET['sso2'])) { $returnTo = $spBaseUrl.'/demo1/attrs.php'; $auth->login($returnTo); @@ -58,6 +66,14 @@ # header('Location: ' . $sloBuiltUrl); # exit(); + # If LogoutRequest IssueInstant need to be saved in order to later validate it, do instead + # $sloBuiltUrl = $auth->logout(null, $paramters, $nameId, $sessionIndex, true); + # $_SESSION['LogoutRequestIssueInstant'] = $auth->getLastRequestIssueInstant(); + # header('Pragma: no-cache'); + # header('Cache-Control: no-cache, must-revalidate'); + # header('Location: ' . $sloBuiltUrl); + # exit(); + } else if (isset($_GET['acs'])) { if (isset($_SESSION) && isset($_SESSION['AuthNRequestID'])) { $requestID = $_SESSION['AuthNRequestID']; @@ -65,7 +81,13 @@ $requestID = null; } - $auth->processResponse($requestID); + if (isset($_SESSION) && isset($_SESSION['AuthNRequestIssueInstant'])) { + $requestIssueInstant = $_SESSION['AuthNRequestIssueInstant']; + } else { + $requestIssueInstant = null; + } + + $auth->processResponse($requestID, $requestIssueInstant); $errors = $auth->getErrors(); @@ -95,7 +117,13 @@ $requestID = null; } - $auth->processSLO(false, $requestID); + if (isset($_SESSION) && isset($_SESSION['LogoutRequestIssueInstant'])) { + $requestIssueInstant = $_SESSION['LogoutRequestIssueInstant']; + } else { + $requestIssueInstant = null; + } + + $auth->processSLO(false, $requestID, false, null, false, $requestIssueInstant); $errors = $auth->getErrors(); if (empty($errors)) { echo '

Sucessfully logged out

'; diff --git a/lib/Saml2/Auth.php b/lib/Saml2/Auth.php index 591ffd37..4c2f2de6 100644 --- a/lib/Saml2/Auth.php +++ b/lib/Saml2/Auth.php @@ -123,6 +123,13 @@ class OneLogin_Saml2_Auth */ private $_lastRequestID; + /** + * Last AuthNRequest or LogoutRequest IssueInstant generated by this Service Provider + * + * @var string + */ + private $_lastRequestIssueInstant; + /** * The most recently-constructed/processed XML SAML request * (AuthNRequest, LogoutRequest) @@ -189,7 +196,7 @@ public function setStrict($value) * @throws OneLogin_Saml2_Error * @throws OneLogin_Saml2_ValidationError */ - public function processResponse($requestId = null) + public function processResponse($requestId = null, $requestIssueInstant = null) { $this->_errors = array(); $this->_errorReason = null; @@ -198,7 +205,7 @@ public function processResponse($requestId = null) $response = new OneLogin_Saml2_Response($this->_settings, $_POST['SAMLResponse']); $this->_lastResponse = $response->getXMLDocument(); - if ($response->isValid($requestId)) { + if ($response->isValid($requestId, $requestIssueInstant)) { $this->_attributes = $response->getAttributes(); $this->_attributesWithFriendlyName = $response->getAttributesWithFriendlyName(); $this->_nameid = $response->getNameId(); @@ -237,14 +244,14 @@ public function processResponse($requestId = null) * * @throws OneLogin_Saml2_Error */ - public function processSLO($keepLocalSession = false, $requestId = null, $retrieveParametersFromServer = false, $cbDeleteSession = null, $stay = false) + public function processSLO($keepLocalSession = false, $requestId = null, $retrieveParametersFromServer = false, $cbDeleteSession = null, $stay = false, $requestIssueInstant = null) { $this->_errors = array(); $this->_errorReason = null; if (isset($_GET['SAMLResponse'])) { $logoutResponse = new OneLogin_Saml2_LogoutResponse($this->_settings, $_GET['SAMLResponse']); $this->_lastResponse = $logoutResponse->getXML(); - if (!$logoutResponse->isValid($requestId, $retrieveParametersFromServer)) { + if (!$logoutResponse->isValid($requestId, $retrieveParametersFromServer, $requestIssueInstant)) { $this->_errors[] = 'invalid_logout_response'; $this->_errorReason = $logoutResponse->getError(); } else if ($logoutResponse->getStatus() !== OneLogin_Saml2_Constants::STATUS_SUCCESS) { @@ -497,6 +504,7 @@ public function login($returnTo = null, $parameters = array(), $forceAuthn = fal $this->_lastRequest = $authnRequest->getXML(); $this->_lastRequestID = $authnRequest->getId(); + $this->_lastRequestIssueInstant = $authnRequest->getIssueInstant(); $samlRequest = $authnRequest->getRequest(); $parameters['SAMLRequest'] = $samlRequest; @@ -554,6 +562,7 @@ public function logout($returnTo = null, $parameters = array(), $nameId = null, $this->_lastRequest = $logoutRequest->getXML(); $this->_lastRequestID = $logoutRequest->id; + $this->_lastRequestIssueInstant = $logoutRequest->getIssueInstant(); $samlRequest = $logoutRequest->getRequest(); @@ -624,6 +633,16 @@ public function getLastRequestID() return $this->_lastRequestID; } + /** + * Gets the IssueInstant of the last AuthNRequest or LogoutRequest generated by the Service Provider. + * + * @return string The IssueInstant of the Request SAML message. + */ + public function getLastRequestIssueInstant() + { + return $this->_lastRequestIssueInstant; + } + /** * Generates the Signature for a SAML Request * diff --git a/tests/src/OneLogin/Saml2/AuthTest.php b/tests/src/OneLogin/Saml2/AuthTest.php index 94e3872f..d9f464ef 100644 --- a/tests/src/OneLogin/Saml2/AuthTest.php +++ b/tests/src/OneLogin/Saml2/AuthTest.php @@ -57,6 +57,22 @@ public function testGetLastRequestID() $this->assertNotEquals($id1, $id2); } + /** + * Tests the getLastRequestIssueInstant method of the OneLogin_Saml2_Auth class + * + * @covers OneLogin_Saml2_Auth::getLastRequestIssueInstant + */ + public function testGetLastRequestIssueInstant() + { + $targetSSOURL = $this->_auth->login(null, array(), false, false, true, false, false); + $issueInstant1 = $this->_auth->getLastRequestIssueInstant(); + $this->assertNotNull($issueInstant1); + + $targetSLOURL = $this->_auth->logout(null, array(), null, null, true, null, null); + $issueInstant2 = $this->_auth->getLastRequestIssueInstant(); + $this->assertNotNull($issueInstant2); + } + /** * Tests the getSSOurl method of the OneLogin_Saml2_Auth class *