mirror of
https://github.com/netzbegruenung/user_saml.git
synced 2024-05-04 01:53:42 +02:00
04c7b4187a
Signed-off-by: Lukas Reschke <lukas@statuscode.ch>
332 lines
11 KiB
PHP
332 lines
11 KiB
PHP
<?php
|
|
|
|
/**
|
|
* SAML 2 Logout Response
|
|
*
|
|
*/
|
|
class OneLogin_Saml2_LogoutResponse
|
|
{
|
|
/**
|
|
* Contains the ID of the Logout Response
|
|
* @var string
|
|
*/
|
|
public $id;
|
|
|
|
/**
|
|
* Object that represents the setting info
|
|
* @var OneLogin_Saml2_Settings
|
|
*/
|
|
protected $_settings;
|
|
|
|
/**
|
|
* The decoded, unprocessed XML response provided to the constructor.
|
|
* @var string
|
|
*/
|
|
protected $_logoutResponse;
|
|
|
|
/**
|
|
* A DOMDocument class loaded from the SAML LogoutResponse.
|
|
* @var DomDocument
|
|
*/
|
|
public $document;
|
|
|
|
/**
|
|
* After execute a validation process, if it fails, this var contains the cause
|
|
* @var string
|
|
*/
|
|
private $_error;
|
|
|
|
/**
|
|
* Constructs a Logout Response object (Initialize params from settings and if provided
|
|
* load the Logout Response.
|
|
*
|
|
* @param OneLogin_Saml2_Settings $settings Settings.
|
|
* @param string|null $response An UUEncoded SAML Logout response from the IdP.
|
|
*/
|
|
public function __construct(OneLogin_Saml2_Settings $settings, $response = null)
|
|
{
|
|
$this->_settings = $settings;
|
|
|
|
$baseURL = $this->_settings->getBaseURL();
|
|
if (!empty($baseURL)) {
|
|
OneLogin_Saml2_Utils::setBaseURL($baseURL);
|
|
}
|
|
|
|
if ($response) {
|
|
$decoded = base64_decode($response);
|
|
$inflated = @gzinflate($decoded);
|
|
if ($inflated != false) {
|
|
$this->_logoutResponse = $inflated;
|
|
} else {
|
|
$this->_logoutResponse = $decoded;
|
|
}
|
|
$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');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the Issuer of the Logout Response.
|
|
*
|
|
* @return string|null $issuer The Issuer
|
|
*/
|
|
public function getIssuer()
|
|
{
|
|
$issuer = null;
|
|
$issuerNodes = $this->_query('/samlp:LogoutResponse/saml:Issuer');
|
|
if ($issuerNodes->length == 1) {
|
|
$issuer = $issuerNodes->item(0)->textContent;
|
|
}
|
|
return $issuer;
|
|
}
|
|
|
|
/**
|
|
* Gets the Status of the Logout Response.
|
|
*
|
|
* @return string The Status
|
|
*/
|
|
public function getStatus()
|
|
{
|
|
$entries = $this->_query('/samlp:LogoutResponse/samlp:Status/samlp:StatusCode');
|
|
if ($entries->length != 1) {
|
|
return null;
|
|
}
|
|
$status = $entries->item(0)->getAttribute('Value');
|
|
return $status;
|
|
}
|
|
|
|
/**
|
|
* Determines if the SAML LogoutResponse is valid
|
|
*
|
|
* @param string|null $requestId The ID of the LogoutRequest sent by this SP to the IdP
|
|
* @param bool $retrieveParametersFromServer
|
|
*
|
|
* @return bool Returns if the SAML LogoutResponse is or not valid
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function isValid($requestId = null, $retrieveParametersFromServer = false)
|
|
{
|
|
$this->_error = null;
|
|
try {
|
|
$idpData = $this->_settings->getIdPData();
|
|
$idPEntityId = $idpData['entityId'];
|
|
|
|
if ($this->_settings->isStrict()) {
|
|
$security = $this->_settings->getSecurityData();
|
|
|
|
if ($security['wantXMLValidation']) {
|
|
$res = OneLogin_Saml2_Utils::validateXML($this->document, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive());
|
|
if (!$res instanceof DOMDocument) {
|
|
throw new OneLogin_Saml2_ValidationError(
|
|
"Invalid SAML Logout Response. Not match the saml-schema-protocol-2.0.xsd",
|
|
OneLogin_Saml2_ValidationError::INVALID_XML_FORMAT
|
|
);
|
|
}
|
|
}
|
|
|
|
// Check if the InResponseTo of the Logout Response matchs the ID of the Logout Request (requestId) if provided
|
|
if (isset($requestId) && $this->document->documentElement->hasAttribute('InResponseTo')) {
|
|
$inResponseTo = $this->document->documentElement->getAttribute('InResponseTo');
|
|
if ($requestId != $inResponseTo) {
|
|
throw new OneLogin_Saml2_ValidationError(
|
|
"The InResponseTo of the Logout Response: $inResponseTo, does not match the ID of the Logout request sent by the SP: $requestId",
|
|
OneLogin_Saml2_ValidationError::WRONG_INRESPONSETO
|
|
);
|
|
}
|
|
}
|
|
|
|
// Check issuer
|
|
$issuer = $this->getIssuer();
|
|
if (!empty($issuer) && $issuer != $idPEntityId) {
|
|
throw new OneLogin_Saml2_ValidationError(
|
|
"Invalid issuer in the Logout Response",
|
|
OneLogin_Saml2_ValidationError::WRONG_ISSUER
|
|
);
|
|
}
|
|
|
|
$currentURL = OneLogin_Saml2_Utils::getSelfRoutedURLNoQuery();
|
|
|
|
// Check destination
|
|
if ($this->document->documentElement->hasAttribute('Destination')) {
|
|
$destination = $this->document->documentElement->getAttribute('Destination');
|
|
if (!empty($destination)) {
|
|
if (strpos($destination, $currentURL) === false) {
|
|
throw new OneLogin_Saml2_ValidationError(
|
|
"The LogoutResponse was received at $currentURL instead of $destination",
|
|
OneLogin_Saml2_ValidationError::WRONG_DESTINATION
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($security['wantMessagesSigned']) {
|
|
if (!isset($_GET['Signature'])) {
|
|
throw new OneLogin_Saml2_ValidationError(
|
|
"The Message of the Logout Response is not signed and the SP requires it",
|
|
OneLogin_Saml2_ValidationError::NO_SIGNED_MESSAGE
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isset($_GET['Signature'])) {
|
|
if (!isset($_GET['SigAlg'])) {
|
|
$signAlg = XMLSecurityKey::RSA_SHA1;
|
|
} else {
|
|
$signAlg = $_GET['SigAlg'];
|
|
}
|
|
|
|
if ($retrieveParametersFromServer) {
|
|
$signedQuery = 'SAMLResponse='.OneLogin_Saml2_Utils::extractOriginalQueryParam('SAMLResponse');
|
|
if (isset($_GET['RelayState'])) {
|
|
$signedQuery .= '&RelayState='.OneLogin_Saml2_Utils::extractOriginalQueryParam('RelayState');
|
|
}
|
|
$signedQuery .= '&SigAlg='.OneLogin_Saml2_Utils::extractOriginalQueryParam('SigAlg');
|
|
} else {
|
|
$signedQuery = 'SAMLResponse='.urlencode($_GET['SAMLResponse']);
|
|
if (isset($_GET['RelayState'])) {
|
|
$signedQuery .= '&RelayState='.urlencode($_GET['RelayState']);
|
|
}
|
|
$signedQuery .= '&SigAlg='.urlencode($signAlg);
|
|
}
|
|
|
|
if (!isset($idpData['x509cert']) || empty($idpData['x509cert'])) {
|
|
throw new OneLogin_Saml2_Error(
|
|
"In order to validate the sign on the Logout Response, the x509cert of the IdP is required",
|
|
OneLogin_Saml2_Error::CERT_NOT_FOUND
|
|
);
|
|
}
|
|
$cert = $idpData['x509cert'];
|
|
|
|
$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'public'));
|
|
$objKey->loadKey($cert, false, true);
|
|
|
|
if ($signAlg != XMLSecurityKey::RSA_SHA1) {
|
|
try {
|
|
$objKey = OneLogin_Saml2_Utils::castKey($objKey, $signAlg, 'public');
|
|
} catch (Exception $e) {
|
|
throw new OneLogin_Saml2_ValidationError(
|
|
"Invalid signAlg in the recieved Logout Response",
|
|
OneLogin_Saml2_ValidationError::INVALID_SIGNATURE
|
|
);
|
|
}
|
|
}
|
|
|
|
if ($objKey->verifySignature($signedQuery, base64_decode($_GET['Signature'])) !== 1) {
|
|
throw new OneLogin_Saml2_ValidationError(
|
|
"Signature validation failed. Logout Response rejected",
|
|
OneLogin_Saml2_ValidationError::INVALID_SIGNATURE
|
|
);
|
|
}
|
|
}
|
|
return true;
|
|
} catch (Exception $e) {
|
|
$this->_error = $e->getMessage();
|
|
$debug = $this->_settings->isDebugActive();
|
|
if ($debug) {
|
|
echo $this->_error;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extracts a node from the DOMDocument (Logout Response Menssage)
|
|
*
|
|
* @param string $query Xpath Expresion
|
|
*
|
|
* @return DOMNodeList The queried node
|
|
*/
|
|
private function _query($query)
|
|
{
|
|
return OneLogin_Saml2_Utils::query($this->document, $query);
|
|
|
|
}
|
|
|
|
/**
|
|
* Generates a Logout Response object.
|
|
*
|
|
* @param string $inResponseTo InResponseTo value for the Logout Response.
|
|
*/
|
|
public function build($inResponseTo)
|
|
{
|
|
|
|
$spData = $this->_settings->getSPData();
|
|
$idpData = $this->_settings->getIdPData();
|
|
|
|
$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="{$this->id}"
|
|
Version="2.0"
|
|
IssueInstant="{$issueInstant}"
|
|
Destination="{$idpData['singleLogoutService']['url']}"
|
|
InResponseTo="{$inResponseTo}"
|
|
>
|
|
<saml:Issuer>{$spData['entityId']}</saml:Issuer>
|
|
<samlp:Status>
|
|
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
|
|
</samlp:Status>
|
|
</samlp:LogoutResponse>
|
|
LOGOUTRESPONSE;
|
|
$this->_logoutResponse = $logoutResponse;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
$subject = $this->_logoutResponse;
|
|
|
|
if (is_null($deflate)) {
|
|
$deflate = $this->_settings->shouldCompressResponses();
|
|
}
|
|
|
|
if ($deflate) {
|
|
$subject = gzdeflate($this->_logoutResponse);
|
|
}
|
|
return base64_encode($subject);
|
|
}
|
|
|
|
/* After execute a validation process, if fails this method returns the cause.
|
|
*
|
|
* @return string Cause
|
|
*/
|
|
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
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getXML()
|
|
{
|
|
return $this->_logoutResponse;
|
|
}
|
|
}
|