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 }}
+