Skip to content
Snippets Groups Projects
Commit 9c4b02ac authored by Patrick Radtke's avatar Patrick Radtke
Browse files

Initial tests for IDP initiated login

parent fbc06498
No related branches found
No related tags found
No related merge requests found
......@@ -2,6 +2,8 @@
namespace SimpleSAML\Metadata;
use SimpleSAML\Utils\ClearableState;
/**
* This file defines a class for metadata handling.
*
......@@ -9,7 +11,7 @@ namespace SimpleSAML\Metadata;
* @package SimpleSAMLphp
*/
class MetaDataStorageHandler
class MetaDataStorageHandler implements ClearableState
{
/**
* This static variable contains a reference to the current
......@@ -358,4 +360,14 @@ class MetaDataStorageHandler
return null;
}
/**
* Clear any metadata cached.
* Allows for metadata configuration to be changed and reloaded during a given request. Most useful
* when running phpunit tests and needing to alter config.php and metadata sources between test cases
*/
public static function clearInternalState()
{
self::$metadataHandler = null;
}
}
......@@ -402,11 +402,15 @@ class SAML2
$sessionLostParams = [
'spentityid' => $spEntityId,
'cookieTime' => time(),
];
if ($relayState !== null) {
$sessionLostParams['RelayState'] = $relayState;
}
/*
Putting cookieTime as the last parameter makes unit testing easier since we don't need to handle a
changing time component in the middle of the url
*/
$sessionLostParams['cookieTime'] = time();
$sessionLostURL = \SimpleSAML\Utils\HTTP::addURLParameters(
\SimpleSAML\Utils\HTTP::getSelfURLNoQuery(),
......
......@@ -18,7 +18,7 @@ class StateClearer
* Class that implement \SimpleSAML\Utils\ClearableState and should have clearInternalState called between tests
* @var array
*/
private $clearableState = ['SimpleSAML\Configuration'];
private $clearableState = ['SimpleSAML\Configuration', 'SimpleSAML\Metadata\MetaDataStorageHandler'];
/**
* Environmental variables to unset
......
......@@ -2,7 +2,12 @@
namespace SimpleSAML\Test\Module\saml\IdP;
class SAML2Test extends \PHPUnit_Framework_TestCase
use SimpleSAML\Configuration;
use SimpleSAML\IdP;
use SimpleSAML\Module\saml\IdP\SAML2;
use SimpleSAML\Test\Utils\ClearStateTestCase;
class SAML2Test extends ClearStateTestCase
{
public function testProcessSOAPAuthnRequest()
{
......@@ -37,4 +42,140 @@ class SAML2Test extends \PHPUnit_Framework_TestCase
\SimpleSAML\Module\saml\IdP\SAML2::processSOAPAuthnRequest($state);
}
/**
* Default values for the state array expected to be generated at the start of logins
* @var array
*/
private $defaultExpectedAuthState = [
'Responder' =>['\SimpleSAML\Module\saml\IdP\SAML2', 'sendResponse'],
'\SimpleSAML\Auth\State.exceptionFunc' => ['\SimpleSAML\Module\saml\IdP\SAML2', 'handleAuthError'],
'saml:RelayState' => null,
'saml:RequestId' => null,
'saml:IDPList' => Array (),
'saml:ProxyCount' => null,
'saml:RequesterID' => null,
'ForceAuthn' => false,
'isPassive' => false,
'saml:ConsumerURL' => 'SP-specific',
'saml:Binding' => 'SP-specific',
'saml:NameIDFormat' => null,
'saml:AllowCreate' => true,
'saml:Extensions' => null,
'saml:RequestedAuthnContext' => null];
/**
* Test that invoking the idp initiated endpoint with the minimum necessary parameters works.
*/
public function testIdPInitiatedLoginMinimumParams()
{
$state = $this->idpInitiatedHelper(['spentityid' => 'https://some-sp-entity-id']);
$this->assertEquals('https://some-sp-entity-id', $state['SPMetadata']['entityid']);
$this->assertStringStartsWith(
'http://idp.examlple.com/saml2/idp/SSOService.php?spentityid=https%3A%2F%2Fsome-sp-entity-id&cookie',
$state['\SimpleSAML\Auth\State.restartURL']
);
unset($state['saml:AuthnRequestReceivedAt']); // timestamp can't be tested in equality assertion
unset($state['SPMetadata']); // entityid asserted above
unset($state['\SimpleSAML\Auth\State.restartURL']); // url contains a cookie time which varies by test
$expectedState = $this->defaultExpectedAuthState;
$expectedState[ 'saml:ConsumerURL'] = 'https://example.com/Shibboleth.sso/SAML2/POST';
$expectedState[ 'saml:Binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST';
$this->assertEquals($expectedState, $state);
}
/**
* Test that invoking the idp initiated endpoint with the optional parameters works.
*/
public function testIdPInitiatedLoginOptionalParams()
{
$state = $this->idpInitiatedHelper([
'spentityid' => 'https://some-sp-entity-id',
'RelayState' => 'http://relay',
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:PAOS',
'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
]);
$this->assertEquals('https://some-sp-entity-id', $state['SPMetadata']['entityid']);
//currently only spentityid and relay state are used in the restart url.
$this->assertStringStartsWith(
'http://idp.examlple.com/saml2/idp/SSOService.php?'
. 'spentityid=https%3A%2F%2Fsome-sp-entity-id&RelayState=http%3A%2F%2Frelay&cookieTime',
$state['\SimpleSAML\Auth\State.restartURL']
);
unset($state['saml:AuthnRequestReceivedAt']); // timestamp can't be tested in equality assertion
unset($state['SPMetadata']); // entityid asserted above
unset($state['\SimpleSAML\Auth\State.restartURL']); // url contains a cookie time which varies by test
$expectedState = $this->defaultExpectedAuthState;
$expectedState['saml:ConsumerURL'] = 'https://example.com/Shibboleth.sso/SAML2/ECP';
$expectedState['saml:Binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:PAOS';
$expectedState['saml:NameIDFormat'] = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress';
$expectedState['saml:RelayState'] = 'http://relay';
$this->assertEquals($expectedState, $state);
}
/**
* Invoke IDP initiated login with the given query parameters.
* Callers should validate the return state array or confirm appropriate exceptions are returned.
*
* @param array $queryParams
* @return string[] The state array used for handling the authentication request.
*/
private function idpInitiatedHelper(array $queryParams)
{
/** @var $idpStub \PHPUnit_Framework_MockObject_MockObject|IdP */
$idpStub = $this->getMockBuilder(IdP::class)
->disableOriginalConstructor()
->getMock();
$idpMetadata = Configuration::loadFromArray([
'entityid' => 'https://idp-entity.id',
'saml20.ecp' => true, //enable additional bindings so we can test selection logic
]);
$idpStub->method("getConfig")
->willReturn($idpMetadata);
// phpcs:disable
$spMetadataXml = <<< 'EOT'
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://some-sp-entity-id">
<md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol">
<md:AssertionConsumerService index="1" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://example.com/Shibboleth.sso/SAML2/POST" />
<md:AssertionConsumerService index="2" Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS" Location="https://example.com/Shibboleth.sso/SAML2/ECP" />
</md:SPSSODescriptor>
</EntityDescriptor>
EOT;
// phpcs:enable
\SimpleSAML\Configuration::loadFromArray([
'baseurlpath' => 'https://idp.example.com/',
'metadata.sources' => [
["type" => "xml", 'xml' => $spMetadataXml],
],
], '', 'simplesaml');
// Since we aren't really running on a webserver some of the url calculations done, such as for restart url
// won't line up perfectly
$_REQUEST = $_REQUEST + $queryParams;
$_SERVER['HTTP_HOST'] = 'idp.examlple.com';
$_SERVER['REQUEST_URI'] = '/saml2/idp/SSOService.php?' . http_build_query($queryParams);
$state = [];
$idpStub->expects($this->once())
->method('handleAuthenticationRequest')
->with($this->callback(function ($arg) use (&$state) {
$state = $arg;
return true;
}));
SAML2::receiveAuthnRequest($idpStub);
return $state;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment