diff --git a/.env.test b/.env.test index d126baebb..b828296cb 100644 --- a/.env.test +++ b/.env.test @@ -10,3 +10,4 @@ administrator_teams="'urn:collab:group:dev.openconext.local:dev:openconext:local jira_test_mode_storage_path='/var/www/html/var/issues.json' authorization_attribute_name='eduPersonEntitlement' surfconext_responsible_authorization='urn:mace:surfnet.nl:surfnet.nl:sab:role:SURFconext-verantwoordelijke' +test_idp_entity_ids='["http://mock-idp","test-idp-1"]' diff --git a/tests/webtests/Manage/Client/ClientResult.php b/tests/webtests/Manage/Client/ClientResult.php index 4db95df02..8d4c31407 100644 --- a/tests/webtests/Manage/Client/ClientResult.php +++ b/tests/webtests/Manage/Client/ClientResult.php @@ -39,13 +39,16 @@ class ClientResult implements ClientResultInterface private $teamName; + private string $institutionId; + public function __construct( string $protocol, string $id, string $entityId, ?string $metadataUrl, string $name, - ?string $teamName + ?string $teamName, + ?string $institutionId, ) { $this->id = $id; $this->protocol = $protocol; @@ -56,6 +59,7 @@ public function __construct( $this->metadataUrl = $metadataUrl; $this->name = $name; $this->teamName = $teamName; + $this->institutionId = $institutionId; if ($teamName === null) { $this->teamName = 'urn:collab:group:vm.openconext.org:demo:openconext:org:surf.nl'; } @@ -87,7 +91,8 @@ public function getEntityResult(): array $this->metadataUrl, $this->name, str_replace('_', '-', $this->protocol), - $this->teamName + $this->teamName, + $this->institutionId ); return json_decode($data, true); } @@ -116,7 +121,8 @@ public static function decode($data): self $data['entityId'], $data['metadataUrl'], $data['name'], - $data['teamName'] + $data['teamName'], + $data['institutionId'], ); } @@ -129,6 +135,7 @@ public function encode(): array 'metadataUrl' => $this->metadataUrl, 'name' => $this->name, 'teamName' => $this->teamName, + 'institutionId' => $this->institutionId, ]; } } diff --git a/tests/webtests/Manage/Client/FakeIdentityProviderClient.php b/tests/webtests/Manage/Client/FakeIdentityProviderClient.php index 51cd1b02e..9df27e6ed 100644 --- a/tests/webtests/Manage/Client/FakeIdentityProviderClient.php +++ b/tests/webtests/Manage/Client/FakeIdentityProviderClient.php @@ -26,14 +26,16 @@ class FakeIdentityProviderClient implements IdentityProviderRepository { + private string $path = __DIR__ . '/../../../../var/webtest-idps.json'; /** * @var ClientResult[] */ private $entities = []; - public function registerEntity(string $protocol, string $id, string $entityId, string $name) + public function registerEntity(string $protocol, string $id, string $entityId, string $name, string $institutionId = '') { - $this->entities[$id] = new ClientResult($protocol, $id, $entityId, null, $name, null); + $this->entities[$id] = new ClientResult($protocol, $id, $entityId, null, $name, null, $institutionId); + $this->storeEntities(); } /** @@ -41,6 +43,7 @@ public function registerEntity(string $protocol, string $id, string $entityId, s */ public function findAll() { + $this->load(); $list = []; foreach ($this->entities as $manageResult) { $list[] = IdentityProviderFactory::fromManageResult($manageResult->getEntityResult()); @@ -50,6 +53,7 @@ public function findAll() public function findByEntityId(EntityId $entityId): ?IdentityProvider { + $this->load(); foreach ($this->entities as $manageResult) { $entity = IdentityProviderFactory::fromManageResult($manageResult->getEntityResult()); if ($entity->getEntityId() === (string) $entityId) { @@ -66,10 +70,46 @@ public function findByEntityId(EntityId $entityId): ?IdentityProvider */ public function findByInstitutionId(InstitutionId $institutionId): array { + $this->load(); $list = []; foreach ($this->entities as $manageResult) { $list[] = IdentityProviderFactory::fromManageResult($manageResult->getEntityResult()); } return $list; } + + + private function read() + { + return json_decode(file_get_contents($this->path), true); + } + + private function write(array $data) + { + file_put_contents($this->path, json_encode($data)); + } + + private function storeEntities() + { + // Also store the new entity in the on-file storage + $data = []; + foreach ($this->entities as $identifier => $entity) { + $data[$identifier] = $entity->encode(); + } + $this->write($data); + } + + private function load() + { + $data = $this->read(); + foreach ($data as $id => $rawClientResult) { + if (array_key_exists('protocol', $rawClientResult)) { + $this->entities[$id] = ClientResult::decode($rawClientResult); + continue; + } + if (array_key_exists('json', $rawClientResult)) { + $this->entities[$id] = ClientResultRaw::decode($rawClientResult); + } + } + } } diff --git a/tests/webtests/Manage/Client/FakeQueryClient.php b/tests/webtests/Manage/Client/FakeQueryClient.php index 6248c6dc4..b4bf2f456 100644 --- a/tests/webtests/Manage/Client/FakeQueryClient.php +++ b/tests/webtests/Manage/Client/FakeQueryClient.php @@ -55,9 +55,10 @@ public function registerEntity( string $entityId, ?string $metadataUrl, string $name, - ?string $teamName = null + ?string $teamName = null, + ?string $institutionId = '', ) { - $this->entities[$id] = new ClientResult($protocol, $id, $entityId, $metadataUrl, $name, $teamName); + $this->entities[$id] = new ClientResult($protocol, $id, $entityId, $metadataUrl, $name, $teamName, $institutionId); $this->storeEntities(); } @@ -142,6 +143,7 @@ public function findResourceServerByEntityId($entityId, $state) $searchResults[] = ManageEntity::fromApiResponse($result); } } + return $searchResults; } public function findByManageIdAndProtocol(string $manageId, string $protocol) :? ManageEntity diff --git a/tests/webtests/Manage/Client/template/ccc.json b/tests/webtests/Manage/Client/template/ccc.json index 7d9c086ed..7bb4ccd92 100644 --- a/tests/webtests/Manage/Client/template/ccc.json +++ b/tests/webtests/Manage/Client/template/ccc.json @@ -36,6 +36,7 @@ "OrganizationDisplayName:nl": "%5$s Organisation Name Dutch", "OrganizationURL:nl": "%5$s Organisation Url Dutch", "coin:service_team_id": "%7$s", + "coin:institution_id": "%8$s", "isPublicClient": true }, "allowedEntities": [], diff --git a/tests/webtests/Manage/Client/template/oidc10.json b/tests/webtests/Manage/Client/template/oidc10.json index ce6fb8923..61dda5267 100644 --- a/tests/webtests/Manage/Client/template/oidc10.json +++ b/tests/webtests/Manage/Client/template/oidc10.json @@ -64,6 +64,7 @@ "OrganizationDisplayName:nl": "%5$s Organisation Name Dutch", "OrganizationURL:nl": "%5$s Organisation Url Dutch", "coin:service_team_id": "%7$s", + "coin:institution_id": "%8$s", "isPublicClient": true }, "allowedEntities": [], diff --git a/tests/webtests/Manage/Client/template/saml20_idp.json b/tests/webtests/Manage/Client/template/saml20_idp.json index 3f6e8f5ab..52a612d3f 100644 --- a/tests/webtests/Manage/Client/template/saml20_idp.json +++ b/tests/webtests/Manage/Client/template/saml20_idp.json @@ -21,7 +21,8 @@ "metaDataFields": { "name:en": "%5$s Name English", "name:nl": "%5$s Name Dutch", - "coin:service_team_id": "%7$s" + "coin:service_team_id": "%7$s", + "coin:institution_id": "%8$s" }, "eid": 31 } diff --git a/tests/webtests/Manage/Client/template/saml20_sp.json b/tests/webtests/Manage/Client/template/saml20_sp.json index a53726291..0929bf7c9 100644 --- a/tests/webtests/Manage/Client/template/saml20_sp.json +++ b/tests/webtests/Manage/Client/template/saml20_sp.json @@ -64,6 +64,7 @@ "OrganizationDisplayName:nl": "%5$s Organisation Name Dutch", "OrganizationURL:nl": "%5$s Organisation Url Dutch", "coin:service_team_id": "%7$s", + "coin:institution_id": "%8$s", "logo:0:url": "%3$s\/images\/logo.png", "logo:0:width": 100, "logo:0:height": 100, diff --git a/tests/webtests/SurfConextResponsibleTest.php b/tests/webtests/SurfConextResponsibleTest.php index 31eca756e..a9a6ba202 100644 --- a/tests/webtests/SurfConextResponsibleTest.php +++ b/tests/webtests/SurfConextResponsibleTest.php @@ -20,18 +20,126 @@ class SurfConextResponsibleTest extends WebTestCase { + private string $institutionId = 'ACME Corporation'; public function setUp(): void { parent::setUp(); - $this->loadFixtures(); - $this->teamsQueryClient->registerTeam('demo:openconext:org:surf.nl', 'data'); - $this->logInSurfConextResponsible('ACME Corporation'); + $this->teamsQueryClient->registerTeam('demo:openconext:org:acme.nl', 'data'); } public function test_after_login_i_am_on_connections_page() { - $crawler = self::$pantherClient->getCrawler(); - self::assertEquals('/connections', $crawler->getBaseHref()); + $this->logInSurfConextResponsible($this->institutionId); + $url = self::$pantherClient->getCurrentURL(); + $urlParts = parse_url($url); + self::assertEquals('/connections', $urlParts['path']); + self::assertOnPage('No entities found'); // At this point there should be no entities + } + + public function test_entities_are_listed_on_the_page() + { + $this->registerManageEntity( + 'test', + 'saml20_sp', + 'aee8f00d-428a-4fbc-9cf8-ad2f3b2af589', + 'ACME Anvil', + 'http://acme-anvil', + 'https://acme-anvil.example.com/metadata', + 'demo:openconext:org:acme.nl', + $this->institutionId, + ); + $this->logInSurfConextResponsible($this->institutionId); + $this->assertOnPage('ACME Anvil Name English'); + // When logging in with only the SURF representative, we do not know the service the entity is associated with + $this->assertOnPage('Unknown service name'); + } + + public function test_entities_are_listed_on_the_page_with_connected_idp() + { + $this->registerManageEntity( + 'test', + 'saml20_sp', + 'aee8f00d-428a-4fbc-9cf8-ad2f3b2af589', + 'ACME Anvil', + 'http://acme-anvil', + 'https://acme-anvil.example.com/metadata', + 'demo:openconext:org:acme.nl', + $this->institutionId, + ); + $this->registerManageEntity( + 'test', + 'saml20_idp', + '1d4abec3-3f67-4b8a-b90d-ce56a3b0abc5', + 'Test IdP', + 'test-idp-1', + 'https://test-idp/metadata', + 'demo:openconext:org:acme.nl', + $this->institutionId, + ); + $this->logInSurfConextResponsible($this->institutionId); + $this->assertOnPage('ACME Anvil Name English'); + $this->assertOnPage('Test IdP Name Dutch'); + } + + public function test_entities_are_listed_on_the_page_with_connected_idp_with_multiple_sps() + { + $this->registerManageEntity( + 'test', + 'saml20_sp', + 'aee8f00d-428a-4fbc-9cf8-ad2f3b2af589', + 'ACME Anvil 1', + 'http://acme-anvil-1', + 'https://acme-anvil.example.com/metadata', + 'demo:openconext:org:acme.nl', + $this->institutionId, + ); + $this->registerManageEntity( + 'test', + 'saml20_sp', + 'bee8f00d-428a-4fbc-9cf8-ad2f3b2af589', + 'ACME Anvil 2', + 'http://acme-anvil-2', + 'https://acme-anvil.example.com/metadata', + 'demo:openconext:org:acme.nl', + $this->institutionId, + ); + $this->registerManageEntity( + 'test', + 'saml20_sp', + 'cee8f00d-428a-4fbc-9cf8-ad2f3b2af589', + 'ACME Anvil 3', + 'http://acme-anvil-3', + 'https://acme-anvil.example.com/metadata', + 'demo:openconext:org:acme.nl', + $this->institutionId, + ); + $this->registerManageEntity( + 'test', + 'saml20_sp', + 'fee8f00d-428a-4fbc-9cf8-ad2f3b2af589', + 'Should not be on page', + 'http://foobar', + 'https://foobar.example.com/metadata', + 'demo:openconext:org:acme.nl', + 'not-acme', + ); + $this->registerManageEntity( + 'test', + 'saml20_idp', + '1d4abec3-3f67-4b8a-b90d-ce56a3b0abc5', + 'Test IdP', + 'test-idp-1', + 'https://test-idp/metadata', + 'demo:openconext:org:acme.nl', + $this->institutionId, + ); + $this->logInSurfConextResponsible($this->institutionId); + $this->assertOnPage('ACME Anvil 1 Name English'); + $this->assertOnPage('ACME Anvil 2 Name English'); + $this->assertOnPage('ACME Anvil 3 Name English'); + // The fourth SP should not show up on the page + $this->assertNotOnPage('Should not be on page'); + $this->assertOnPage('Test IdP Name Dutch'); } } diff --git a/tests/webtests/WebTestCase.php b/tests/webtests/WebTestCase.php index 741670a1f..1b3d578a1 100644 --- a/tests/webtests/WebTestCase.php +++ b/tests/webtests/WebTestCase.php @@ -178,7 +178,8 @@ protected function registerManageEntity( string $name, string $entityId, ?string $metadataUrl = null, - ?string $teamName = null + ?string $teamName = null, + ?string $institutionId = '', ) { switch ($protocol) { case 'saml20_sp': @@ -191,11 +192,12 @@ protected function registerManageEntity( $name, $entityId, $metadataUrl, - $teamName + $teamName, + $institutionId, ); break; case 'saml20_idp': - $this->registerIdP($env, $protocol, $id, $name, $entityId); + $this->registerIdP($env, $protocol, $id, $name, $entityId, $institutionId); break; } } @@ -236,7 +238,8 @@ private function registerSp( string $name, string $entityId, ?string $metadataUrl = null, - ?string $teamName = null + ?string $teamName = null, + ?string $institutionId = '', ) { switch ($env) { case 'production': @@ -246,7 +249,8 @@ private function registerSp( $entityId, $metadataUrl, $name, - $teamName + $teamName, + $institutionId ); break; case 'test': @@ -256,7 +260,8 @@ private function registerSp( $entityId, $metadataUrl, $name, - $teamName + $teamName, + $institutionId ); break; default: @@ -264,7 +269,7 @@ private function registerSp( } } - private function registerIdP(string $env, string $protocol, string $id, string $name, string $entityId) + private function registerIdP(string $env, string $protocol, string $id, string $name, string $entityId, string $institutionId = '') { switch ($env) { case 'production': @@ -272,7 +277,8 @@ private function registerIdP(string $env, string $protocol, string $id, string $ $protocol, $id, $entityId, - $name + $name, + $institutionId ); break; case 'test': @@ -280,7 +286,8 @@ private function registerIdP(string $env, string $protocol, string $id, string $ $protocol, $id, $entityId, - $name + $name, + $institutionId ); break; default: @@ -374,7 +381,7 @@ protected function logInSurfConextResponsible(string $institutionId): void ); $select->click(); $entitlement = $crawler->filter(sprintf('input[name="urn:mace:dir:attribute-def:%s"]', $this->surfConextRepresentativeAttributeName)); - $entitlement->sendKeys($institutionId); + $entitlement->sendKeys('urn:mace:surfnet.nl:surfnet.nl:sab:organizationCode:' . $institutionId); // Now also send the attribute value that indicates this user is of role SurfConext representative $select = $crawler->filterXPath( sprintf( @@ -383,11 +390,12 @@ protected function logInSurfConextResponsible(string $institutionId): void ) ); $select->click(); - $entitlement = $crawler->filter(sprintf('input[name="urn:mace:dir:attribute-def:%s"]', $this->surfConextRepresentativeAttributeName)); - $entitlement->sendKeys($this->surfConextRepresentativeAttributeValue); + $entitlement = $crawler + ->filter(sprintf('input[name="urn:mace:dir:attribute-def:%s"]', $this->surfConextRepresentativeAttributeName)) + ->eq(1); // There are now 2 entitlement attrs, pick the second + $entitlement->sendKeys($this->surfConextRepresentativeAttributeValue); $this->finishLogin(); - self::$pantherClient->takeScreenshot('Foobar.png'); die; } private function finishLogin(): Crawler @@ -491,4 +499,9 @@ protected function getAuthorizationService(): AuthorizationService { return self::getContainer()->get(AuthorizationService::class); } + + protected function screenshot(string $filename) + { + self::$pantherClient->takeScreenshot($filename); + } }