From 44b99a6ada56fbaf8d7f75a879b8a081c4cf250b Mon Sep 17 00:00:00 2001 From: Tim van Dijen <tvdijen@gmail.com> Date: Mon, 12 Jun 2023 22:50:42 +0200 Subject: [PATCH] Migrate samlp:Scoping + sub-elements to new interface --- modules/saml/src/Auth/Source/SP.php | 31 +++++++---- modules/saml/src/IdP/SAML2.php | 19 +++++-- tests/modules/saml/src/Auth/Source/SPTest.php | 51 ++++++++++--------- 3 files changed, 63 insertions(+), 38 deletions(-) diff --git a/modules/saml/src/Auth/Source/SP.php b/modules/saml/src/Auth/Source/SP.php index 1693e5883..a0f8403eb 100644 --- a/modules/saml/src/Auth/Source/SP.php +++ b/modules/saml/src/Auth/Source/SP.php @@ -14,7 +14,7 @@ use SimpleSAML\SAML2\Exception\ArrayValidationException; use SimpleSAML\SAML2\Exception\Protocol\{NoAvailableIDPException, NoPassiveException, NoSupportedIDPException}; use SimpleSAML\SAML2\XML\md\ContactPerson; use SimpleSAML\SAML2\XML\saml\NameID; -use SimpleSAML\SAML2\XML\samlp\Extensions; +use SimpleSAML\SAML2\XML\samlp\{Extensions, IDPEntry, IDPList, RequesterID, Scoping}; use SimpleSAML\Store\StoreFactory; use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; use Symfony\Component\HttpFoundation\{RedirectResponse, Request, Response}; @@ -565,39 +565,50 @@ class SP extends Auth\Source $ar->setNameIdPolicy($state['saml:NameIDPolicy']); } + $proxyCount = $idpList = null; $requesterID = []; /* Only check for real info for Scoping element if we are going to send Scoping element */ if ($this->disable_scoping !== true && $idpMetadata->getOptionalBoolean('disable_scoping', false) !== true) { + $idpEntry = []; if (isset($state['IDPList'])) { - $ar->setIDPList($state['IDPList']); + $idpList = $state['IDPList']; } elseif (!empty($this->metadata->getOptionalArray('IDPList', []))) { - $ar->setIDPList($this->metadata->getArray('IDPList')); + foreach ($this->metadata->getArray('IDPList') as $entry) { + $idpEntry[] = new IDPEntry($entry); + } + $idpList = new IDPList($idpEntry); } elseif (!empty($idpMetadata->getOptionalArray('IDPList', []))) { - $ar->setIDPList($idpMetadata->getArray('IDPList')); + foreach ($idpMetadata->getArray('IDPList') as $entry) { + $idpEntry[] = new IDPEntry($entry); + } + $idpList = new IDPList($idpEntry); } if (isset($state['saml:ProxyCount']) && $state['saml:ProxyCount'] !== null) { - $ar->setProxyCount($state['saml:ProxyCount']); + $proxyCount = $state['saml:ProxyCount']; } elseif ($idpMetadata->hasValue('ProxyCount')) { - $ar->setProxyCount($idpMetadata->getInteger('ProxyCount')); + $proxyCount = $idpMetadata->getInteger('ProxyCount'); } elseif ($this->metadata->hasValue('ProxyCount')) { - $ar->setProxyCount($this->metadata->getInteger('ProxyCount')); + $proxyCount = $this->metadata->getInteger('ProxyCount'); } $requesterID = []; if (isset($state['saml:RequesterID'])) { - $requesterID = $state['saml:RequesterID']; + foreach ($state['saml:RequesterID'] as $requesterId) { + $requesterID[] = new RequesterID($requesterId); + } } if (isset($state['core:SP'])) { - $requesterID[] = $state['core:SP']; + $requesterID[] = new RequesterID($state['core:SP']); } } else { Logger::debug('Disabling samlp:Scoping for ' . var_export($idpMetadata->getString('entityid'), true)); } - $ar->setRequesterID($requesterID); + $scoping = new Scoping($proxyCount, $idpList, $requesterID); + $ar->setScoping($scoping); // If the downstream SP has set extensions then use them. // Otherwise use extensions that might be defined in the local SP (only makes sense in a proxy scenario) diff --git a/modules/saml/src/IdP/SAML2.php b/modules/saml/src/IdP/SAML2.php index 92de48445..b83b2626e 100644 --- a/modules/saml/src/IdP/SAML2.php +++ b/modules/saml/src/IdP/SAML2.php @@ -24,6 +24,7 @@ use Symfony\Bridge\PsrHttpMessage\Factory\{HttpFoundationFactory, PsrHttpFactory use Symfony\Component\HttpFoundation\{Request, Response}; use function array_key_exists; +use function array_map; use function array_merge; use function array_unique; use function array_unshift; @@ -419,12 +420,24 @@ class SAML2 $relayState = $request->getRelayState(); $requestId = $request->getId(); - $IDPList = $request->getIDPList(); - $ProxyCount = $request->getProxyCount(); + $scoping = $request->getScoping(); + + $ProxyCount = $scoping->getProxyCount(); if ($ProxyCount !== null) { $ProxyCount--; } - $RequesterID = $request->getRequesterID(); + + if ($scoping->getIDPList() !== null) { + $IDPList = ($scoping->getIDPList()->toArray())['IDPEntry']; + } else { + $IDPList = []; + } + + $RequesterID = $scoping->getRequesterID(); + if ($RequesterID !== null) { + $RequesterID = array_map('strval', $RequesterID); + } + $forceAuthn = $request->getForceAuthn(); $isPassive = $request->getIsPassive(); $consumerURL = $request->getAssertionConsumerServiceURL(); diff --git a/tests/modules/saml/src/Auth/Source/SPTest.php b/tests/modules/saml/src/Auth/Source/SPTest.php index eecb8fb43..d32dc0213 100644 --- a/tests/modules/saml/src/Auth/Source/SPTest.php +++ b/tests/modules/saml/src/Auth/Source/SPTest.php @@ -14,6 +14,7 @@ use SimpleSAML\SAML2\Constants as C; use SimpleSAML\SAML2\Exception\Protocol\{NoAvailableIDPException, NoSupportedIDPException}; use SimpleSAML\SAML2\Utils\XPath; use SimpleSAML\SAML2\XML\saml\NameID; +use SimpleSAML\SAML2\XML\samlp\{IDPEntry, IDPList}; use SimpleSAML\Test\Metadata\MetaDataStorageSourceTest; use SimpleSAML\TestUtils\ClearStateTestCase; use SimpleSAML\Test\Utils\{ExitTestException, SpTester}; @@ -497,12 +498,12 @@ class SPTest extends ClearStateTestCase public function testSPIdpListScoping(): void { $ar = $this->createAuthnRequest([ - 'IDPList' => ['https://scope.example.com'] + 'IDPList' => new IDPList([new IDPEntry('https://scope.example.com')]), ]); $this->assertContains( - 'https://scope.example.com', - $ar->getIDPList() + (new IDPEntry('https://scope.example.com'))->toArray(), + ($ar->getScoping()->getIDPList()->toArray())['IDPEntry'], ); } @@ -516,8 +517,8 @@ class SPTest extends ClearStateTestCase $ar = $this->createAuthnRequest([]); $this->assertContains( - 'https://scope.example.com', - $ar->getIDPList() + (new IDPEntry('https://scope.example.com'))->toArray(), + ($ar->getScoping()->getIDPList()->toArray())['IDPEntry'], ); } @@ -541,8 +542,8 @@ class SPTest extends ClearStateTestCase } catch (ExitTestException $e) { ['ar' => $ar] = $e->getTestResult(); $this->assertContains( - 'https://scope.example.com', - $ar->getIDPList() + (new IDPEntry('https://scope.example.com'))->toArray(), + ($ar->getScoping()->getIDPList()->toArray())['IDPEntry'], ); } } @@ -555,7 +556,7 @@ class SPTest extends ClearStateTestCase * @dataProvider getScopingOrders */ public function testSPIdpListScopingOrder( - ?array $stateIdpList, + ?IDPList $stateIdpList, ?array $idpConfigArray, ?array $remoteMetadata, string $expectedScope @@ -583,8 +584,8 @@ class SPTest extends ClearStateTestCase ['ar' => $ar] = $e->getTestResult(); $this->assertContains( - $expectedScope, - $ar->getIDPList() + (new IDPEntry($expectedScope))->toArray(), + ($ar->getScoping()->getIDPList()->toArray())['IDPEntry'], ); } } @@ -593,34 +594,34 @@ class SPTest extends ClearStateTestCase { return [ [ - 'stateIdpList' => ['https//scope1.example.com'], - 'idpConfigArray' => ['https//scope2.example.com'], - 'remoteMetadata' => ['https//scope3.example.com'], - 'expectedScope' => 'https//scope1.example.com' + 'stateIdpList' => new IDPList([new IDPEntry('https://scope1.example.com')]), + 'idpConfigArray' => ['https://scope2.example.com'], + 'remoteMetadata' => ['https://scope3.example.com'], + 'expectedScope' => 'https://scope1.example.com' ], [ 'stateIdpList' => null, - 'idpConfigArray' => ['https//scope2.example.com'], - 'remoteMetadata' => ['https//scope3.example.com'], - 'expectedScope' => 'https//scope3.example.com' + 'idpConfigArray' => ['https://scope2.example.com'], + 'remoteMetadata' => ['https://scope3.example.com'], + 'expectedScope' => 'https://scope3.example.com' ], [ 'stateIdpList' => null, 'idpConfigArray' => null, - 'remoteMetadata' => ['https//scope3.example.com'], - 'expectedScope' => 'https//scope3.example.com' + 'remoteMetadata' => ['https://scope3.example.com'], + 'expectedScope' => 'https://scope3.example.com' ], [ - 'stateIdpList' => ['https//scope1.example.com'], + 'stateIdpList' => new IDPList([new IDPEntry('https://scope1.example.com')]), 'idpConfigArray' => null, - 'remoteMetadata' => ['https//scope3.example.com'], - 'expectedScope' => 'https//scope1.example.com' + 'remoteMetadata' => ['https://scope3.example.com'], + 'expectedScope' => 'https://scope1.example.com' ], [ - 'stateIdpList' => ['https//scope1.example.com'], - 'idpConfigArray' => ['https//scope2.example.com'], + 'stateIdpList' => new IDPList([new IDPEntry('https://scope1.example.com')]), + 'idpConfigArray' => ['https://scope2.example.com'], 'remoteMetadata' => null, - 'expectedScope' => 'https//scope1.example.com' + 'expectedScope' => 'https://scope1.example.com' ] ]; } -- GitLab