diff --git a/src/Console/stubs/example/views/layouts/app.stub b/src/Console/stubs/example/views/layouts/app.stub index 98de551..5d077c6 100644 --- a/src/Console/stubs/example/views/layouts/app.stub +++ b/src/Console/stubs/example/views/layouts/app.stub @@ -14,7 +14,7 @@ - +
diff --git a/src/SPIDAuth.php b/src/SPIDAuth.php index a296dcd..4ea849a 100644 --- a/src/SPIDAuth.php +++ b/src/SPIDAuth.php @@ -161,11 +161,10 @@ public function acs(): RedirectResponse $SPIDUser = new SPIDUser($attributes); $idpEntityName = $this->getIdpEntityName($lastResponseXML); - $spidSessionIndex = $this->getSAML($idp)->getSessionIndex() ?? $this->getRandomString(); session(['spid_idp' => $idp]); session(['spid_idpEntityName' => $idpEntityName]); - session(['spid_sessionIndex' => $spidSessionIndex]); + session(['spid_sessionId' => $this->getSAML($idp)->getLastMessageId()]); session(['spid_nameId' => $this->getSAML($idp)->getNameId()]); session(['spid_user' => $SPIDUser]); @@ -191,7 +190,7 @@ public function acs(): RedirectResponse public function logout(): RedirectResponse { if ($this->isAuthenticated()) { - $sessionIndex = session()->pull('spid_sessionIndex'); + $sessionId = session()->pull('spid_sessionId'); $nameId = session()->pull('spid_nameId'); $returnTo = url(config('spid-auth.after_logout_url')); $idp = session()->get('spid_idp'); @@ -211,7 +210,7 @@ public function logout(): RedirectResponse } try { - return $this->getSAML($idp)->logout($returnTo, [], $nameId, $sessionIndex, false, SAMLConstants::NAMEID_TRANSIENT, $idpEntityId); + return $this->getSAML($idp)->logout($returnTo, [], $nameId, $sessionId, false, SAMLConstants::NAMEID_TRANSIENT, $idpEntityId); } catch (SAMLError $e) { throw new SPIDLogoutException($e->getMessage(), SPIDLogoutException::SAML_LOGOUT_ERROR, $e); } @@ -250,7 +249,7 @@ public function logout(): RedirectResponse */ public function isAuthenticated(): bool { - return session()->has('spid_sessionIndex'); + return session()->has('spid_sessionId'); } /** @@ -482,8 +481,13 @@ protected function validateLoginResponse(string $lastResponseXML, string $lastRe throw new SPIDLoginException('SAML response validation error: empty or missing AudienceRestriction element', SPIDLoginException::SAML_VALIDATION_ERROR); } - if (0 === preg_match('/https:\/\/www\.spid\.gov\.it\/SpidL[123]/', $authContextClassRef->textContent)) { + $matchedSPIDLevel = []; + $configuredSpidLevel = preg_match('/https:\/\/www\.spid\.gov\.it\/SpidL([123])/', config('spid-auth.sp_spid_level'), $matchedSPIDLevel) ? (int) $matchedSPIDLevel[1] : null; + + if (0 === preg_match('/https:\/\/www\.spid\.gov\.it\/SpidL([123])/', $authContextClassRef->textContent, $matchedSPIDLevel)) { throw new SPIDLoginException('SAML response validation error: wrong AuthContextClassRef element', SPIDLoginException::SAML_VALIDATION_ERROR); + } elseif ((int) $matchedSPIDLevel[1] < $configuredSpidLevel) { + throw new SPIDLoginException('SAML response validation error: minimum SPID Level not enforced', SPIDLoginException::SAML_VALIDATION_ERROR); } try { diff --git a/tests/MiddlewareTest.php b/tests/MiddlewareTest.php index a51ea65..8305c4d 100644 --- a/tests/MiddlewareTest.php +++ b/tests/MiddlewareTest.php @@ -36,7 +36,7 @@ public function testAuthenticated() Route::get('/', function () { return 'home'; })->middleware('spid.auth'); - $response = $this->withSession(['spid_sessionIndex' => 'sessionIndex'])->get('/'); + $response = $this->withSession(['spid_sessionId' => 'sessionId'])->get('/'); $response->assertSuccessful(); } diff --git a/tests/ResponseValidationTest.php b/tests/ResponseValidationTest.php index 2826720..0b5d764 100644 --- a/tests/ResponseValidationTest.php +++ b/tests/ResponseValidationTest.php @@ -195,6 +195,17 @@ public function testWrongAuthContextClassRef() ]); } + public function testWrongSPIDLevel() + { + $this->runValidationTest([ + 'responseXmlFile' => 'valid_level1.xml', + 'config' => [ + 'spid-auth.sp_spid_level' => 'https://www.spid.gov.it/SpidL2', + ], + 'exceptionMessage' => 'SAML response validation error: minimum SPID Level not enforced', + ]); + } + public function testInvalidResponseIssueInstant() { $this->runValidationTest([ @@ -272,6 +283,9 @@ protected function runValidationTest(array $testSettings) $compiledResponseXML = str_replace('{{IssueInstant}}', SAMLUtils::parseTime2SAML(time()), $responseXML); $compiledResponseXML = str_replace('{{ResponseIssueInstant}}', $testSettings['responseIssueInstant'] ?? SAMLUtils::parseTime2SAML(time()), $compiledResponseXML); $compiledResponseXML = str_replace('{{AssertionIssueInstant}}', $testSettings['assertionIssueInstant'] ?? SAMLUtils::parseTime2SAML(time()), $compiledResponseXML); + foreach ($testSettings['config'] ?? [] as $key => $value) { + $this->app['config']->set($key, $value); + } $this->setSPIDAuthMock()->withLastResponseXML($compiledResponseXML); $this->withoutExceptionHandling(); $this->expectException(SPIDLoginException::class); diff --git a/tests/SPIDAuthBaseTestCase.php b/tests/SPIDAuthBaseTestCase.php index d433cf3..64111bf 100644 --- a/tests/SPIDAuthBaseTestCase.php +++ b/tests/SPIDAuthBaseTestCase.php @@ -47,10 +47,11 @@ protected function getPackageProviders($app) return ['Italia\SPIDAuth\ServiceProvider']; } - protected function setSPIDAuthMock($spidLevel = 1) + protected function setSPIDAuthMock(?array $testSettings = []) { $testRedirectURL = $this->app['config']->get('spid-idps.test.singleSignOnService.url'); - $responseXML = file_get_contents(__DIR__ . "/responses/valid_level{$spidLevel}.xml"); + $responseXMLFile = ($testSettings['responseXmlFile'] ?? false) ? $testSettings['responseXmlFile'] : 'valid_level1.xml'; + $responseXML = file_get_contents(__DIR__ . '/responses/' . $responseXMLFile); $compiledResponseXML = str_replace('{{IssueInstant}}', SAMLUtils::parseTime2SAML(time()), $responseXML); $compiledResponseXML = str_replace('{{ResponseIssueInstant}}', SAMLUtils::parseTime2SAML(time()), $compiledResponseXML); $compiledResponseXML = str_replace('{{AssertionIssueInstant}}', SAMLUtils::parseTime2SAML(time()), $compiledResponseXML); @@ -79,11 +80,9 @@ protected function setSPIDAuthMock($spidLevel = 1) ' ); $SAMLAuth->shouldReceive('getLastResponseXML')->andReturn($compiledResponseXML)->byDefault(); - $SAMLAuth->shouldReceive('getSessionIndex')->andReturnUsing(function () use ($spidLevel) { - return $spidLevel > 1 ? null : 'sessionIndex'; - }); + $SAMLAuth->shouldReceive('getLastMessageId')->andReturn('sessionId'); $SAMLAuth->shouldReceive('getNameId')->andReturn('nameId'); - $SAMLAuth->shouldReceive('logout')->with(URL::to($this->afterLogoutURL), [], 'nameId', 'sessionIndex', false, SAMLConstants::NAMEID_TRANSIENT, 'spid-testenv')->andReturn( + $SAMLAuth->shouldReceive('logout')->with(URL::to($this->afterLogoutURL), [], 'nameId', 'sessionId', false, SAMLConstants::NAMEID_TRANSIENT, 'spid-testenv')->andReturn( Response::redirectTo($this->logoutURL) )->byDefault(); $SAMLAuth->shouldReceive('getErrors')->andReturn(false)->byDefault(); @@ -91,7 +90,7 @@ protected function setSPIDAuthMock($spidLevel = 1) $SAMLAuth->shouldReceive('getSettings')->andReturn($SAMLAuth); $SAMLAuth->shouldReceive('withErrors')->andReturnUsing(function () { - return m::self()->shouldReceive('logout')->with(URL::to($this->afterLogoutURL), [], 'nameId', 'sessionIndex', false, SAMLConstants::NAMEID_TRANSIENT, 'spid-testenv')->andThrow( + return m::self()->shouldReceive('logout')->with(URL::to($this->afterLogoutURL), [], 'nameId', 'sessionId', false, SAMLConstants::NAMEID_TRANSIENT, 'spid-testenv')->andThrow( new SAMLError( 'The IdP does not support Single Log Out', SAMLError::SAML_SINGLE_LOGOUT_NOT_SUPPORTED diff --git a/tests/SPIDAuthTest.php b/tests/SPIDAuthTest.php index 9a1a1e2..56fa8a7 100644 --- a/tests/SPIDAuthTest.php +++ b/tests/SPIDAuthTest.php @@ -25,7 +25,7 @@ public function testLogin() public function testLoginIfAuthenticated() { $response = $this->withSession([ - 'spid_sessionIndex' => 'sessionIndex', + 'spid_sessionId' => 'sessionId', ])->get($this->loginURL); $response->assertRedirect($this->afterLoginURL); @@ -34,7 +34,7 @@ public function testLoginIfAuthenticated() public function testLoginIfAuthenticatedWithIntendedURL() { $response = $this->withSession([ - 'spid_sessionIndex' => 'sessionIndex', + 'spid_sessionId' => 'sessionId', 'url.intended' => 'intendedURL', ])->get($this->loginURL); @@ -44,7 +44,7 @@ public function testLoginIfAuthenticatedWithIntendedURL() public function testDoLoginIfAuthenticated() { $response = $this->withSession([ - 'spid_sessionIndex' => 'sessionIndex', + 'spid_sessionId' => 'sessionId', ])->post($this->doLoginURL); $response->assertRedirect($this->afterLoginURL); @@ -53,7 +53,7 @@ public function testDoLoginIfAuthenticated() public function testDoLoginIfAuthenticatedWithIntendedURL() { $response = $this->withSession([ - 'spid_sessionIndex' => 'sessionIndex', + 'spid_sessionId' => 'sessionId', 'url.intended' => 'intendedURL', ])->post($this->doLoginURL); @@ -114,11 +114,10 @@ public function testDoLogin() $response->assertRedirect(); } - public function testAcs($spidLevel = 1) + public function testAcs() { Event::fake(); - $this->setSPIDAuthMock($spidLevel); - $expectedSessionIndex = $spidLevel > 1 ? 'RANDOM_STRING' : 'sessionIndex'; + $this->setSPIDAuthMock(); $response = $this->withCookies([ 'spid_lastRequestId' => 'UNIQUE_ID', @@ -127,7 +126,7 @@ public function testAcs($spidLevel = 1) ])->post($this->acsURL); $response->assertSessionHas('spid_idpEntityName', 'Test IdP'); - $response->assertSessionHas('spid_sessionIndex', $expectedSessionIndex); + $response->assertSessionHas('spid_sessionId', 'sessionId'); $response->assertSessionHas('spid_nameId', 'nameId'); $response->assertSessionHas('spid_user'); $response->assertRedirect($this->afterLoginURL); @@ -165,7 +164,7 @@ public function testAcsWithIntendedURL() $this->assertFalse(cache()->has('RANDOM_STRING')); $response->assertSessionHas('spid_idpEntityName', 'Test IdP'); - $response->assertSessionHas('spid_sessionIndex', 'sessionIndex'); + $response->assertSessionHas('spid_sessionId', 'sessionId'); $response->assertSessionHas('spid_nameId', 'nameId'); $response->assertSessionHas('spid_user'); $response->assertRedirect('intendedURL'); @@ -260,14 +259,34 @@ public function testAcsWithReplayAttack() $response->assertStatus(500); } - public function testAcsWithSpidLevel2() + public function testAcsWithSPIDLevel2() { - $this->testAcs(2); + $this->setSPIDAuthMock([ + 'responseXmlFile' => 'valid_level2.xml', + ]); + + $response = $this->withCookies([ + 'spid_lastRequestId' => 'UNIQUE_ID', + 'spid_lastRequestIssueInstant' => SAMLUtils::parseTime2SAML(time()), + 'spid_idp' => 'test', + ])->post($this->acsURL); + + $response->assertRedirect(); } - public function testAcsWithSpidLevel3() + public function testAcsWithSPIDLevel3() { - $this->testAcs(3); + $this->setSPIDAuthMock([ + 'responseXmlFile' => 'valid_level3.xml', + ]); + + $response = $this->withCookies([ + 'spid_lastRequestId' => 'UNIQUE_ID', + 'spid_lastRequestIssueInstant' => SAMLUtils::parseTime2SAML(time()), + 'spid_idp' => 'test', + ])->post($this->acsURL); + + $response->assertRedirect(); } public function testLogout() @@ -276,7 +295,7 @@ public function testLogout() $response = $this->get($this->logoutURL); - $response->assertSessionMissing('spid_sessionIndex'); + $response->assertSessionMissing('spid_sessionId'); $response->assertSessionMissing('spid_nameId'); $response->assertRedirect($this->logoutURL); } @@ -357,7 +376,7 @@ public function testLogoutSpOnly() $response = $this->get($this->logoutURL); - $response->assertSessionMissing('spid_sessionIndex'); + $response->assertSessionMissing('spid_sessionId'); $response->assertSessionMissing('spid_nameId'); $response->assertRedirect($this->afterLogoutURL); Event::assertDispatched(LogoutEvent::class, function ($e) {