From 816a4e03b585db6db1d806160a637bb6a34150e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Fri, 17 Apr 2026 11:28:47 +0200 Subject: [PATCH 1/8] Move to routes response --- .../CredentialJsonLdContextController.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Controllers/VerifiableCredentials/CredentialJsonLdContextController.php b/src/Controllers/VerifiableCredentials/CredentialJsonLdContextController.php index 2d13f754..f78bb382 100644 --- a/src/Controllers/VerifiableCredentials/CredentialJsonLdContextController.php +++ b/src/Controllers/VerifiableCredentials/CredentialJsonLdContextController.php @@ -27,7 +27,6 @@ use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException; use SimpleSAML\Module\oidc\Services\LoggerService; use SimpleSAML\Module\oidc\Utils\Routes; -use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; /** @@ -81,7 +80,7 @@ public function context(string $credentialConfigurationId): Response return $this->routes->newResponse(null, Response::HTTP_NOT_FOUND); } - return new JsonResponse( + return $this->routes->newJsonResponse( $contextDocument, Response::HTTP_OK, ['Content-Type' => 'application/ld+json'], From cca05a8f519289352ff1b47b678c84916c837b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Thu, 23 Apr 2026 13:31:01 +0200 Subject: [PATCH 2/8] Add some debuging --- .../CredentialIssuerCredentialController.php | 51 ++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/src/Controllers/VerifiableCredentials/CredentialIssuerCredentialController.php b/src/Controllers/VerifiableCredentials/CredentialIssuerCredentialController.php index 3595a3a1..932be023 100644 --- a/src/Controllers/VerifiableCredentials/CredentialIssuerCredentialController.php +++ b/src/Controllers/VerifiableCredentials/CredentialIssuerCredentialController.php @@ -524,13 +524,20 @@ public function credential(Request $request): Response // Get valid claim paths so we can check if the user attribute is allowed to be included in the credential, // as per the credential configuration supported configuration. $validClaimPaths = $this->moduleConfig->getVciValidCredentialClaimPathsFor($resolvedCredentialIdentifier); - + $this->loggerService->debug( + 'CredentialIssuerCredentialController::credential: Valid claim paths for credential configuration ', + ['validClaimPaths' => $validClaimPaths], + ); // Map user attributes to credential claims $credentialSubject = []; // For JwtVcJson $disclosureBag = $this->verifiableCredentials->disclosureBagFactory()->build(); // For DcSdJwt $attributeToCredentialClaimPathMap = $this->moduleConfig->getVciUserAttributeToCredentialClaimPathMapFor( $resolvedCredentialIdentifier, ); + $this->loggerService->debug( + 'CredentialIssuerCredentialController::credential: Attribute to credential claim path map', + ['attributeToCredentialClaimPathMap' => $attributeToCredentialClaimPathMap], + ); foreach ($attributeToCredentialClaimPathMap as $mapEntry) { if (!is_array($mapEntry)) { $this->loggerService->warning( @@ -542,6 +549,11 @@ public function credential(Request $request): Response continue; } + $this->loggerService->debug( + 'Map entry: ', + ['mapEntry' => $mapEntry], + ); + $userAttributeName = key($mapEntry); if (!is_string($userAttributeName)) { $this->loggerService->warning( @@ -553,6 +565,10 @@ public function credential(Request $request): Response continue; } + $this->loggerService->debug( + 'User attribute name: ' . $userAttributeName, + ); + /** @psalm-suppress MixedAssignment */ $credentialClaimPath = current($mapEntry); if (!is_array($credentialClaimPath)) { @@ -574,6 +590,11 @@ public function credential(Request $request): Response continue; } + $this->loggerService->debug( + 'Credential claim path', + ['credentialClaimPath' => $credentialClaimPath], + ); + if (!isset($userAttributes[$userAttributeName])) { $this->loggerService->warning( 'Attribute "%s" does not exist in user attributes.', @@ -590,6 +611,7 @@ public function credential(Request $request): Response $userAttributes[$userAttributeName]; if ($credentialFormatId === CredentialFormatIdentifiersEnum::JwtVcJson->value) { + $this->loggerService->debug('JwtVcJson format detected, adding user attribute to credential subject.'); $this->verifiableCredentials->helpers()->arr()->setNestedValue( $credentialSubject, $attributeValue, @@ -598,6 +620,11 @@ public function credential(Request $request): Response } if (in_array($credentialFormatId, self::SD_JWT_FORMAT_IDS, true)) { + $this->loggerService->debug( + 'CredentialIssuerCredentialController::credential: Processing SD JWT credential format ID ' + . $credentialFormatId, + ); + // For now, we will only support disclosures for object properties. $claimName = array_pop($credentialClaimPath); if (!is_string($claimName)) { @@ -611,8 +638,17 @@ public function credential(Request $request): Response continue; } - if ($credentialFormatId === CredentialFormatIdentifiersEnum::VcSdJwt->value) { + $this->loggerService->debug('Claim name: ' . $claimName); + + if ( + $credentialFormatId === CredentialFormatIdentifiersEnum::VcSdJwt->value && + !in_array(ClaimsEnum::Credential_Subject->value, $credentialClaimPath, true) + ) { + $this->loggerService->debug('VC SD JWT - adding credential subject to claim path for claim "%s".'); array_unshift($credentialClaimPath, ClaimsEnum::Credential_Subject->value); + $this->loggerService->debug( + 'Credential claim path for credential subject: ' . print_r($credentialClaimPath, true), + ); } /** @psalm-suppress ArgumentTypeCoercion */ @@ -722,14 +758,16 @@ public function credential(Request $request): Response // Always start with the VCDM 2.0 base context URL (mandatory). $atContext = [AtContextsEnum::W3OrgNsCredentialsV2->value]; - // If a JSON-LD context document is configured for this credential, append the module-hosted - // context URL so that verifiers can resolve the custom credential subject terms. + // If a JSON-LD context document is configured for this credential, + // append the module-hosted context URL so that verifiers can + // resolve the custom credential subject terms. if ($this->moduleConfig->getVciCredentialJsonLdContextFor($resolvedCredentialIdentifier) !== null) { $atContext[] = $this->routes->urlCredentialJsonLdContext($resolvedCredentialIdentifier); } - // Append any additional context URLs declared in the credential configuration's @context field - // (skipping the base W3C URL, which is already first in the list). + // Append any additional context URLs declared in the credential + // configuration's @context field (skipping the base W3C URL, + // which is already first in the list). /** @psalm-suppress MixedAssignment */ $configuredContexts = $resolvedCredentialConfiguration[ClaimsEnum::AtContext->value] ?? []; if (is_array($configuredContexts)) { @@ -776,6 +814,7 @@ public function credential(Request $request): Response [ ClaimsEnum::Kid->value => $issuerDid . '#0', ], + disclosureBag: $disclosureBag, ); } From 2e33193d478e4d867a7d461c92c1fc9c248b3874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Thu, 23 Apr 2026 14:52:49 +0200 Subject: [PATCH 3/8] WIP --- composer.json | 2 +- routing/routes/routes.php | 3 + src/Codebooks/RoutesEnum.php | 1 + .../Admin/FederationTestController.php | 47 ++++++++++++++++ src/Factories/TemplateFactory.php | 7 +++ src/Utils/Routes.php | 5 ++ templates/tests/federation-discovery.twig | 55 +++++++++++++++++++ 7 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 templates/tests/federation-discovery.twig diff --git a/composer.json b/composer.json index 49b5e769..330db5fc 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "psr/container": "^2.0", "psr/log": "^3", "simplesamlphp/composer-module-installer": "^1.3", - "simplesamlphp/openid": "~v0.1.1", + "simplesamlphp/openid": "dev-entity-collection", "spomky-labs/base64url": "^2.0", "symfony/expression-language": "^7.4", "symfony/psr-http-message-bridge": "^7.4", diff --git a/routing/routes/routes.php b/routing/routes/routes.php index 82bbb0b2..b14f724c 100644 --- a/routing/routes/routes.php +++ b/routing/routes/routes.php @@ -77,6 +77,9 @@ $routes->add(RoutesEnum::AdminTestTrustMarkValidation->name, RoutesEnum::AdminTestTrustMarkValidation->value) ->controller([FederationTestController::class, 'trustMarkValidation']) ->methods([HttpMethodsEnum::GET->value, HttpMethodsEnum::POST->value]); + $routes->add(RoutesEnum::AdminTestFederationDiscovery->name, RoutesEnum::AdminTestFederationDiscovery->value) + ->controller([FederationTestController::class, 'federationDiscovery']) + ->methods([HttpMethodsEnum::GET->value, HttpMethodsEnum::POST->value]); $routes->add( RoutesEnum::AdminTestVerifiableCredentialIssuance->name, RoutesEnum::AdminTestVerifiableCredentialIssuance->value, diff --git a/src/Codebooks/RoutesEnum.php b/src/Codebooks/RoutesEnum.php index 0a3a70ce..f08f3ca8 100644 --- a/src/Codebooks/RoutesEnum.php +++ b/src/Codebooks/RoutesEnum.php @@ -28,6 +28,7 @@ enum RoutesEnum: string // Testing case AdminTestTrustChainResolution = 'admin/test/trust-chain-resolution'; case AdminTestTrustMarkValidation = 'admin/test/trust-mark-validation'; + case AdminTestFederationDiscovery = 'admin/test/federation-discovery'; case AdminTestVerifiableCredentialIssuance = 'admin/test/verifiable-credential-issuance'; diff --git a/src/Controllers/Admin/FederationTestController.php b/src/Controllers/Admin/FederationTestController.php index f9d60ebc..bc432623 100644 --- a/src/Controllers/Admin/FederationTestController.php +++ b/src/Controllers/Admin/FederationTestController.php @@ -169,4 +169,51 @@ public function trustMarkValidation(Request $request): Response RoutesEnum::AdminTestTrustMarkValidation->value, ); } + + + /** + * @throws \SimpleSAML\Error\ConfigurationError + * @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException + * @throws \SimpleSAML\Module\oidc\Exceptions\OidcException + */ + public function federationDiscovery(Request $request): Response + { + $trustAnchorId = null; + $isFormSubmitted = false; + $entities = []; + + if ($request->isMethod(Request::METHOD_POST)) { + $isFormSubmitted = true; + + !empty($trustAnchorId = $request->request->getString('trustAnchorId')) || + throw new OidcException('Empty Trust Anchor ID.'); + + try { + $entities = $this->federationWithArrayLogger->federationDiscovery()->discoverAndFetch( + trustAnchorId: $trustAnchorId, + forceRefresh: true, + ); + + } catch (\Throwable $exception) { + $this->arrayLogger->error(sprintf( + 'Error during entity discovery under Trust Anchor %s. Error was %s', + $trustAnchorId, + $exception->getMessage(), + )); + } + } + + $logMessages = $this->arrayLogger->getEntries(); + + return $this->templateFactory->build( + 'oidc:tests/federation-discovery.twig', + compact( + 'trustAnchorId', + 'logMessages', + 'isFormSubmitted', + 'entities', + ), + RoutesEnum::AdminTestFederationDiscovery->value, + ); + } } diff --git a/src/Factories/TemplateFactory.php b/src/Factories/TemplateFactory.php index 7e26f6e5..382d8845 100644 --- a/src/Factories/TemplateFactory.php +++ b/src/Factories/TemplateFactory.php @@ -142,6 +142,13 @@ protected function includeDefaultMenuItems(): void ), ); + $this->oidcMenu->addItem( + $this->oidcMenu->buildItem( + $this->moduleConfig->getModuleUrl(RoutesEnum::AdminTestFederationDiscovery->value), + Translate::noop('Test Federation Discovery'), + ), + ); + $this->oidcMenu->addItem( $this->oidcMenu->buildItem( $this->moduleConfig->getModuleUrl(RoutesEnum::AdminConfigVerifiableCredential->value), diff --git a/src/Utils/Routes.php b/src/Utils/Routes.php index ff5b2711..ab37629d 100644 --- a/src/Utils/Routes.php +++ b/src/Utils/Routes.php @@ -146,6 +146,11 @@ public function urlAdminTestTrustMarkValidation(array $parameters = []): string return $this->getModuleUrl(RoutesEnum::AdminTestTrustMarkValidation->value, $parameters); } + public function urlAdminTestFederationDiscovery(array $parameters = []): string + { + return $this->getModuleUrl(RoutesEnum::AdminTestFederationDiscovery->value, $parameters); + } + public function urlAdminTestVerifiableCredentialIssuance(array $parameters = []): string { return $this->getModuleUrl(RoutesEnum::AdminTestVerifiableCredentialIssuance->value, $parameters); diff --git a/templates/tests/federation-discovery.twig b/templates/tests/federation-discovery.twig new file mode 100644 index 00000000..d6a5ed1a --- /dev/null +++ b/templates/tests/federation-discovery.twig @@ -0,0 +1,55 @@ +{% set subPageTitle = 'Test Federation Discovery'|trans %} + +{% extends "@oidc/base.twig" %} + +{% block oidcContent %} + +

+ {{ 'You can use the form below to test Federation Discovery under given Trust Anchor.'|trans }} + {{ 'Log messages will show if any warnings or errors were raised during the process.'|trans }} +

+ +
+ +
+ + + + +
+ +
+
+ + {% if isFormSubmitted|default %} +

{{ 'Log messages'|trans }}

+

+ {% if logMessages|default %} + + {{- logMessages|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) -}} + + {% else %} + {{ 'Federation discovery passed (there were no warnings or errors during the process).'|trans }} + {% endif %} +

+ +

{{ 'Entities'|trans }}

+ {% if entities|default %} + + {{- entities|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) -}} + + {% else %} + {{ 'No entities were found during the process.'|trans }} + {% endif %} + + {% endif %} + +{% endblock oidcContent -%} From 037bc14414f72501df01050711fc0f74f3d38469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Fri, 24 Apr 2026 14:01:54 +0200 Subject: [PATCH 4/8] WIP --- .../Admin/FederationTestController.php | 13 ++++++++++--- .../Federation/EntityStatementController.php | 2 -- templates/tests/federation-discovery.twig | 16 +++++++++++++++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/Controllers/Admin/FederationTestController.php b/src/Controllers/Admin/FederationTestController.php index bc432623..a624de9a 100644 --- a/src/Controllers/Admin/FederationTestController.php +++ b/src/Controllers/Admin/FederationTestController.php @@ -189,11 +189,10 @@ public function federationDiscovery(Request $request): Response throw new OidcException('Empty Trust Anchor ID.'); try { - $entities = $this->federationWithArrayLogger->federationDiscovery()->discoverAndFetch( + $entities = $this->federationWithArrayLogger->federationDiscovery()->discoverEntityIds( trustAnchorId: $trustAnchorId, - forceRefresh: true, + // forceRefresh: true, // TODO make optional in form ); - } catch (\Throwable $exception) { $this->arrayLogger->error(sprintf( 'Error during entity discovery under Trust Anchor %s. Error was %s', @@ -205,6 +204,13 @@ public function federationDiscovery(Request $request): Response $logMessages = $this->arrayLogger->getEntries(); + try { + $trustAnchorIds = $this->moduleConfig->getFederationTrustAnchorIds(); + } catch (\Throwable $exception) { + $this->arrayLogger->error('Module config error: ' . $exception->getMessage()); + $trustAnchorIds = []; + } + return $this->templateFactory->build( 'oidc:tests/federation-discovery.twig', compact( @@ -212,6 +218,7 @@ public function federationDiscovery(Request $request): Response 'logMessages', 'isFormSubmitted', 'entities', + 'trustAnchorIds', ), RoutesEnum::AdminTestFederationDiscovery->value, ); diff --git a/src/Controllers/Federation/EntityStatementController.php b/src/Controllers/Federation/EntityStatementController.php index 55d3784f..1b840d7b 100644 --- a/src/Controllers/Federation/EntityStatementController.php +++ b/src/Controllers/Federation/EntityStatementController.php @@ -95,8 +95,6 @@ public function configuration(): Response ClaimsEnum::OrganizationUri->value => $this->moduleConfig->getOrganizationUri(), ], )), - ClaimsEnum::FederationFetchEndpoint->value => $this->routes->urlFederationFetch(), - ClaimsEnum::FederationListEndpoint->value => $this->routes->urlFederationList(), // TODO v7 mivanci Add when ready. Use ClaimsEnum for keys. // https://openid.net/specs/openid-federation-1_0.html#name-federation-entity //'federation_resolve_endpoint', diff --git a/templates/tests/federation-discovery.twig b/templates/tests/federation-discovery.twig index d6a5ed1a..15e5429c 100644 --- a/templates/tests/federation-discovery.twig +++ b/templates/tests/federation-discovery.twig @@ -8,6 +8,18 @@ {{ 'You can use the form below to test Federation Discovery under given Trust Anchor.'|trans }} {{ 'Log messages will show if any warnings or errors were raised during the process.'|trans }}

+

+ {% if trustAnchorIds|default %} + {{ 'Curently configured Trust Anchors for this entity:'|trans }} +

    + {% for trustAnchor in trustAnchorIds %} +
  • {{ trustAnchor }}
  • + {% endfor %} +
+ {% else %} + {{ 'No Trust Anchors are currently configured for this entity.'|trans }} + {% endif %} +

- + Use any Trust Anchor ID for discovery
@@ -42,6 +54,7 @@

{{ 'Entities'|trans }}

+

{% if entities|default %} {{- entities|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) -}} @@ -49,6 +62,7 @@ {% else %} {{ 'No entities were found during the process.'|trans }} {% endif %} +

{% endif %} From 985c9c74b2389319d08ad23f261eb6f080647caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Thu, 30 Apr 2026 09:17:38 +0200 Subject: [PATCH 5/8] WIP --- src/Controllers/Admin/FederationTestController.php | 6 +++++- templates/tests/federation-discovery.twig | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Controllers/Admin/FederationTestController.php b/src/Controllers/Admin/FederationTestController.php index a624de9a..b7ae346e 100644 --- a/src/Controllers/Admin/FederationTestController.php +++ b/src/Controllers/Admin/FederationTestController.php @@ -181,6 +181,7 @@ public function federationDiscovery(Request $request): Response $trustAnchorId = null; $isFormSubmitted = false; $entities = []; + $forceRefresh = false; if ($request->isMethod(Request::METHOD_POST)) { $isFormSubmitted = true; @@ -188,10 +189,12 @@ public function federationDiscovery(Request $request): Response !empty($trustAnchorId = $request->request->getString('trustAnchorId')) || throw new OidcException('Empty Trust Anchor ID.'); + $forceRefresh = $request->request->getBoolean('forceRefresh'); + try { $entities = $this->federationWithArrayLogger->federationDiscovery()->discoverEntityIds( trustAnchorId: $trustAnchorId, - // forceRefresh: true, // TODO make optional in form + forceRefresh: $forceRefresh, ); } catch (\Throwable $exception) { $this->arrayLogger->error(sprintf( @@ -219,6 +222,7 @@ public function federationDiscovery(Request $request): Response 'isFormSubmitted', 'entities', 'trustAnchorIds', + 'forceRefresh', ), RoutesEnum::AdminTestFederationDiscovery->value, ); diff --git a/templates/tests/federation-discovery.twig b/templates/tests/federation-discovery.twig index 15e5429c..4052da35 100644 --- a/templates/tests/federation-discovery.twig +++ b/templates/tests/federation-discovery.twig @@ -36,6 +36,16 @@ > Use any Trust Anchor ID for discovery + + + + {% trans %}Check if you want to force the refresh of entities in federation.{% endtrans %} + +
From de88bfbc020da65affa5ac421a4fe42d86a62a92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= Date: Thu, 30 Apr 2026 21:15:00 +0200 Subject: [PATCH 6/8] WIP --- public/assets/css/src/default.css | 25 +++- public/assets/js/src/default.js | 14 +- .../Admin/FederationTestController.php | 71 ++++++++- templates/tests/federation-discovery.twig | 138 +++++++++++++++--- templates/tests/trust-chain-resolution.twig | 4 +- templates/tests/trust-mark-validation.twig | 4 +- .../tests/verifiable-credential-issuance.twig | 4 +- 7 files changed, 231 insertions(+), 29 deletions(-) diff --git a/public/assets/css/src/default.css b/public/assets/css/src/default.css index d9a5ee7c..a4a3288c 100644 --- a/public/assets/css/src/default.css +++ b/public/assets/css/src/default.css @@ -104,8 +104,31 @@ table.client-table { font-weight: bolder; } -.confirm-action {} + form.pure-form-stacked .full-width { width: 100%; } + +/* Form loading state */ +.form-loading-spinner { + display: inline-block; + width: 0.85em; + height: 0.85em; + border: 2px solid currentColor; + border-top-color: transparent; + border-radius: 50%; + animation: form-spinner-rotate 0.7s linear infinite; + vertical-align: middle; + margin-right: 0.4em; + opacity: 0.8; +} + +@keyframes form-spinner-rotate { + to { transform: rotate(360deg); } +} + +button[disabled].loading { + opacity: 0.7; + cursor: not-allowed; +} diff --git a/public/assets/js/src/default.js b/public/assets/js/src/default.js index e6eb33fc..81fe2a09 100644 --- a/public/assets/js/src/default.js +++ b/public/assets/js/src/default.js @@ -1,4 +1,3 @@ - (function() { // Attach `confirm-action` click event to all elements with the `confirm-action` class. @@ -19,4 +18,17 @@ } }); }); + + // Handle forms with loading state + document.querySelectorAll('form.form-with-loading-state').forEach(form => { + form.addEventListener('submit', function (event) { + const submitter = event.submitter || this.querySelector('button[type="submit"]'); + if (submitter) { + const loadingText = submitter.getAttribute('data-loading-text') || 'Processing...'; + submitter.disabled = true; + submitter.classList.add('loading'); + submitter.innerHTML = ` ${loadingText}`; + } + }); + }); })(); diff --git a/src/Controllers/Admin/FederationTestController.php b/src/Controllers/Admin/FederationTestController.php index b7ae346e..498fc45d 100644 --- a/src/Controllers/Admin/FederationTestController.php +++ b/src/Controllers/Admin/FederationTestController.php @@ -182,6 +182,15 @@ public function federationDiscovery(Request $request): Response $isFormSubmitted = false; $entities = []; $forceRefresh = false; + $filterEntityTypes = []; + $filterTrustMarkTypes = ''; + $filterQuery = ''; + $sortBy = 'entity_id'; + $sortOrder = 'asc'; + $pageLimit = 50; + $pageFrom = null; + $nextPageToken = null; + $totalCount = 0; if ($request->isMethod(Request::METHOD_POST)) { $isFormSubmitted = true; @@ -190,12 +199,60 @@ public function federationDiscovery(Request $request): Response throw new OidcException('Empty Trust Anchor ID.'); $forceRefresh = $request->request->getBoolean('forceRefresh'); + $filterEntityTypes = $request->request->all('filterEntityTypes'); + $filterTrustMarkTypes = $request->request->getString('filterTrustMarkTypes'); + $filterQuery = $request->request->getString('filterQuery'); + $sortBy = $request->request->getString('sortBy', 'entity_id'); + $sortOrder = $request->request->getString('sortOrder', 'asc'); + $pageLimit = $request->request->getInt('pageLimit', 50); + $pageFrom = $request->request->get('pageFrom'); try { - $entities = $this->federationWithArrayLogger->federationDiscovery()->discoverEntityIds( + $entityCollection = $this->federationWithArrayLogger->federationDiscovery()->discover( trustAnchorId: $trustAnchorId, forceRefresh: $forceRefresh, ); + + // 1. Filtering + $criteria = array_filter([ + 'entity_type' => $filterEntityTypes, + 'trust_mark_type' => $this->helpers->str()->convertTextToArray($filterTrustMarkTypes), + 'query' => $filterQuery, + ]); + if (!empty($criteria)) { + $entityCollection->filter($criteria); + } + + $totalCount = count($entityCollection->getEntities()); + + // 2. Sorting + $claimPaths = match ($sortBy) { + 'display_name' => [ + ['metadata', EntityTypesEnum::OpenIdProvider->value, 'display_name'], + ['metadata', EntityTypesEnum::FederationEntity->value, 'display_name'], + ['metadata', EntityTypesEnum::OpenIdRelyingParty->value, 'display_name'], + ], + 'organization_name' => [ + ['metadata', EntityTypesEnum::OpenIdProvider->value, 'organization_name'], + ['metadata', EntityTypesEnum::FederationEntity->value, 'organization_name'], + ['metadata', EntityTypesEnum::OpenIdRelyingParty->value, 'organization_name'], + ], + default => [['sub']], + }; + $entityCollection->sort($claimPaths, $sortOrder); + + // 3. Pagination + /** @var positive-int $pageLimit */ + $entityCollection->paginate($pageLimit, $pageFrom); + + $nextPageToken = $entityCollection->getNextPageToken(); + + foreach ($entityCollection->getEntities() as $id => $payload) { + $entities[] = [ + 'id' => $id, + 'payload' => $payload, + ]; + } } catch (\Throwable $exception) { $this->arrayLogger->error(sprintf( 'Error during entity discovery under Trust Anchor %s. Error was %s', @@ -214,6 +271,8 @@ public function federationDiscovery(Request $request): Response $trustAnchorIds = []; } + $entityTypeOptions = array_map(fn (EntityTypesEnum $enum) => $enum->value, EntityTypesEnum::cases()); + return $this->templateFactory->build( 'oidc:tests/federation-discovery.twig', compact( @@ -223,6 +282,16 @@ public function federationDiscovery(Request $request): Response 'entities', 'trustAnchorIds', 'forceRefresh', + 'filterEntityTypes', + 'filterTrustMarkTypes', + 'filterQuery', + 'sortBy', + 'sortOrder', + 'pageLimit', + 'pageFrom', + 'nextPageToken', + 'totalCount', + 'entityTypeOptions', ), RoutesEnum::AdminTestFederationDiscovery->value, ); diff --git a/templates/tests/federation-discovery.twig b/templates/tests/federation-discovery.twig index 4052da35..758fa2d6 100644 --- a/templates/tests/federation-discovery.twig +++ b/templates/tests/federation-discovery.twig @@ -21,11 +21,13 @@ {% endif %}

+ + class="pure-form pure-form-stacked form-with-loading-state">
+ {{ 'Basic options'|trans }} Use any Trust Anchor ID for discovery +
- - - - {% trans %}Check if you want to force the refresh of entities in federation.{% endtrans %} - +
+ {{ 'Filtering'|trans }} +
+ + -
- -
+ + + + + + +
+ +
+ {{ 'Sorting'|trans }} +
+ + + + + +
+
+ +
+ {{ 'Pagination'|trans }} +
+ + + +
+
+ +
+ {% if isFormSubmitted|default %} +
+ +

{{ 'Log messages'|trans }}

{% if logMessages|default %} @@ -63,16 +106,71 @@ {% endif %}

-

{{ 'Entities'|trans }}

-

+

+
+

{{ 'Entities'|trans }}

+
+
+

{{ 'Total matching entities:'|trans }} {{ totalCount }}

+
+
+ {% if entities|default %} - - {{- entities|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) -}} - + + + + + + + + + + {% for entity in entities %} + + + + + + {% endfor %} + +
{{ 'Entity ID'|trans }}{{ 'Display Name'|trans }}{{ 'Types'|trans }}
{{ entity.id }} + {% set displayName = '' %} + {% for type in entityTypeOptions %} + {% if entity.payload.metadata[type].display_name is defined and displayName == '' %} + {% set displayName = entity.payload.metadata[type].display_name %} + {% endif %} + {% endfor %} + {{ displayName }} + + {% set types = [] %} + {% for type in entityTypeOptions %} + {% if entity.payload.metadata[type] is defined %} + {% set types = types|merge([type]) %} + {% endif %} + {% endfor %} + {{ types|join(', ') }} +
+ + {% if nextPageToken %} +
+
+ + + {% for type in filterEntityTypes %} + + {% endfor %} + + + + + + + +
+ {% endif %} {% else %} - {{ 'No entities were found during the process.'|trans }} +

{{ 'No entities were found matching the criteria.'|trans }}

{% endif %} -

{% endif %} diff --git a/templates/tests/trust-chain-resolution.twig b/templates/tests/trust-chain-resolution.twig index fde0f21d..616f2b78 100644 --- a/templates/tests/trust-chain-resolution.twig +++ b/templates/tests/trust-chain-resolution.twig @@ -12,7 +12,7 @@
+ class="pure-form pure-form-stacked form-with-loading-state">
@@ -34,7 +34,7 @@ {{ 'Enter one Trust Anchor ID per line.'|trans }}
- +
diff --git a/templates/tests/trust-mark-validation.twig b/templates/tests/trust-mark-validation.twig index d426889d..788ce174 100644 --- a/templates/tests/trust-mark-validation.twig +++ b/templates/tests/trust-mark-validation.twig @@ -12,7 +12,7 @@
+ class="pure-form pure-form-stacked form-with-loading-state">
@@ -46,7 +46,7 @@
- +
diff --git a/templates/tests/verifiable-credential-issuance.twig b/templates/tests/verifiable-credential-issuance.twig index fdec0b70..5d40c967 100644 --- a/templates/tests/verifiable-credential-issuance.twig +++ b/templates/tests/verifiable-credential-issuance.twig @@ -19,7 +19,7 @@ {{ 'You will be presented with a Credential Offer which you can use to test credential issuance.'|trans }}

-
+