/** * Obtains the SSO URL containing the AuthRequest * message deflated. * * @param OneLogin_Saml2_Settings $settings Settings */ public function getRedirectUrl($returnTo = null) { $settings = $this->auth->getSettings(); $authnRequest = new OneLogin_Saml2_AuthnRequest($settings); $parameters = array('SAMLRequest' => $authnRequest->getRequest()); if (!empty($returnTo)) { $parameters['RelayState'] = $returnTo; } else { $parameters['RelayState'] = OneLogin_Saml2_Utils::getSelfURLNoQuery(); } $url = OneLogin_Saml2_Utils::redirect($this->auth->getSSOurl(), $parameters, true); return $url; }
/** * Tests the isValid method of the OneLogin_Saml2_LogoutResponse * * @covers OneLogin_Saml2_LogoutResponse::isValid */ public function testIsValid() { $message = file_get_contents(TEST_ROOT . '/data/logout_responses/logout_response_deflated.xml.base64'); $response = new OneLogin_Saml2_LogoutResponse($this->_settings, $message); $this->assertTrue($response->isValid()); $this->_settings->setStrict(true); $response2 = new OneLogin_Saml2_LogoutResponse($this->_settings, $message); $this->assertFalse($response2->isValid()); $this->assertContains('The LogoutResponse was received at', $response2->getError()); $plainMessage = gzinflate(base64_decode($message)); $currentURL = OneLogin_Saml2_Utils::getSelfURLNoQuery(); $plainMessage = str_replace('http://stuff.com/endpoints/endpoints/sls.php', $currentURL, $plainMessage); $message3 = base64_encode(gzdeflate($plainMessage)); $response3 = new OneLogin_Saml2_LogoutResponse($this->_settings, $message3); $this->assertTrue($response3->isValid()); }
/** * Tests the processSLO method of the OneLogin_Saml2_Auth class * Case Valid Logout Request, validating the relayState, * a signed LogoutResponse is created and a redirection executed * * @covers OneLogin_Saml2_Auth::processSLO * @runInSeparateProcess */ public function testProcessSLORequestSignedResponse() { $settingsDir = TEST_ROOT . '/settings/'; include $settingsDir . 'settings1.php'; $settingsInfo['security']['logoutResponseSigned'] = true; $auth = new OneLogin_Saml2_Auth($settingsInfo); $message = file_get_contents(TEST_ROOT . '/data/logout_requests/logout_request_deflated.xml.base64'); // In order to avoid the destination problem $plainMessage = gzinflate(base64_decode($message)); $currentURL = OneLogin_Saml2_Utils::getSelfURLNoQuery(); $plainMessage = str_replace('http://stuff.com/endpoints/endpoints/sls.php', $currentURL, $plainMessage); $message = base64_encode(gzdeflate($plainMessage)); $_GET['SAMLRequest'] = $message; $_GET['RelayState'] = 'http://relaystate.com'; try { $auth->setStrict(true); $auth->processSLO(false); $this->assertFalse(true); } catch (Exception $e) { $this->assertContains('Cannot modify header information', $e->getMessage()); $trace = $e->getTrace(); $targetUrl = getUrlFromRedirect($trace); $parsedQuery = getParamsFromUrl($targetUrl); $sloUrl = $settingsInfo['idp']['singleLogoutService']['url']; $this->assertContains($sloUrl, $targetUrl); $this->assertArrayHasKey('SAMLResponse', $parsedQuery); $this->assertArrayHasKey('RelayState', $parsedQuery); $this->assertArrayHasKey('SigAlg', $parsedQuery); $this->assertArrayHasKey('Signature', $parsedQuery); $this->assertEquals('http://relaystate.com', $parsedQuery['RelayState']); $this->assertEquals(XMLSecurityKey::RSA_SHA1, $parsedQuery['SigAlg']); } }
/** * SAMPLE Code to demonstrate how to initiate a SAML Authorization request * * When the user visits this URL, the browser will be redirected to the SSO * IdP with an authorization request. If successful, it will then be * redirected to the consume URL (specified in settings) with the auth * details. */ session_start(); require_once '../_toolkit_loader.php'; if (!isset($_SESSION['samlUserdata'])) { $settings = new OneLogin_Saml2_Settings(); $authRequest = new OneLogin_Saml2_AuthnRequest($settings); $samlRequest = $authRequest->getRequest(); $parameters = array('SAMLRequest' => $samlRequest); $parameters['RelayState'] = OneLogin_Saml2_Utils::getSelfURLNoQuery(); $idpData = $settings->getIdPData(); $ssoUrl = $idpData['singleSignOnService']['url']; $url = OneLogin_Saml2_Utils::redirect($ssoUrl, $parameters, true); header("Location: {$url}"); } else { if (!empty($_SESSION['samlUserdata'])) { $attributes = $_SESSION['samlUserdata']; echo 'You have the following attributes:<br>'; echo '<table><thead><th>Name</th><th>Values</th></thead><tbody>'; foreach ($attributes as $attributeName => $attributeValues) { echo '<tr><td>' . htmlentities($attributeName) . '</td><td><ul>'; foreach ($attributeValues as $attributeValue) { echo '<li>' . htmlentities($attributeValue) . '</li>'; } echo '</ul></td></tr>';
/** * Tests the getSelfURLNoQuery method of the OneLogin_Saml2_Utils * * @covers OneLogin_Saml2_Utils::getSelfURLNoQuery */ public function testGetSelfURLNoQuery() { $url = OneLogin_Saml2_Utils::getSelfURLhost(); $url .= $_SERVER['SCRIPT_NAME']; $this->assertEquals($url, OneLogin_Saml2_Utils::getSelfURLNoQuery()); $_SERVER['PATH_INFO'] = '/test'; $this->assertEquals($url . '/test', OneLogin_Saml2_Utils::getSelfURLNoQuery()); }
/** * Tests the isValid method of the OneLogin_Saml2_Response * Case valid encrypted assertion * * Signed data can't be modified, so Destination will always fail in strict mode * * @covers OneLogin_Saml2_Response::isValid */ public function testIsValidEnc() { $xml = file_get_contents(TEST_ROOT . '/data/responses/double_signed_encrypted_assertion.xml.base64'); $response = new OneLogin_Saml2_Response($this->_settings, $xml); $this->assertTrue($response->isValid()); $xml2 = file_get_contents(TEST_ROOT . '/data/responses/signed_encrypted_assertion.xml.base64'); $response2 = new OneLogin_Saml2_Response($this->_settings, $xml2); $this->assertTrue($response2->isValid()); $xml3 = file_get_contents(TEST_ROOT . '/data/responses/signed_message_encrypted_assertion.xml.base64'); $response3 = new OneLogin_Saml2_Response($this->_settings, $xml3); $this->assertTrue($response3->isValid()); $settingsDir = TEST_ROOT . '/settings/'; include $settingsDir . 'settings1.php'; $settingsInfo['strict'] = true; $settings = new OneLogin_Saml2_Settings($settingsInfo); $xml4 = file_get_contents(TEST_ROOT . '/data/responses/valid_encrypted_assertion.xml.base64'); // In order to avoid the destination problem $plainMessage4 = base64_decode($xml4); $currentURL = OneLogin_Saml2_Utils::getSelfURLNoQuery(); $plainMessage4 = str_replace('http://stuff.com/endpoints/endpoints/acs.php', $currentURL, $plainMessage4); $message4 = base64_encode($plainMessage4); $response4 = new OneLogin_Saml2_Response($settings, $message4); $response4->isValid(); $this->assertContains('No Signature found. SAML Response rejected', $response4->getError()); }
/** * Determines if the SAML LogoutResponse is valid * * @param string $requestId The ID of the LogoutRequest sent by this SP to the IdP * * @throws Exception * @return bool Returns if the SAML LogoutResponse is or not valid */ public function isValid($requestId = null) { try { $idpData = $this->_settings->getIdPData(); $idPEntityId = $idpData['entityId']; if ($this->_settings->isStrict()) { $res = OneLogin_Saml2_Utils::validateXML($this->document, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive()); if (!$res instanceof DOMDocument) { throw new Exception("Invalid SAML Logout Response. Not match the saml-schema-protocol-2.0.xsd"); } $security = $this->_settings->getSecurityData(); // 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 Exception("The InResponseTo of the Logout Response: {$inResponseTo}, does not match the ID of the Logout request sent by the SP: {$requestId}"); } } // Check issuer $issuer = $this->getIssuer(); if (empty($issuer) || $issuer != $idPEntityId) { throw new Exception("Invalid issuer in the Logout Request"); } $currentURL = OneLogin_Saml2_Utils::getSelfURLNoQuery(); // Check destination if ($this->document->documentElement->hasAttribute('Destination')) { $destination = $this->document->documentElement->getAttribute('Destination'); if (!empty($destination)) { if (strpos($destination, $currentURL) === false) { throw new Exception("The LogoutRequest was received at {$currentURL} instead of {$destination}"); } } } if ($security['wantMessagesSigned']) { if (!isset($_GET['Signature'])) { throw new Exception("The Message of the Logout Response is not signed and the SP requires it"); } } } if (isset($_GET['Signature'])) { if (!isset($_GET['SigAlg'])) { $signAlg = XMLSecurityKey::RSA_SHA1; } else { $signAlg = $_GET['SigAlg']; } if ($signAlg != XMLSecurityKey::RSA_SHA1) { throw new Exception('Invalid signAlg in the recieved Logout Response'); } $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 Exception('In order to validate the sign on the Logout Response, the x509cert of the IdP is required'); } $cert = $idpData['x509cert']; $objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'public')); $objKey->loadKey($cert, false, true); if (!$objKey->verifySignature($signedQuery, base64_decode($_GET['Signature']))) { throw new Exception('Signature validation failed. Logout Response rejected'); } } return true; } catch (Exception $e) { $debug = $this->_settings->isDebugActive(); if ($debug) { echo $e->getMessage(); } return false; } }
/** * Tests the isValid method of the OneLogin_Saml2_LogoutRequest * * @covers OneLogin_Saml2_LogoutRequest::isValid */ public function testIsInValidSign() { $currentURL = OneLogin_Saml2_Utils::getSelfURLNoQuery(); $this->_settings->setStrict(false); $_GET = array('SAMLRequest' => 'lVLBitswEP0Vo7tjeWzJtki8LIRCYLvbNksPewmyPc6K2pJqyXQ/v1LSQlroQi/DMJr33rwZbZ2cJysezNms/gt+X9H55G2etBOXlx1ZFy2MdMoJLWd0wvfieP/xQcCGCrsYb3ozkRvI+wjpHC5eGU2Sw35HTg3lA8hqZFwWFcMKsStpxbEsxoLXeQN9OdY1VAgk+YqLC8gdCUQB7tyKB+281D6UaF6mtEiBPudcABcMXkiyD26Ulv6CevXeOpFlVvlunb5ttEmV3ZjlnGn8YTRO5qx0NuBs8kzpAd829tXeucmR5NH4J/203I8el6gFRUqbFPJnyEV51Wq30by4TLW0/9ZyarYTxt4sBsjUYLMZvRykl1Fxm90SXVkfwx4P++T4KSafVzmpUcVJ/sfSrQZJPphllv79W8WKGtLx0ir8IrVTqD1pT2MH3QAMSs4KTvui71jeFFiwirOmprwPkYW063+5uRq4urHiiC4e8hCX3J5wqAEGaPpw9XB5JmkBdeDqSlkz6CmUXdl0Qae5kv2F/1384wu3PwE=', 'RelayState' => '_1037fbc88ec82ce8e770b2bed1119747bb812a07e6', 'SigAlg' => 'http://www.w3.org/2000/09/xmldsig#rsa-sha1', 'Signature' => 'XCwCyI5cs7WhiJlB5ktSlWxSBxv+6q2xT3c8L7dLV6NQG9LHWhN7gf8qNsahSXfCzA0Ey9dp5BQ0EdRvAk2DIzKmJY6e3hvAIEp1zglHNjzkgcQmZCcrkK9Czi2Y1WkjOwR/WgUTUWsGJAVqVvlRZuS3zk3nxMrLH6f7toyvuJc='); $request = gzinflate(base64_decode($_GET['SAMLRequest'])); $encodedRequest = $_GET['SAMLRequest']; $logoutRequest = new OneLogin_Saml2_LogoutRequest($this->_settings, $encodedRequest); $this->assertTrue($logoutRequest->isValid()); $this->_settings->setStrict(true); $logoutRequest2 = new OneLogin_Saml2_LogoutRequest($this->_settings, $encodedRequest); $this->assertFalse($logoutRequest2->isValid()); $this->assertContains('The LogoutRequest was received at', $logoutRequest2->getError()); $this->_settings->setStrict(false); $oldSignature = $_GET['Signature']; $_GET['Signature'] = 'vfWbbc47PkP3ejx4bjKsRX7lo9Ml1WRoE5J5owF/0mnyKHfSY6XbhO1wwjBV5vWdrUVX+xp6slHyAf4YoAsXFS0qhan6txDiZY4Oec6yE+l10iZbzvie06I4GPak4QrQ4gAyXOSzwCrRmJu4gnpeUxZ6IqKtdrKfAYRAcVf3333='; $logoutRequest3 = new OneLogin_Saml2_LogoutRequest($this->_settings, $encodedRequest); $this->assertFalse($logoutRequest3->isValid()); $this->assertContains('Signature validation failed. Logout Request rejected', $logoutRequest3->getError()); $_GET['Signature'] = $oldSignature; $oldSigAlg = $_GET['SigAlg']; unset($_GET['SigAlg']); $this->assertTrue($logoutRequest3->isValid()); $oldRelayState = $_GET['RelayState']; $_GET['RelayState'] = 'http://example.com/relaystate'; $this->assertFalse($logoutRequest3->isValid()); $this->assertContains('Signature validation failed. Logout Request rejected', $logoutRequest3->getError()); $this->_settings->setStrict(true); $request2 = str_replace('https://pitbulk.no-ip.org/newonelogin/demo1/index.php?sls', $currentURL, $request); $request2 = str_replace('https://pitbulk.no-ip.org/simplesaml/saml2/idp/metadata.php', 'http://idp.example.com/', $request2); $deflatedRequest2 = gzdeflate($request2); $encodedRequest2 = base64_encode($deflatedRequest2); $_GET['SAMLRequest'] = $encodedRequest2; $logoutRequest4 = new OneLogin_Saml2_LogoutRequest($this->_settings, $encodedRequest2); $this->assertFalse($logoutRequest4->isValid()); $this->assertEquals('Signature validation failed. Logout Request rejected', $logoutRequest4->getError()); $this->_settings->setStrict(false); $logoutRequest5 = new OneLogin_Saml2_LogoutRequest($this->_settings, $encodedRequest2); $this->assertFalse($logoutRequest5->isValid()); $this->assertEquals('Signature validation failed. Logout Request rejected', $logoutRequest5->getError()); $_GET['SigAlg'] = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1'; $this->assertFalse($logoutRequest5->isValid()); $this->assertEquals('Invalid signAlg in the recieved Logout Request', $logoutRequest5->getError()); $settingsDir = TEST_ROOT . '/settings/'; include $settingsDir . 'settings1.php'; $settingsInfo['strict'] = true; $settingsInfo['security']['wantMessagesSigned'] = true; $settings = new OneLogin_Saml2_Settings($settingsInfo); $_GET['SigAlg'] = $oldSigAlg; $oldSignature = $_GET['Signature']; unset($_GET['Signature']); $logoutRequest6 = new OneLogin_Saml2_LogoutRequest($settings, $encodedRequest2); $this->assertFalse($logoutRequest6->isValid()); $this->assertEquals('The Message of the Logout Request is not signed and the SP require it', $logoutRequest6->getError()); $_GET['Signature'] = $oldSignature; $settingsInfo['idp']['certFingerprint'] = 'afe71c28ef740bc87425be13a2263d37971da1f9'; unset($settingsInfo['idp']['x509cert']); $settings2 = new OneLogin_Saml2_Settings($settingsInfo); $logoutRequest7 = new OneLogin_Saml2_LogoutRequest($settings2, $encodedRequest2); $this->assertFalse($logoutRequest7->isValid()); $this->assertContains('In order to validate the sign on the Logout Request, the x509cert of the IdP is required', $logoutRequest7->getError()); }
/** * Determines if the SAML Response is valid using the certificate. * * @param string $requestId The ID of the AuthNRequest sent by this SP to the IdP * * @throws Exception * @return bool Validate the document */ public function isValid($requestId = 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'); } $singleAssertion = $this->validateNumAssertions(); if (!$singleAssertion) { throw new Exception('SAML Response must contain 1 assertion'); } $status = $this->checkStatus(); $idpData = $this->_settings->getIdPData(); $idPEntityId = $idpData['entityId']; $spData = $this->_settings->getSPData(); $spEntityId = $spData['entityId']; $signedElements = array(); if ($this->encrypted) { $signNodes = $this->decryptedDocument->getElementsByTagName('Signature'); } else { $signNodes = $this->document->getElementsByTagName('Signature'); } foreach ($signNodes as $signNode) { $signedElements[] = $signNode->parentNode->tagName; } if ($this->_settings->isStrict()) { $res = OneLogin_Saml2_Utils::validateXML($this->document, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive()); if (!$res instanceof DOMDocument) { print_r($res); throw new Exception("Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd"); } $security = $this->_settings->getSecurityData(); $currentURL = OneLogin_Saml2_Utils::getSelfURLNoQuery(); 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 == 0) { throw new Exception("The NameID of the Response is not encrypted and the SP requires it"); } } // Validate Asserion timestamps $validTimestamps = $this->validateTimestamps(); if (!$validTimestamps) { throw new Exception('Timing issues (please check your clock settings)'); } // 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 = $this->document->documentElement->getAttribute('Destination'); if (!empty($destination)) { if (strpos($destination, $currentURL) !== 0) { $currentURLrouted = OneLogin_Saml2_Utils::getSelfRoutedURLNoQuery(); if (strpos($destination, $currentURLrouted) !== 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) { if (empty($issuer) || $issuer != $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'] && !in_array('saml:Assertion', $signedElements)) { throw new Exception("The Assertion of the Response is not signed and the SP requires it"); } if ($security['wantMessagesSigned'] && !in_array('samlp:Response', $signedElements)) { throw new Exception("The Message of the Response is not signed and the SP requires it"); } } if (!empty($signedElements)) { $cert = $idpData['x509cert']; $fingerprint = $idpData['certFingerprint']; // Only validates the first sign found if (in_array('samlp:Response', $signedElements)) { $documentToValidate = $this->document; } else { if ($this->encrypted) { $documentToValidate = $this->decryptedDocument; } else { $documentToValidate = $this->document; } } if (!OneLogin_Saml2_Utils::validateSign($documentToValidate, $cert, $fingerprint)) { throw new Exception('Signature validation failed. SAML Response rejected'); } } return true; } catch (Exception $e) { $debug = $this->_settings->isDebugActive(); if ($debug) { echo $e->getMessage(); } return false; } }
<?php /** * SAMPLE Code to demonstrate how to initiate a SAML Authorization request * * When the user visits this URL, the browser will be redirected to the SSO * IdP with an authorization request. If successful, it will then be * redirected to the consume URL (specified in settings) with the auth * details. */ session_start(); require_once '../_toolkit_loader.php'; $auth = new OneLogin_Saml2_Auth(); if (!isset($_SESSION['samlUserdata'])) { $auth->login(); } else { $indexUrl = str_replace('/sso.php', '/index.php', OneLogin_Saml2_Utils::getSelfURLNoQuery()); OneLogin_Saml2_Utils::redirect($indexUrl); }
/** * Determines if the SAML Response is valid using the certificate. * * @param string|null $requestId The ID of the AuthNRequest sent by this SP to the IdP * * @return bool Validate the document * * @throws Exception */ 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; } }
/** * Initiates the SLO process. * * @param string $returnTo The target URL the user should be returned to after logout. */ public function logout($returnTo = null) { $sloUrl = $this->getSLOurl(); if (!isset($sloUrl)) { throw new OneLogin_Saml2_Error('The IdP does not support Single Log Out', OneLogin_Saml2_Error::SAML_SINGLE_LOGOUT_NOT_SUPPORTED); } $logoutRequest = new OneLogin_Saml2_LogoutRequest($this->_settings); $samlRequest = $logoutRequest->getRequest(); $parameters = array('SAMLRequest' => $samlRequest); if (!empty($returnTo)) { $parameters['RelayState'] = $returnTo; } else { $parameters['RelayState'] = OneLogin_Saml2_Utils::getSelfURLNoQuery(); } $security = $this->_settings->getSecurityData(); if (isset($security['logoutRequestSigned']) && $security['logoutRequestSigned']) { $signature = $this->buildRequestSignature($samlRequest, $parameters['RelayState']); $parameters['SigAlg'] = XMLSecurityKey::RSA_SHA1; $parameters['Signature'] = $signature; } $this->redirectTo($sloUrl, $parameters); }
/** * Checks if the Logout Request recieved is valid. * * @param OneLogin_Saml2_Settings $settings Settings * @param string|DOMDocument $request Logout Request decoded * * @return boolean If the Logout Request is or not valid */ public static function isValid(OneLogin_Saml2_Settings $settings, $request, $debug = false) { try { if ($request instanceof DOMDocument) { $dom = $request; } else { $dom = new DOMDocument(); $dom = OneLogin_Saml2_Utils::loadXML($dom, $request); } $idpData = $settings->getIdPData(); $idPEntityId = $idpData['entityId']; if ($settings->isStrict()) { $res = OneLogin_Saml2_Utils::validateXML($dom, 'saml-schema-protocol-2.0.xsd', $debug); if (!$res instanceof DOMDocument) { throw new Exception("Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd"); } $security = $settings->getSecurityData(); $currentURL = OneLogin_Saml2_Utils::getSelfURLNoQuery(); // Check NotOnOrAfter if ($dom->documentElement->hasAttribute('NotOnOrAfter')) { $na = OneLogin_Saml2_Utils::parseSAML2Time($dom->documentElement->getAttribute('NotOnOrAfter')); if ($na <= time()) { throw new Exception('Timing issues (please check your clock settings)'); } } // Check destination if ($dom->documentElement->hasAttribute('Destination')) { $destination = $dom->documentElement->getAttribute('Destination'); if (!empty($destination)) { if (strpos($destination, $currentURL) === false) { throw new Exception("The LogoutRequest was received at {$currentURL} instead of {$destination}"); } } } $nameId = self::getNameId($dom); // Check issuer $issuer = self::getIssuer($dom); if (empty($issuer) || $issuer != $idPEntityId) { throw new Exception("Invalid issuer in the Logout Request"); } if ($security['wantMessagesSigned']) { if (!isset($_GET['Signature'])) { throw new Exception("The Message of the Logout Request is not signed and the SP require it"); } } } if (isset($_GET['Signature'])) { if (!isset($_GET['SigAlg'])) { $signAlg = XMLSecurityKey::RSA_SHA1; } else { $signAlg = $_GET['SigAlg']; } if ($signAlg != XMLSecurityKey::RSA_SHA1) { throw new Exception('Invalid signAlg in the recieved Logout Request'); } $signedQuery = 'SAMLRequest=' . urlencode($_GET['SAMLRequest']); if (isset($_GET['RelayState'])) { $signedQuery .= '&RelayState=' . urlencode($_GET['RelayState']); } $signedQuery .= '&SigAlg=' . urlencode($signAlg); if (!isset($idpData['x509cert']) || empty($idpData['x509cert'])) { throw new Exception('In order to validate the sign on the Logout Request, the x509cert of the IdP is required'); } $cert = $idpData['x509cert']; $objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'public')); $objKey->loadKey($cert, false, true); if (!$objKey->verifySignature($signedQuery, base64_decode($_GET['Signature']))) { throw new Exception('Signature validation failed. Logout Request rejected'); } } return true; } catch (Exception $e) { $debug = $settings->isDebugActive(); if ($debug) { echo $e->getMessage(); } return false; } }