Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IssueInstant check #397

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down
4 changes: 4 additions & 0 deletions advanced_settings_example.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
32 changes: 30 additions & 2 deletions demo1/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -58,14 +66,28 @@
# 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'];
} else {
$requestID = null;
}

$auth->processResponse($requestID);
if (isset($_SESSION) && isset($_SESSION['AuthNRequestIssueInstant'])) {
$requestIssueInstant = $_SESSION['AuthNRequestIssueInstant'];
} else {
$requestIssueInstant = null;
}

$auth->processResponse($requestID, $requestIssueInstant);

$errors = $auth->getErrors();

Expand Down Expand Up @@ -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 '<p>Sucessfully logged out</p>';
Expand Down
27 changes: 23 additions & 4 deletions lib/Saml2/Auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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
*
Expand Down
17 changes: 17 additions & 0 deletions lib/Saml2/AuthnRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ class OneLogin_Saml2_AuthnRequest
*/
private $_id;

/**
* SAML AuthNRequest IssueInstant.
* @var string
*/
private $_issueInstant;

/**
* Constructs the AuthnRequest object.
*
Expand Down Expand Up @@ -146,6 +152,7 @@ public function __construct(OneLogin_Saml2_Settings $settings, $forceAuthn = fal

$this->_id = $id;
$this->_authnRequest = $request;
$this->_issueInstant = $issueInstant;
}

/**
Expand Down Expand Up @@ -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
*
Expand Down
1 change: 1 addition & 0 deletions lib/Saml2/Error.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;


/**
Expand Down
17 changes: 17 additions & 0 deletions lib/Saml2/LogoutRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ class OneLogin_Saml2_LogoutRequest
*/
private $_error;

/**
* SAML LogoutRequest IssueInstant.
* @var string
*/
private $_issueInstant;

/**
* Constructs the Logout Request object.
*
Expand Down Expand Up @@ -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']) {
Expand Down Expand Up @@ -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
Expand Down
15 changes: 14 additions & 1 deletion lib/Saml2/LogoutResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down
15 changes: 14 additions & 1 deletion lib/Saml2/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions lib/Saml2/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading