public function isValid($requestId = null)
{
$this->_error = null;
try {
// Check SAML version
if ($this->document->documentElement->getAttribute('Version') != '2.0') {
throw new Exception('Unsupported SAML version');
}
if (!$this->document->documentElement->hasAttribute('ID')) {
throw new Exception('Missing ID attribute on SAML Response');
}
$status = $this->checkStatus();
$singleAssertion = $this->validateNumAssertions();
if (!$singleAssertion) {
throw new Exception('SAML Response must contain 1 assertion');
}
$idpData = $this->_settings->getIdPData();
$idPEntityId = $idpData['entityId'];
$spData = $this->_settings->getSPData();
$spEntityId = $spData['entityId'];
$signedElements = $this->processSignedElements();
$responseTag = '{' . OneLogin_Saml2_Constants::NS_SAMLP . '}Response';
$assertionTag = '{' . OneLogin_Saml2_Constants::NS_SAML . '}Assertion';
$hasSignedResponse = in_array($responseTag, $signedElements);
$hasSignedAssertion = in_array($assertionTag, $signedElements);
if ($this->_settings->isStrict()) {
$security = $this->_settings->getSecurityData();
if ($security['wantXMLValidation']) {
$errorXmlMsg = "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd";
$res = OneLogin_Saml2_Utils::validateXML($this->document, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive());
if (!$res instanceof DOMDocument) {
throw new Exception($errorXmlMsg);
}
# If encrypted, check also the decrypted document
if ($this->encrypted) {
$res = OneLogin_Saml2_Utils::validateXML($this->decryptedDocument, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive());
if (!$res instanceof DOMDocument) {
throw new Exception($errorXmlMsg);
}
}
}
$currentURL = OneLogin_Saml2_Utils::getSelfRoutedURLNoQuery();
if ($this->document->documentElement->hasAttribute('InResponseTo')) {
$responseInResponseTo = $this->document->documentElement->getAttribute('InResponseTo');
}
// Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided
if (isset($requestId) && isset($responseInResponseTo)) {
if ($requestId != $responseInResponseTo) {
throw new Exception("The InResponseTo of the Response: {$responseInResponseTo}, does not match the ID of the AuthNRequest sent by the SP: {$requestId}");
}
}
if (!$this->encrypted && $security['wantAssertionsEncrypted']) {
throw new Exception("The assertion of the Response is not encrypted and the SP requires it");
}
if ($security['wantNameIdEncrypted']) {
$encryptedIdNodes = $this->_queryAssertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData');
if ($encryptedIdNodes->length != 1) {
throw new Exception("The NameID of the Response is not encrypted and the SP requires it");
}
}
// Validate Conditions element exists
if (!$this->checkOneCondition()) {
throw new Exception("The Assertion must include a Conditions element");
}
// Validate Asserion timestamps
$validTimestamps = $this->validateTimestamps();
if (!$validTimestamps) {
throw new Exception('Timing issues (please check your clock settings)');
}
// Validate AuthnStatement element exists and is unique
if (!$this->checkOneAuthnStatement()) {
throw new Exception("The Assertion must include an AuthnStatement element");
}
// EncryptedAttributes are not supported
$encryptedAttributeNodes = $this->_queryAssertion('/saml:AttributeStatement/saml:EncryptedAttribute');
if ($encryptedAttributeNodes->length > 0) {
throw new Exception("There is an EncryptedAttribute in the Response and this SP not support them");
}
// Check destination
if ($this->document->documentElement->hasAttribute('Destination')) {
$destination = trim($this->document->documentElement->getAttribute('Destination'));
if (empty($destination)) {
throw new Exception("The response has an empty Destination value");
} else {
if (strpos($destination, $currentURL) !== 0) {
$currentURLNoRouted = OneLogin_Saml2_Utils::getSelfURLNoQuery();
if (strpos($destination, $currentURLNoRouted) !== 0) {
throw new Exception("The response was received at {$currentURL} instead of {$destination}");
}
}
}
}
// Check audience
$validAudiences = $this->getAudiences();
if (!empty($validAudiences) && !in_array($spEntityId, $validAudiences)) {
throw new Exception("{$spEntityId} is not a valid audience for this Response");
}
// Check the issuers
$issuers = $this->getIssuers();
foreach ($issuers as $issuer) {
$trimmedIssuer = trim($issuer);
if (empty($trimmedIssuer) || $trimmedIssuer !== $idPEntityId) {
throw new Exception("Invalid issuer in the Assertion/Response");
}
}
// Check the session Expiration
$sessionExpiration = $this->getSessionNotOnOrAfter();
if (!empty($sessionExpiration) && $sessionExpiration <= time()) {
throw new Exception("The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response");
}
// Check the SubjectConfirmation, at least one SubjectConfirmation must be valid
$anySubjectConfirmation = false;
$subjectConfirmationNodes = $this->_queryAssertion('/saml:Subject/saml:SubjectConfirmation');
foreach ($subjectConfirmationNodes as $scn) {
if ($scn->hasAttribute('Method') && $scn->getAttribute('Method') != OneLogin_Saml2_Constants::CM_BEARER) {
continue;
}
$subjectConfirmationDataNodes = $scn->getElementsByTagName('SubjectConfirmationData');
if ($subjectConfirmationDataNodes->length == 0) {
continue;
} else {
$scnData = $subjectConfirmationDataNodes->item(0);
if ($scnData->hasAttribute('InResponseTo')) {
$inResponseTo = $scnData->getAttribute('InResponseTo');
if ($responseInResponseTo != $inResponseTo) {
continue;
}
}
if ($scnData->hasAttribute('Recipient')) {
$recipient = $scnData->getAttribute('Recipient');
if (!empty($recipient) && strpos($recipient, $currentURL) === false) {
continue;
}
}
if ($scnData->hasAttribute('NotOnOrAfter')) {
$noa = OneLogin_Saml2_Utils::parseSAML2Time($scnData->getAttribute('NotOnOrAfter'));
if ($noa <= time()) {
continue;
}
}
if ($scnData->hasAttribute('NotBefore')) {
$nb = OneLogin_Saml2_Utils::parseSAML2Time($scnData->getAttribute('NotBefore'));
if ($nb > time()) {
continue;
}
}
$anySubjectConfirmation = true;
break;
}
}
if (!$anySubjectConfirmation) {
throw new Exception("A valid SubjectConfirmation was not found on this Response");
}
if ($security['wantAssertionsSigned'] && !$hasSignedAssertion) {
throw new Exception("The Assertion of the Response is not signed and the SP requires it");
}
if ($security['wantMessagesSigned'] && !$hasSignedResponse) {
throw new Exception("The Message of the Response is not signed and the SP requires it");
}
}
// Detect case not supported
if ($this->encrypted) {
$encryptedIDNodes = OneLogin_Saml2_Utils::query($this->decryptedDocument, '/samlp:Response/saml:Assertion/saml:Subject/saml:EncryptedID');
if ($encryptedIDNodes->length > 0) {
throw new Exception('Unsigned SAML Response that contains a signed and encrypted Assertion with encrypted nameId is not supported.');
}
}
if (empty($signedElements) || !$hasSignedResponse && !$hasSignedAssertion) {
throw new Exception('No Signature found. SAML Response rejected');
} else {
$cert = $idpData['x509cert'];
$fingerprint = $idpData['certFingerprint'];
$fingerprintalg = $idpData['certFingerprintAlgorithm'];
# If find a Signature on the Response, validates it checking the original response
if ($hasSignedResponse && !OneLogin_Saml2_Utils::validateSign($this->document, $cert, $fingerprint, $fingerprintalg, OneLogin_Saml2_Utils::RESPONSE_SIGNATURE_XPATH)) {
throw new Exception("Signature validation failed. SAML Response rejected");
}
# If find a Signature on the Assertion (decrypted assertion if was encrypted)
$documentToCheckAssertion = $this->encrypted ? $this->decryptedDocument : $this->document;
if ($hasSignedAssertion && !OneLogin_Saml2_Utils::validateSign($documentToCheckAssertion, $cert, $fingerprint, $fingerprintalg, OneLogin_Saml2_Utils::ASSERTION_SIGNATURE_XPATH)) {
throw new Exception("Signature validation failed. SAML Response rejected");
}
}
return true;
} catch (Exception $e) {
$this->_error = $e->getMessage();
$debug = $this->_settings->isDebugActive();
if ($debug) {
echo $this->_error;
}
return false;
}
}