diff --git a/README.md b/README.md index 028fc78..900e041 100644 --- a/README.md +++ b/README.md @@ -753,6 +753,8 @@ if (!$auth->isAuthenticated()) { $_SESSION['samlUserdata'] = $auth->getAttributes(); $_SESSION['samlNameId'] = $auth->getNameId(); $_SESSION['samlNameIdFormat'] = $auth->getNameIdFormat(); +$_SESSION['samlNameidNameQualifier' = $auth->getNameIdNameQualifier(); +$_SESSION['samlNameidSPNameQualifier' = $auth->getNameIdSPNameQualifier(); $_SESSION['samlSessionIndex'] = $auth->getSessionIndex(); if (isset($_POST['RelayState']) && OneLogin_Saml2_Utils::getSelfURL() != $_POST['RelayState']) { @@ -980,7 +982,7 @@ $auth = new OneLogin_Saml2_Auth(); $auth->logout(); // Method that sent the Logout Request. ``` -Also there are six optional parameters that can be set: +Also there are eight optional parameters that can be set: * `$returnTo` - The target URL the user should be returned to after logout. * `$parameters` - Extra parameters to be added to the GET. * `$name_id` - That will be used to build the LogoutRequest. If `name_id` parameter is not set and the auth object processed a @@ -988,6 +990,8 @@ SAML Response with a `NameId`, then this `NameId` will be used. * `$session_index` - SessionIndex that identifies the session of the user. * `$stay` - True if we want to stay (returns the url string) False to redirect. * `$nameIdFormat` - The NameID Format will be set in the LogoutRequest. +* `$nameIdNameQualifier` - The NameID NameQualifier will be set in the LogoutRequest. +* `$nameIdSPNameQualifier` - The NameID SP NameQualifier will be set in the LogoutRequest. The Logout Request will be sent signed or unsigned based on the security info of the `advanced_settings.php` (`'logoutRequestSigned'`). @@ -1014,6 +1018,9 @@ $paramters = array(); $nameId = null; $sessionIndex = null; $nameIdFormat = null; +$nameIdNameQualifier = null; +$nameIdSPNameQualifier = null; + if (isset($_SESSION['samlNameId'])) { $nameId = $_SESSION['samlNameId']; } @@ -1023,7 +1030,13 @@ if (isset($_SESSION['samlSessionIndex'])) { if (isset($_SESSION['samlNameIdFormat'])) { $nameIdFormat = $_SESSION['samlNameIdFormat']; } -$auth->logout($returnTo, $paramters, $nameId, $sessionIndex, false, $nameIdFormat); +if (isset($_SESSION['samlNameIdNameQualifier'])) { + $nameIdNameQualifier = $_SESSION['samlNameIdNameQualifier']; +} +if (isset($_SESSION['samlNameIdSPNameQualifier'])) { + $nameIdSPNameQualifier = $_SESSION['samlNameIdSPNameQualifier']; +} +$auth->logout($returnTo, $paramters, $nameId, $sessionIndex, false, $nameIdFormat, $nameIdNameQualifier, $nameIdSPNameQualifier); ``` If a match on the future LogoutResponse ID and the LogoutRequest ID to be sent is required, that LogoutRequest ID must to be extracted and stored. @@ -1282,6 +1295,9 @@ Main class of OneLogin PHP Toolkit * `getAttributes` - Returns the set of SAML attributes. * `getAttribute` - Returns the requested SAML attribute * `getNameId` - Returns the nameID + * `getNameIdFormat` - Gets the NameID Format provided by the SAML response from the IdP. + * `getNameIdNameQualifier` - Gets the NameID NameQualifier provided from the SAML Response String. + * `getNameIdNameSPQualifier` - Gets the NameID SP NameQualifier provided from the SAML Response String. * `getSessionIndex` - Gets the SessionIndex from the AuthnStatement. * `getErrors` - Returns if there were any error * `getSSOurl` - Gets the SSO url. @@ -1318,6 +1334,8 @@ SAML 2 Authentication Response class IdP. * `getNameId` - Gets the NameID provided by the SAML response from the IdP. * `getNameIdFormat` - Gets the NameID Format provided by the SAML response from the IdP. + * `getNameIdNameQualifier` - Gets the NameID NameQualifier provided from the SAML Response String. + * `getNameIdNameSPQualifier` - Gets the NameID SP NameQualifier provided from the SAML Response String. * `getSessionNotOnOrAfter` - Gets the SessionNotOnOrAfter from the AuthnStatement * `getSessionIndex` - Gets the SessionIndex from the AuthnStatement. diff --git a/demo1/index.php b/demo1/index.php index 3996791..2400451 100644 --- a/demo1/index.php +++ b/demo1/index.php @@ -35,14 +35,20 @@ if (isset($_SESSION['samlNameId'])) { $nameId = $_SESSION['samlNameId']; } - if (isset($_SESSION['samlSessionIndex'])) { - $sessionIndex = $_SESSION['samlSessionIndex']; - } if (isset($_SESSION['samlNameIdFormat'])) { $nameIdFormat = $_SESSION['samlNameIdFormat']; } + if (isset($_SESSION['samlNameIdNameQualifier'])) { + $nameIdNameQualifier = $_SESSION['samlNameIdNameQualifier']; + } + if (isset($_SESSION['samlNameIdSPNameQualifier'])) { + $nameIdSPNameQualifier = $_SESSION['samlNameIdSPNameQualifier']; + } + if (isset($_SESSION['samlSessionIndex'])) { + $sessionIndex = $_SESSION['samlSessionIndex']; + } - $auth->logout($returnTo, $parameters, $nameId, $sessionIndex, false, $nameIdFormat); + $auth->logout($returnTo, $parameters, $nameId, $sessionIndex, false, $nameIdFormat, $nameIdNameQualifier, $nameIdSPNameQualifier); # If LogoutRequest ID need to be saved in order to later validate it, do instead # $sloBuiltUrl = $auth->logout(null, $paramters, $nameId, $sessionIndex, true); @@ -75,6 +81,8 @@ $_SESSION['samlUserdata'] = $auth->getAttributes(); $_SESSION['samlNameId'] = $auth->getNameId(); $_SESSION['samlNameIdFormat'] = $auth->getNameIdFormat(); + $_SESSION['samlNameIdNameQualifier'] = $auth->getNameIdNameQualifier(); + $_SESSION['samlNameIdSPNameQualifier'] = $auth->getNameIdSPNameQualifier(); $_SESSION['samlSessionIndex'] = $auth->getSessionIndex(); unset($_SESSION['AuthNRequestID']); if (isset($_POST['RelayState']) && OneLogin_Saml2_Utils::getSelfURL() != $_POST['RelayState']) { diff --git a/lib/Saml2/Auth.php b/lib/Saml2/Auth.php index fed8ace..591ffd3 100644 --- a/lib/Saml2/Auth.php +++ b/lib/Saml2/Auth.php @@ -49,6 +49,13 @@ class OneLogin_Saml2_Auth */ private $_nameidNameQualifier; + /** + * NameID SP NameQualifier + * + * @var string + */ + private $_nameidSPNameQualifier; + /** * If user is authenticated. * @@ -197,6 +204,7 @@ public function processResponse($requestId = null) $this->_nameid = $response->getNameId(); $this->_nameidFormat = $response->getNameIdFormat(); $this->_nameidNameQualifier = $response->getNameIdNameQualifier(); + $this->_nameidSPNameQualifier = $response->getNameIdSPNameQualifier(); $this->_authenticated = true; $this->_sessionIndex = $response->getSessionIndex(); $this->_sessionExpiration = $response->getSessionNotOnOrAfter(); @@ -380,6 +388,16 @@ public function getNameIdNameQualifier() return $this->_nameidNameQualifier; } + /** + * Returns the nameID SP NameQualifier + * + * @return string The nameID SP NameQualifier of the assertion + */ + public function getNameIdSPNameQualifier() + { + return $this->_nameidSPNameQualifier; + } + /** * Returns the SessionIndex * @@ -513,7 +531,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, $nameIdNameQualifier = null) + public function logout($returnTo = null, $parameters = array(), $nameId = null, $sessionIndex = null, $stay = false, $nameIdFormat = null, $nameIdNameQualifier = null, $nameIdSPNameQualifier = null) { assert('is_array($parameters)'); @@ -532,7 +550,7 @@ public function logout($returnTo = null, $parameters = array(), $nameId = null, $nameIdFormat = $this->_nameidFormat; } - $logoutRequest = new OneLogin_Saml2_LogoutRequest($this->_settings, null, $nameId, $sessionIndex, $nameIdFormat, $nameIdNameQualifier); + $logoutRequest = new OneLogin_Saml2_LogoutRequest($this->_settings, null, $nameId, $sessionIndex, $nameIdFormat, $nameIdNameQualifier, $nameIdSPNameQualifier); $this->_lastRequest = $logoutRequest->getXML(); $this->_lastRequestID = $logoutRequest->id; @@ -650,7 +668,7 @@ public function buildResponseSignature($samlResponse, $relayState, $signAlgorith * * @throws OneLogin_Saml2_Error */ - private function buildMessageSignature($samlMessage, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA256, $type="SAMLRequest") + private function buildMessageSignature($samlMessage, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA256, $type = "SAMLRequest") { $key = $this->_settings->getSPkey(); if (empty($key)) { diff --git a/lib/Saml2/LogoutRequest.php b/lib/Saml2/LogoutRequest.php index 595c1ca..e152390 100644 --- a/lib/Saml2/LogoutRequest.php +++ b/lib/Saml2/LogoutRequest.php @@ -39,10 +39,11 @@ class OneLogin_Saml2_LogoutRequest * @param string|null $sessionIndex The SessionIndex (taken from the SAML Response in the SSO process). * @param string|null $nameIdFormat The NameID Format will be set in the LogoutRequest. * @param string|null $nameIdNameQualifier The NameID NameQualifier will be set in the LogoutRequest. + * @param string|null $nameIdSPNameQualifier The NameID SP NameQualifier will be set in the LogoutRequest. * * @throws OneLogin_Saml2_Error */ - public function __construct(OneLogin_Saml2_Settings $settings, $request = null, $nameId = null, $sessionIndex = null, $nameIdFormat = null, $nameIdNameQualifier = null) + public function __construct(OneLogin_Saml2_Settings $settings, $request = null, $nameId = null, $sessionIndex = null, $nameIdFormat = null, $nameIdNameQualifier = null, $nameIdSPNameQualifier = null) { $this->_settings = $settings; @@ -59,7 +60,6 @@ public function __construct(OneLogin_Saml2_Settings $settings, $request = null, $id = OneLogin_Saml2_Utils::generateUniqueID(); $this->id = $id; - $nameIdValue = OneLogin_Saml2_Utils::generateUniqueID(); $issueInstant = OneLogin_Saml2_Utils::parseTime2SAML(time()); $cert = null; @@ -78,16 +78,26 @@ public function __construct(OneLogin_Saml2_Settings $settings, $request = null, $spData['NameIDFormat'] != OneLogin_Saml2_Constants::NAMEID_UNSPECIFIED) { $nameIdFormat = $spData['NameIDFormat']; } - $spNameQualifier = null; } else { $nameId = $idpData['entityId']; $nameIdFormat = OneLogin_Saml2_Constants::NAMEID_ENTITY; - $spNameQualifier = $spData['entityId']; + } + + /* From saml-core-2.0-os 8.3.6, when the entity Format is used: + "The NameQualifier, SPNameQualifier, and SPProvidedID attributes MUST be omitted. + */ + if (!empty($nameIdFormat) && $nameIdFormat == OneLogin_Saml2_Constants::NAMEID_ENTITY) { + $nameIdNameQualifier = null; + $nameIdSPNameQualifier = null; + } + // NameID Format UNSPECIFIED omitted + if (!empty($nameIdFormat) && $nameIdFormat == OneLogin_Saml2_Constants::NAMEID_UNSPECIFIED) { + $nameIdFormat = null; } $nameIdObj = OneLogin_Saml2_Utils::generateNameId( $nameId, - $spNameQualifier, + $nameIdSPNameQualifier, $nameIdFormat, $cert, $nameIdNameQualifier diff --git a/lib/Saml2/Response.php b/lib/Saml2/Response.php index b2a6f68..55e480d 100644 --- a/lib/Saml2/Response.php +++ b/lib/Saml2/Response.php @@ -671,6 +671,23 @@ public function getNameIdNameQualifier() return $nameIdNameQualifier; } + /** + * Gets the NameID SP NameQualifier provided by the SAML response from the IdP. + * + * @return string|null NameID SP NameQualifier + * + * @throws ValidationError + */ + public function getNameIdSPNameQualifier() + { + $nameIdSPNameQualifier = null; + $nameIdData = $this->getNameIdData(); + if (!empty($nameIdData) && isset($nameIdData['SPNameQualifier'])) { + $nameIdSPNameQualifier = $nameIdData['SPNameQualifier']; + } + return $nameIdSPNameQualifier; + } + /** * Gets the SessionNotOnOrAfter from the AuthnStatement. * Could be used to set the local session expiration diff --git a/tests/src/OneLogin/Saml2/AuthTest.php b/tests/src/OneLogin/Saml2/AuthTest.php index 2606448..94e3872 100644 --- a/tests/src/OneLogin/Saml2/AuthTest.php +++ b/tests/src/OneLogin/Saml2/AuthTest.php @@ -120,6 +120,8 @@ public function testProcessNoResponse() * @covers OneLogin_Saml2_Auth::getAttribute * @covers OneLogin_Saml2_Auth::getNameId * @covers OneLogin_Saml2_Auth::getNameIdFormat + * @covers OneLogin_Saml2_Auth::getNameIdNameQualifier + * @covers OneLogin_Saml2_Auth::getNameIdSPNameQualifier * @covers OneLogin_Saml2_Auth::getErrors * @covers OneLogin_Saml2_Auth::getSessionIndex * @covers OneLogin_Saml2_Auth::getSessionExpiration @@ -136,6 +138,8 @@ public function testProcessResponseInvalid() $this->assertEmpty($this->_auth->getAttributes()); $this->assertNull($this->_auth->getNameId()); $this->assertNull($this->_auth->getNameIdFormat()); + $this->assertNull($this->_auth->getNameIdNameQualifier()); + $this->assertNull($this->_auth->getNameIdSPNameQualifier()); $this->assertNull($this->_auth->getSessionIndex()); $this->assertNull($this->_auth->getSessionExpiration()); $this->assertNull($this->_auth->getAttribute('uid')); @@ -209,6 +213,63 @@ public function testProcessResponseValid() $this->assertEquals('2655106621', $sessionExpiration); } + /** + * Tests the getNameIdNameQualifier method of the Auth class + * Case found + * @covers OneLogin_Saml2_Auth::getNameIdNameQualifier + */ + public function testGetNameIdNameQualifier() + { + $message = file_get_contents(TEST_ROOT . '/data/responses/valid_response_with_namequalifier.xml.base64'); + $_POST['SAMLResponse'] = $message; + $this->assertNull($this->_auth->getNameIdNameQualifier()); + $this->_auth->processResponse(); + $this->assertTrue($this->_auth->isAuthenticated()); + $this->assertEquals('https://test.example.com/saml/metadata', $this->_auth->getNameIdNameQualifier()); + } + /** + * Tests the getNameIdNameQualifier method of the Auth class + * Case Null + * @covers OneLogin_Saml2_Auth::getNameIdNameQualifier + */ + public function testGetNameIdNameQualifier2() + { + $message = file_get_contents(TEST_ROOT . '/data/responses/valid_response.xml.base64'); + $_POST['SAMLResponse'] = $message; + $this->assertNull($this->_auth->getNameIdNameQualifier()); + $this->_auth->processResponse(); + $this->assertTrue($this->_auth->isAuthenticated()); + $this->assertNull($this->_auth->getNameIdNameQualifier()); + } + /** + * Tests the getNameIdSPNameQualifier method of the Auth class + * Case Found + * @covers OneLogin_Saml2_Auth::getNameIdSPNameQualifier + */ + public function testGetNameIdSPNameQualifier() + { + $message = file_get_contents(TEST_ROOT . '/data/responses/valid_response_with_namequalifier.xml.base64'); + $_POST['SAMLResponse'] = $message; + $this->assertNull($this->_auth->getNameIdSPNameQualifier()); + $this->_auth->processResponse(); + $this->assertTrue($this->_auth->isAuthenticated()); + $this->assertNull($this->_auth->getNameIdSPNameQualifier()); + } + /** + * Tests the getNameIdSPNameQualifier method of the Auth class + * Case Null + * @covers OneLogin_Saml2_Auth::getNameIdSPNameQualifier + */ + public function testGetNameIdSPNameQualifier2() + { + $message = file_get_contents(TEST_ROOT . '/data/responses/valid_response.xml.base64'); + $_POST['SAMLResponse'] = $message; + $this->assertNull($this->_auth->getNameIdSPNameQualifier()); + $this->_auth->processResponse(); + $this->assertTrue($this->_auth->isAuthenticated()); + $this->assertEquals('http://stuff.com/endpoints/metadata.php', $this->_auth->getNameIdSPNameQualifier()); + } + /** * Tests the getAttributes and getAttributesWithFriendlyName methods * @covers OneLogin_Saml2_Auth::getAttributes diff --git a/tests/src/OneLogin/Saml2/LogoutRequestTest.php b/tests/src/OneLogin/Saml2/LogoutRequestTest.php index 7c5c3cf..f48dc55 100644 --- a/tests/src/OneLogin/Saml2/LogoutRequestTest.php +++ b/tests/src/OneLogin/Saml2/LogoutRequestTest.php @@ -374,6 +374,24 @@ public function testGetNameIdData() $this->assertContains('NameID not found in the Logout Request', $e->getMessage()); } + $logoutRequest = new OneLogin_Saml2_LogoutRequest($this->_settings, null, "ONELOGIN_1e442c129e1f822c8096086a1103c5ee2c7cae1c", null, OneLogin_Saml2_Constants::NAMEID_PERSISTENT, $this->_settings->getIdPData()['entityId'], $this->_settings->getSPData()['entityId']); + $logoutRequestStr = $logoutRequest->getXML(); + $this->assertContains('ONELOGIN_1e442c129e1f822c8096086a1103c5ee2c7cae1c', $logoutRequestStr); + $this->assertContains('Format="'.OneLogin_Saml2_Constants::NAMEID_PERSISTENT, $logoutRequestStr); + $this->assertContains('NameQualifier="'.$this->_settings->getIdPData()['entityId'], $logoutRequestStr); + $this->assertContains('SPNameQualifier="'.$this->_settings->getSPData()['entityId'], $logoutRequestStr); + $logoutRequest2 = new OneLogin_Saml2_LogoutRequest($this->_settings, null, "ONELOGIN_1e442c129e1f822c8096086a1103c5ee2c7cae1c", null, OneLogin_Saml2_Constants::NAMEID_ENTITY, $this->_settings->getIdPData()['entityId'], $this->_settings->getSPData()['entityId']); + $logoutRequestStr2 = $logoutRequest2->getXML(); + $this->assertContains('ONELOGIN_1e442c129e1f822c8096086a1103c5ee2c7cae1c', $logoutRequestStr2); + $this->assertContains('Format="'.OneLogin_Saml2_Constants::NAMEID_ENTITY, $logoutRequestStr2); + $this->assertNotContains('NameQualifier', $logoutRequestStr2); + $this->assertNotContains('SPNameQualifier', $logoutRequestStr2); + $logoutRequest3 = new OneLogin_Saml2_LogoutRequest($this->_settings, null, "ONELOGIN_1e442c129e1f822c8096086a1103c5ee2c7cae1c", null, OneLogin_Saml2_Constants::NAMEID_UNSPECIFIED); + $logoutRequestStr3 = $logoutRequest3->getXML(); + $this->assertContains('ONELOGIN_1e442c129e1f822c8096086a1103c5ee2c7cae1c', $logoutRequestStr3); + $this->assertNotContains('Format', $logoutRequestStr3); + $this->assertNotContains('NameQualifier', $logoutRequestStr3); + $this->assertNotContains('SPNameQualifier', $logoutRequestStr3); } /** diff --git a/tests/src/OneLogin/Saml2/ResponseTest.php b/tests/src/OneLogin/Saml2/ResponseTest.php index 6d7d280..54097a4 100644 --- a/tests/src/OneLogin/Saml2/ResponseTest.php +++ b/tests/src/OneLogin/Saml2/ResponseTest.php @@ -258,6 +258,35 @@ public function testGetNameIdNameQualifier() } } + /** + * Tests the getNameIdSPNameQualifier method of the Response + * + * @covers OneLogin_Saml2_Response::getNameIdSPNameQualifier + */ + public function testGetNameIdSPNameQualifier() + { + $xml = file_get_contents(TEST_ROOT . '/data/responses/response1.xml.base64'); + $response = new OneLogin_Saml2_Response($this->_settings, $xml); + $this->assertNull($response->getNameIdSPNameQualifier()); + $xml2 = file_get_contents(TEST_ROOT . '/data/responses/response_encrypted_nameid.xml.base64'); + $response2 = new OneLogin_Saml2_Response($this->_settings, $xml2); + $this->assertEquals('http://stuff.com/endpoints/metadata.php', $response2->getNameIdSPNameQualifier()); + $xml3 = file_get_contents(TEST_ROOT . '/data/responses/valid_encrypted_assertion.xml.base64'); + $response3 = new OneLogin_Saml2_Response($this->_settings, $xml3); + $this->assertEquals('http://stuff.com/endpoints/metadata.php', $response3->getNameIdSPNameQualifier()); + $xml4 = file_get_contents(TEST_ROOT . '/data/responses/valid_response.xml.base64'); + $response4 = new OneLogin_Saml2_Response($this->_settings, $xml4); + $this->assertEquals('http://stuff.com/endpoints/metadata.php', $response4->getNameIdSPNameQualifier()); + $xml5 = file_get_contents(TEST_ROOT . '/data/responses/invalids/no_nameid.xml.base64'); + $response5 = new OneLogin_Saml2_Response($this->_settings, $xml5); + try { + $nameId5 = $response5->getNameIdSPNameQualifier(); + $this->fail('ValidationError was not raised'); + } catch (OneLogin_Saml2_ValidationError $e) { + $this->assertContains('NameID not found in the assertion of the Response', $e->getMessage()); + } + } + /** * Tests the getNameIdData method of the OneLogin_Saml2_Response *