Skip to content

Commit

Permalink
SAML-Toolkits#193 Be able to get at the auth object the last processe…
Browse files Browse the repository at this point in the history
…d ID. Clean code. Reset errorReason attribute of the auth object after each Process method
  • Loading branch information
pitbulk committed Mar 1, 2017
1 parent e1d6b8d commit 808ce6d
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 31 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,23 @@ Also a developer can use setSelfProtocol, setSelfHost, setSelfPort and getBaseUR

At the settings the developer will be able to set a 'baseurl' parameter that automatically will use setBaseURL to set values for setSelfProtocol, setSelfHost, setSelfPort and setBaseURLPath.


### Working behind load balancer ###

Is possible that asserting request URL and Destination attribute of SAML response fails when working behind load balancer with SSL offload.

You should be able to workaround this by configuring your server so that it is aware of the proxy and returns the original url when requested.

Or by using the method described on the previous section.


### Reply attacks ###

In order to avoid reply attacks, you can store the ID of the SAML messages already processed, to avoid processing them twice. Since the Messages expires and will be invalidated due that fact, you don't need to store those IDs longer than the time frame that you currently accepting.

Get the ID of the last processed message/assertion with the getLastMessageId/getLastAssertionId method of the Auth object.


### Main classes and methods ###

Described below are the main classes and methods that can be invoked.
Expand Down
66 changes: 60 additions & 6 deletions lib/Saml2/Auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*/
class OneLogin_Saml2_Auth
{

/**
* Settings data.
*
Expand Down Expand Up @@ -59,6 +58,28 @@ class OneLogin_Saml2_Auth
*/
private $_sessionExpiration;

/**
* The ID of the last message processed
*
* @var string
*/
private $_lastMessageId;

/**
* The ID of the last assertion processed
*
* @var string
*/
private $_lastAssertionId;

/**
* The NotOnOrAfter value of the valid SubjectConfirmationData
* node (if any) of the last assertion processed
*
* @var DateTime
*/
private $_lastAssertionNotOnOrAfter;

/**
* If any error.
*
Expand All @@ -82,7 +103,7 @@ class OneLogin_Saml2_Auth

/**
* The most recently-constructed/processed XML SAML request
* (AuthNRequest, LogoutRequest)
* (AuthNRequest, LogoutRequest)
*
* @var string
*/
Expand All @@ -91,7 +112,7 @@ class OneLogin_Saml2_Auth
/**
* The most recently-constructed/processed XML SAML response
* (SAMLResponse, LogoutResponse). If the SAMLResponse was
* encrypted, by default tries to return the decrypted XML
* encrypted, by default tries to return the decrypted XML
*
* @var string
*/
Expand Down Expand Up @@ -146,6 +167,7 @@ public function setStrict($value)
public function processResponse($requestId = null)
{
$this->_errors = array();
$this->_errorReason = null;
if (isset($_POST) && isset($_POST['SAMLResponse'])) {
// AuthnResponse -- HTTP_POST Binding
$response = new OneLogin_Saml2_Response($this->_settings, $_POST['SAMLResponse']);
Expand All @@ -158,6 +180,9 @@ public function processResponse($requestId = null)
$this->_authenticated = true;
$this->_sessionIndex = $response->getSessionIndex();
$this->_sessionExpiration = $response->getSessionNotOnOrAfter();
$this->_lastMessageId = $response->getId();
$this->_lastAssertionId = $response->getAssertionId();
$this->_lastAssertionNotOnOrAfter = $response->getAssertionNotOnOrAfter();
} else {
$this->_errors[] = 'invalid_response';
$this->_errorReason = $response->getError();
Expand All @@ -184,9 +209,10 @@ 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)
{
$this->_errors = array();
$this->_errorReason = null;
if (isset($_GET) && isset($_GET['SAMLResponse'])) {
$logoutResponse = new OneLogin_Saml2_LogoutResponse($this->_settings, $_GET['SAMLResponse']);
$this->_lastResponse = $logoutResponse->getXML();
Expand All @@ -196,6 +222,7 @@ public function processSLO($keepLocalSession = false, $requestId = null, $retrie
} else if ($logoutResponse->getStatus() !== OneLogin_Saml2_Constants::STATUS_SUCCESS) {
$this->_errors[] = 'logout_not_success';
} else {
$this->_lastMessageId = $logoutResponse->id;
if (!$keepLocalSession) {
if ($cbDeleteSession === null) {
OneLogin_Saml2_Utils::deleteLocalSession();
Expand All @@ -219,6 +246,7 @@ public function processSLO($keepLocalSession = false, $requestId = null, $retrie
}
}
$inResponseTo = $logoutRequest->id;
$this->_lastMessageId = $logoutRequest->id;
$responseBuilder = new OneLogin_Saml2_LogoutResponse($this->_settings);
$responseBuilder->build($inResponseTo);
$this->_lastResponse = $responseBuilder->getXML();
Expand Down Expand Up @@ -378,7 +406,7 @@ public function getAttribute($name)
*
* @return If $stay is True, it return a string with the SLO URL + LogoutRequest + parameters
*/
public function login($returnTo = null, $parameters = array(), $forceAuthn = false, $isPassive = false, $stay=false, $setNameIdPolicy = true)
public function login($returnTo = null, $parameters = array(), $forceAuthn = false, $isPassive = false, $stay = false, $setNameIdPolicy = true)
{
assert('is_array($parameters)');

Expand Down Expand Up @@ -419,7 +447,7 @@ public function login($returnTo = null, $parameters = array(), $forceAuthn = fal
*
* @throws OneLogin_Saml2_Error
*/
public function logout($returnTo = null, $parameters = array(), $nameId = null, $sessionIndex = null, $stay=false, $nameIdFormat = null)
public function logout($returnTo = null, $parameters = array(), $nameId = null, $sessionIndex = null, $stay = false, $nameIdFormat = null)
{
assert('is_array($parameters)');

Expand Down Expand Up @@ -583,6 +611,32 @@ public function buildResponseSignature($samlResponse, $relayState, $signAlgorith
return base64_encode($signature);
}

/**
* @return string The ID of the last message processed
*/
public function getLastMessageId()
{
return $this->_lastMessageId;
}

/**
* @return string The ID of the last assertion processed
*/
public function getLastAssertionId()
{
return $this->_lastAssertionId;
}

/**
* @return The NotOnOrAfter value of the valid
* SubjectConfirmationData node (if any)
* of the last assertion processed
*/
public function getLastAssertionNotOnOrAfter()
{
return $this->_lastAssertionNotOnOrAfter;
}

/**
* Returns the most recently-constructed/processed
* XML SAML request (AuthNRequest, LogoutRequest)
Expand Down
5 changes: 1 addition & 4 deletions lib/Saml2/LogoutRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*/
class OneLogin_Saml2_LogoutRequest
{

/**
* Contains the ID of the Logout Request
* @var string
Expand Down Expand Up @@ -50,7 +49,6 @@ public function __construct(OneLogin_Saml2_Settings $settings, $request = null,
}

if (!isset($request) || empty($request)) {

$spData = $this->_settings->getSPData();
$idpData = $this->_settings->getIdPData();
$security = $this->_settings->getSecurityData();
Expand Down Expand Up @@ -288,7 +286,7 @@ public static function getSessionIndexes($request)
*
* @return bool If the Logout Request is or not valid
*/
public function isValid($retrieveParametersFromServer=false)
public function isValid($retrieveParametersFromServer = false)
{
$this->_error = null;
try {
Expand Down Expand Up @@ -359,7 +357,6 @@ public function isValid($retrieveParametersFromServer=false)
}

if (isset($_GET['Signature'])) {

if (!isset($_GET['SigAlg'])) {
$signAlg = XMLSecurityKey::RSA_SHA1;
} else {
Expand Down
28 changes: 22 additions & 6 deletions lib/Saml2/LogoutResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
*/
class OneLogin_Saml2_LogoutResponse
{
/**
* Contains the ID of the Logout Response
* @var string
*/
public $id;

/**
* Object that represents the setting info
Expand Down Expand Up @@ -57,6 +62,10 @@ public function __construct(OneLogin_Saml2_Settings $settings, $response = null)
}
$this->document = new DOMDocument();
$this->document = OneLogin_Saml2_Utils::loadXML($this->document, $this->_logoutResponse);

if ($this->document->documentElement->hasAttribute('ID')) {
$this->id = $this->document->documentElement->getAttribute('ID');
}
}
}

Expand Down Expand Up @@ -100,11 +109,10 @@ public function getStatus()
*
* @throws Exception
*/
public function isValid($requestId = null, $retrieveParametersFromServer=false)
public function isValid($requestId = null, $retrieveParametersFromServer = false)
{
$this->_error = null;
try {

$idpData = $this->_settings->getIdPData();
$idPEntityId = $idpData['entityId'];

Expand Down Expand Up @@ -251,13 +259,13 @@ public function build($inResponseTo)
$spData = $this->_settings->getSPData();
$idpData = $this->_settings->getIdPData();

$id = OneLogin_Saml2_Utils::generateUniqueID();
$this->id = OneLogin_Saml2_Utils::generateUniqueID();
$issueInstant = OneLogin_Saml2_Utils::parseTime2SAML(time());

$logoutResponse = <<<LOGOUTRESPONSE
<samlp:LogoutResponse xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="{$id}"
ID="{$this->id}"
Version="2.0"
IssueInstant="{$issueInstant}"
Destination="{$idpData['singleLogoutService']['url']}"
Expand All @@ -274,9 +282,9 @@ public function build($inResponseTo)

/**
* Returns a Logout Response object.
*
*
* @param bool|null $deflate Whether or not we should 'gzdeflate' the response body before we return it.
*
*
* @return string Logout Response deflated and base64 encoded
*/
public function getResponse($deflate = null)
Expand All @@ -302,6 +310,14 @@ public function getError()
return $this->_error;
}

/**
* @return the ID of the Response
*/
public function getId()
{
return $this->id;
}

/**
* Returns the XML that will be sent as part of the response
* or that was received at the SP
Expand Down
50 changes: 50 additions & 0 deletions lib/Saml2/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ class OneLogin_Saml2_Response
*/
private $_error;

/**
* NotOnOrAfter value of a valid SubjectConfirmationData node
*
* @var DateTime
*/
private $_validSCDNotOnOrAfter;

/**
* Constructs the SAML Response object.
*
Expand Down Expand Up @@ -313,6 +320,11 @@ public function isValid($requestId = null)
continue;
}
}

// Save NotOnOrAfter value
if ($scnData->hasAttribute('NotOnOrAfter')) {
$this->_validSCDNotOnOrAfter = $noa;
}
$anySubjectConfirmation = true;
break;
}
Expand Down Expand Up @@ -389,6 +401,44 @@ public function isValid($requestId = null)
}
}

/**
* @return the ID of the Response
*/
public function getId()
{
$id = null;
if ($this->document->documentElement->hasAttribute('ID')) {
$id = $this->document->documentElement->getAttribute('ID');
}
return $id;
}

/**
* @return the ID of the assertion in the Response
*/
public function getAssertionId()
{
if (!$this->validateNumAssertions()) {
throw new IllegalArgumentException("SAML Response must contain 1 Assertion.");
}
$assertionNodes = $this->_queryAssertion("");
$id = null;
if ($assertionNodes->length == 1) {
if ($assertionNodes->item(0)->hasAttribute('ID')) {
$id = $assertionNodes->item(0)->getAttribute('ID');
}
}
return $id;
}

/**
* @return the NotOnOrAfter value of the valid SubjectConfirmationData * node if any
*/
public function getAssertionNotOnOrAfter()
{
return $this->_validSCDNotOnOrAfter;
}

/**
* Checks if the Status is success
*
Expand Down
Loading

0 comments on commit 808ce6d

Please sign in to comment.