Skip to content

Commit

Permalink
Merge pull request #85 from italia/support-response-higher-spidlevel
Browse files Browse the repository at this point in the history
Support SPID level 2 and 3
  • Loading branch information
pdavide authored Jun 15, 2022
2 parents 0886ff5 + 7fd9c2a commit 6cb78f7
Show file tree
Hide file tree
Showing 22 changed files with 137 additions and 33 deletions.
9 changes: 5 additions & 4 deletions src/SPIDAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public function doLogin(): RedirectResponse
$idp = request('provider');
$this->checkIdp($idp);

$relayState = $this->getRandomRelayState();
$relayState = $this->getRandomString();
$idpRedirectTo = $this->getSAML($idp)->login($relayState, [], true, false, true);
$requestDocument = new DOMDocument();
SAMLUtils::loadXML($requestDocument, $this->getSAML($idp)->getLastRequestXML());
Expand Down Expand Up @@ -161,10 +161,11 @@ 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' => $this->getSAML($idp)->getSessionIndex()]);
session(['spid_sessionIndex' => $spidSessionIndex]);
session(['spid_nameId' => $this->getSAML($idp)->getNameId()]);
session(['spid_user' => $SPIDUser]);

Expand Down Expand Up @@ -752,12 +753,12 @@ protected function getIdpEntityName(string $responseXML)
}

/**
* Return a random string to be used as RelayState value.
* Return a random string.
*
* @return string random string
* @codeCoverageIgnore
*/
protected function getRandomRelayState(): string
protected function getRandomString(): string
{
return Str::random(32);
}
Expand Down
12 changes: 6 additions & 6 deletions tests/ResponseValidationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ public function testWrongAuthContextClassRef()
public function testInvalidResponseIssueInstant()
{
$this->runValidationTest([
'responseXmlFile' => 'valid.xml',
'responseXmlFile' => 'valid_level1.xml',
'responseIssueInstant' => 'invalid-response-issue-instant',
'exceptionMessage' => 'SAML response validation error: invalid IssueInstant attribute',
]);
Expand All @@ -207,7 +207,7 @@ public function testInvalidResponseIssueInstant()
public function testInvalidAssertionIssueInstant()
{
$this->runValidationTest([
'responseXmlFile' => 'valid.xml',
'responseXmlFile' => 'valid_level1.xml',
'assertionIssueInstant' => 'invalid-assertion-issue-instant',
'exceptionMessage' => 'SAML response validation error: invalid IssueInstant attribute',
]);
Expand All @@ -216,13 +216,13 @@ public function testInvalidAssertionIssueInstant()
public function testWrongResponseIssueInstant()
{
$this->runValidationTest([
'responseXmlFile' => 'valid.xml',
'responseXmlFile' => 'valid_level1.xml',
'responseIssueInstant' => SAMLUtils::parseTime2SAML(time() + 400),
'exceptionMessage' => 'SAML response validation error: wrong Response IssueInstant attribute',
]);

$this->runValidationTest([
'responseXmlFile' => 'valid.xml',
'responseXmlFile' => 'valid_level1.xml',
'responseIssueInstant' => SAMLUtils::parseTime2SAML(time() - 400),
'exceptionMessage' => 'SAML response validation error: wrong Response IssueInstant attribute',
]);
Expand All @@ -231,13 +231,13 @@ public function testWrongResponseIssueInstant()
public function testWrongAssertionIssueInstant()
{
$this->runValidationTest([
'responseXmlFile' => 'valid.xml',
'responseXmlFile' => 'valid_level1.xml',
'assertionIssueInstant' => SAMLUtils::parseTime2SAML(time() + 400),
'exceptionMessage' => 'SAML response validation error: wrong Assertion IssueInstant attribute',
]);

$this->runValidationTest([
'responseXmlFile' => 'valid.xml',
'responseXmlFile' => 'valid_level1.xml',
'assertionIssueInstant' => SAMLUtils::parseTime2SAML(time() - 400),
'exceptionMessage' => 'SAML response validation error: wrong Assertion IssueInstant attribute',
]);
Expand Down
10 changes: 6 additions & 4 deletions tests/SPIDAuthBaseTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ protected function getPackageProviders($app)
return ['Italia\SPIDAuth\ServiceProvider'];
}

protected function setSPIDAuthMock()
protected function setSPIDAuthMock($spidLevel = 1)
{
$testRedirectURL = $this->app['config']->get('spid-idps.test.singleSignOnService.url');
$responseXML = file_get_contents(__DIR__ . '/responses/valid.xml');
$responseXML = file_get_contents(__DIR__ . "/responses/valid_level{$spidLevel}.xml");
$compiledResponseXML = str_replace('{{IssueInstant}}', SAMLUtils::parseTime2SAML(time()), $responseXML);
$compiledResponseXML = str_replace('{{ResponseIssueInstant}}', SAMLUtils::parseTime2SAML(time()), $compiledResponseXML);
$compiledResponseXML = str_replace('{{AssertionIssueInstant}}', SAMLUtils::parseTime2SAML(time()), $compiledResponseXML);
Expand Down Expand Up @@ -79,7 +79,9 @@ protected function setSPIDAuthMock()
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" IssueInstant="' . SAMLUtils::parseTime2SAML(time()) . '" />'
);
$SAMLAuth->shouldReceive('getLastResponseXML')->andReturn($compiledResponseXML)->byDefault();
$SAMLAuth->shouldReceive('getSessionIndex')->andReturn('sessionIndex');
$SAMLAuth->shouldReceive('getSessionIndex')->andReturnUsing(function () use ($spidLevel) {
return $spidLevel > 1 ? null : 'sessionIndex';
});
$SAMLAuth->shouldReceive('getNameId')->andReturn('nameId');
$SAMLAuth->shouldReceive('logout')->with(URL::to($this->afterLogoutURL), [], 'nameId', 'sessionIndex', false, SAMLConstants::NAMEID_TRANSIENT, 'spid-testenv')->andReturn(
Response::redirectTo($this->logoutURL)
Expand Down Expand Up @@ -150,7 +152,7 @@ protected function setSPIDAuthMock()
});

$SPIDAuth->shouldReceive('getSAML')->andReturn($SAMLAuth);
$SPIDAuth->shouldReceive('getRandomRelayState')->andReturn('RANDOM_STRING');
$SPIDAuth->shouldReceive('getRandomString')->andReturn('RANDOM_STRING');
$this->app->instance('SPIDAuth', $SPIDAuth);

return $SAMLAuth;
Expand Down
17 changes: 14 additions & 3 deletions tests/SPIDAuthTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,11 @@ public function testDoLogin()
$response->assertRedirect();
}

public function testAcs()
public function testAcs($spidLevel = 1)
{
Event::fake();
$this->setSPIDAuthMock();
$this->setSPIDAuthMock($spidLevel);
$expectedSessionIndex = $spidLevel > 1 ? 'RANDOM_STRING' : 'sessionIndex';

$response = $this->withCookies([
'spid_lastRequestId' => 'UNIQUE_ID',
Expand All @@ -126,7 +127,7 @@ public function testAcs()
])->post($this->acsURL);

$response->assertSessionHas('spid_idpEntityName', 'Test IdP');
$response->assertSessionHas('spid_sessionIndex', 'sessionIndex');
$response->assertSessionHas('spid_sessionIndex', $expectedSessionIndex);
$response->assertSessionHas('spid_nameId', 'nameId');
$response->assertSessionHas('spid_user');
$response->assertRedirect($this->afterLoginURL);
Expand Down Expand Up @@ -259,6 +260,16 @@ public function testAcsWithReplayAttack()
$response->assertStatus(500);
}

public function testAcsWithSpidLevel2()
{
$this->testAcs(2);
}

public function testAcsWithSpidLevel3()
{
$this->testAcs(3);
}

public function testLogout()
{
$this->testAcs();
Expand Down
2 changes: 1 addition & 1 deletion tests/responses/empty_audiencerestriction.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}"
SessionIndex="{{SessionIndex}}">
SessionIndex="sessionIndex">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL1</saml:AuthnContextClassRef>
</saml:AuthnContext>
Expand Down
2 changes: 1 addition & 1 deletion tests/responses/empty_conditions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
NotOnOrAfter="{{NotOnOrAfter}}" />
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}"
SessionIndex="{{SessionIndex}}">
SessionIndex="sessionIndex">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL1</saml:AuthnContextClassRef>
</saml:AuthnContext>
Expand Down
2 changes: 1 addition & 1 deletion tests/responses/empty_conditions_notbefore.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}"
SessionIndex="{{SessionIndex}}">
SessionIndex="sessionIndex">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL1</saml:AuthnContextClassRef>
</saml:AuthnContext>
Expand Down
2 changes: 1 addition & 1 deletion tests/responses/empty_conditions_notonorafter.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}"
SessionIndex="{{SessionIndex}}">
SessionIndex="sessionIndex">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL1</saml:AuthnContextClassRef>
</saml:AuthnContext>
Expand Down
2 changes: 1 addition & 1 deletion tests/responses/empty_nameid.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}"
SessionIndex="{{SessionIndex}}">
SessionIndex="sessionIndex">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL1</saml:AuthnContextClassRef>
</saml:AuthnContext>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}"
SessionIndex="{{SessionIndex}}">
SessionIndex="sessionIndex">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL1</saml:AuthnContextClassRef>
</saml:AuthnContext>
Expand Down
2 changes: 1 addition & 1 deletion tests/responses/missing_destination.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}"
SessionIndex="{{SessionIndex}}">
SessionIndex="sessionIndex">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL1</saml:AuthnContextClassRef>
</saml:AuthnContext>
Expand Down
2 changes: 1 addition & 1 deletion tests/responses/missing_inresponseto.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}"
SessionIndex="{{SessionIndex}}">
SessionIndex="sessionIndex">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL1</saml:AuthnContextClassRef>
</saml:AuthnContext>
Expand Down
2 changes: 1 addition & 1 deletion tests/responses/missing_nameid_namequalifier.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}"
SessionIndex="{{SessionIndex}}">
SessionIndex="sessionIndex">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL1</saml:AuthnContextClassRef>
</saml:AuthnContext>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}"
SessionIndex="{{SessionIndex}}">
SessionIndex="sessionIndex">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL1</saml:AuthnContextClassRef>
</saml:AuthnContext>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}"
SessionIndex="{{SessionIndex}}">
SessionIndex="sessionIndex">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL1</saml:AuthnContextClassRef>
</saml:AuthnContext>
Expand Down
45 changes: 45 additions & 0 deletions tests/responses/valid_level2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<samlp:Response
Destination="{{AssertionConsumerURL}}"
ID="{{ResponseID}}"
InResponseTo="{{AuthnRequestID}}"
IssueInstant="{{ResponseIssueInstant}}"
Version="2.0" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">spid-testenv</saml:Issuer>

<samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>
<saml:Assertion ID="{{AssertionID}}" IssueInstant="{{AssertionIssueInstant}}"
Version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">spid-testenv</saml:Issuer>
<saml:Subject>
<saml:NameID
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
NameQualifier="{{NameIDNameQualifier}}">
{{NameID}}
</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData
InResponseTo="{{AuthnRequestID}}"
NotOnOrAfter="{{NotOnOrAfter}}"
Recipient="{{AssertionConsumerURL}}" />
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions
NotBefore="{{IssueInstant}}"
NotOnOrAfter="{{NotOnOrAfter}}">
<saml:AudienceRestriction>
<saml:Audience>{{Audience}}</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL2</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
{{Attributes}}
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
45 changes: 45 additions & 0 deletions tests/responses/valid_level3.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<samlp:Response
Destination="{{AssertionConsumerURL}}"
ID="{{ResponseID}}"
InResponseTo="{{AuthnRequestID}}"
IssueInstant="{{ResponseIssueInstant}}"
Version="2.0" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">spid-testenv</saml:Issuer>

<samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>
<saml:Assertion ID="{{AssertionID}}" IssueInstant="{{AssertionIssueInstant}}"
Version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">spid-testenv</saml:Issuer>
<saml:Subject>
<saml:NameID
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
NameQualifier="{{NameIDNameQualifier}}">
{{NameID}}
</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData
InResponseTo="{{AuthnRequestID}}"
NotOnOrAfter="{{NotOnOrAfter}}"
Recipient="{{AssertionConsumerURL}}" />
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions
NotBefore="{{IssueInstant}}"
NotOnOrAfter="{{NotOnOrAfter}}">
<saml:AudienceRestriction>
<saml:Audience>{{Audience}}</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL3</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
{{Attributes}}
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
2 changes: 1 addition & 1 deletion tests/responses/wrong_assertion_issuer_format.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}"
SessionIndex="{{SessionIndex}}">
SessionIndex="sessionIndex">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL1</saml:AuthnContextClassRef>
</saml:AuthnContext>
Expand Down
2 changes: 1 addition & 1 deletion tests/responses/wrong_assertion_version.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}"
SessionIndex="{{SessionIndex}}">
SessionIndex="sessionIndex">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL1</saml:AuthnContextClassRef>
</saml:AuthnContext>
Expand Down
2 changes: 1 addition & 1 deletion tests/responses/wrong_authcontextclassref.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}"
SessionIndex="{{SessionIndex}}">
SessionIndex="sessionIndex">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:SpidL1</saml:AuthnContextClassRef>
</saml:AuthnContext>
Expand Down
2 changes: 1 addition & 1 deletion tests/responses/wrong_issuer_format.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}"
SessionIndex="{{SessionIndex}}">
SessionIndex="sessionIndex">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL1</saml:AuthnContextClassRef>
</saml:AuthnContext>
Expand Down
2 changes: 1 addition & 1 deletion tests/responses/wrong_nameid_format.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="{{AuthnIstant}}"
SessionIndex="{{SessionIndex}}">
SessionIndex="sessionIndex">
<saml:AuthnContext>
<saml:AuthnContextClassRef>https://www.spid.gov.it/SpidL1</saml:AuthnContextClassRef>
</saml:AuthnContext>
Expand Down

0 comments on commit 6cb78f7

Please sign in to comment.