Skip to content

Commit 699233d

Browse files
authored
feat: implement passkey endpoints with controller and configuration (#767)
1 parent d2433fe commit 699233d

File tree

16 files changed

+748
-1
lines changed

16 files changed

+748
-1
lines changed

.ci-tools/phpstan-baseline.neon

Lines changed: 181 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,30 @@ parameters:
303303
count: 1
304304
path: ../src/symfony/src/DependencyInjection/Compiler/LoggerSetterCompilerPass.php
305305

306+
-
307+
rawMessage: 'Method Webauthn\Bundle\DependencyInjection\Compiler\PasskeyEndpointsCompilerPass::createControllerDefinition() has a parameter $container with a type declaration of Symfony\Component\DependencyInjection\ContainerBuilder, but containers should not be injected.'
308+
identifier: ergebnis.noParameterWithContainerTypeDeclaration
309+
count: 1
310+
path: ../src/symfony/src/DependencyInjection/Compiler/PasskeyEndpointsCompilerPass.php
311+
312+
-
313+
rawMessage: 'Method Webauthn\Bundle\DependencyInjection\Compiler\PasskeyEndpointsCompilerPass::createPasskeyEndpointsResponse() has a parameter $container with a type declaration of Symfony\Component\DependencyInjection\ContainerBuilder, but containers should not be injected.'
314+
identifier: ergebnis.noParameterWithContainerTypeDeclaration
315+
count: 1
316+
path: ../src/symfony/src/DependencyInjection/Compiler/PasskeyEndpointsCompilerPass.php
317+
318+
-
319+
rawMessage: 'Method Webauthn\Bundle\DependencyInjection\Compiler\PasskeyEndpointsCompilerPass::process() has a parameter $container with a type declaration of Symfony\Component\DependencyInjection\ContainerBuilder, but containers should not be injected.'
320+
identifier: ergebnis.noParameterWithContainerTypeDeclaration
321+
count: 1
322+
path: ../src/symfony/src/DependencyInjection/Compiler/PasskeyEndpointsCompilerPass.php
323+
324+
-
325+
rawMessage: 'Parameter #1 $value of method Webauthn\Bundle\DependencyInjection\Compiler\PasskeyEndpointsCompilerPass::createUrlDefinition() expects array<string, mixed>|string, array|bool|float|int|string given.'
326+
identifier: argument.type
327+
count: 3
328+
path: ../src/symfony/src/DependencyInjection/Compiler/PasskeyEndpointsCompilerPass.php
329+
306330
-
307331
rawMessage: Anonymous function should return array but returns mixed.
308332
identifier: return.type
@@ -831,6 +855,12 @@ parameters:
831855
count: 1
832856
path: ../src/symfony/src/DependencyInjection/WebauthnExtension.php
833857

858+
-
859+
rawMessage: 'Method Webauthn\Bundle\DependencyInjection\WebauthnExtension::loadPasskeyEndpointsConfig() has a parameter $container with a type declaration of Symfony\Component\DependencyInjection\ContainerBuilder, but containers should not be injected.'
860+
identifier: ergebnis.noParameterWithContainerTypeDeclaration
861+
count: 1
862+
path: ../src/symfony/src/DependencyInjection/WebauthnExtension.php
863+
834864
-
835865
rawMessage: 'Method Webauthn\Bundle\DependencyInjection\WebauthnExtension::loadRequestControllersSupport() has a parameter $container with a type declaration of Symfony\Component\DependencyInjection\ContainerBuilder, but containers should not be injected.'
836866
identifier: ergebnis.noParameterWithContainerTypeDeclaration
@@ -867,6 +897,12 @@ parameters:
867897
count: 1
868898
path: ../src/symfony/src/DependencyInjection/WebauthnExtension.php
869899

900+
-
901+
rawMessage: 'Parameter #2 $config of method Webauthn\Bundle\DependencyInjection\WebauthnExtension::loadPasskeyEndpointsConfig() expects array<mixed>, mixed given.'
902+
identifier: argument.type
903+
count: 1
904+
path: ../src/symfony/src/DependencyInjection/WebauthnExtension.php
905+
870906
-
871907
rawMessage: 'Parameter #2 $config of method Webauthn\Bundle\DependencyInjection\WebauthnExtension::loadRequestControllersSupport() expects array<mixed>, mixed given.'
872908
identifier: argument.type
@@ -882,7 +918,7 @@ parameters:
882918
-
883919
rawMessage: 'Parameter #2 $value of method Symfony\Component\DependencyInjection\Container::setParameter() expects array|bool|float|int|string|UnitEnum|null, mixed given.'
884920
identifier: argument.type
885-
count: 5
921+
count: 9
886922
path: ../src/symfony/src/DependencyInjection/WebauthnExtension.php
887923

888924
-
@@ -5049,6 +5085,54 @@ parameters:
50495085
count: 1
50505086
path: ../src/webauthn/src/Denormalizer/TrustPathDenormalizer.php
50515087

5088+
-
5089+
rawMessage: Constructor in Webauthn\Denormalizer\UrlNormalizer has parameter $urlGenerator with default value.
5090+
identifier: ergebnis.noConstructorParameterWithDefaultValue
5091+
count: 1
5092+
path: ../src/webauthn/src/Denormalizer/UrlNormalizer.php
5093+
5094+
-
5095+
rawMessage: 'Method Webauthn\Denormalizer\UrlNormalizer::__construct() has parameter $urlGenerator with a nullable type declaration.'
5096+
identifier: ergebnis.noParameterWithNullableTypeDeclaration
5097+
count: 1
5098+
path: ../src/webauthn/src/Denormalizer/UrlNormalizer.php
5099+
5100+
-
5101+
rawMessage: 'Method Webauthn\Denormalizer\UrlNormalizer::__construct() has parameter $urlGenerator with null as default value.'
5102+
identifier: ergebnis.noParameterWithNullDefaultValue
5103+
count: 1
5104+
path: ../src/webauthn/src/Denormalizer/UrlNormalizer.php
5105+
5106+
-
5107+
rawMessage: 'Method Webauthn\Denormalizer\UrlNormalizer::getSupportedTypes() has parameter $format with a nullable type declaration.'
5108+
identifier: ergebnis.noParameterWithNullableTypeDeclaration
5109+
count: 1
5110+
path: ../src/webauthn/src/Denormalizer/UrlNormalizer.php
5111+
5112+
-
5113+
rawMessage: 'Method Webauthn\Denormalizer\UrlNormalizer::normalize() has parameter $format with a nullable type declaration.'
5114+
identifier: ergebnis.noParameterWithNullableTypeDeclaration
5115+
count: 1
5116+
path: ../src/webauthn/src/Denormalizer/UrlNormalizer.php
5117+
5118+
-
5119+
rawMessage: 'Method Webauthn\Denormalizer\UrlNormalizer::normalize() has parameter $format with null as default value.'
5120+
identifier: ergebnis.noParameterWithNullDefaultValue
5121+
count: 1
5122+
path: ../src/webauthn/src/Denormalizer/UrlNormalizer.php
5123+
5124+
-
5125+
rawMessage: 'Method Webauthn\Denormalizer\UrlNormalizer::supportsNormalization() has parameter $format with a nullable type declaration.'
5126+
identifier: ergebnis.noParameterWithNullableTypeDeclaration
5127+
count: 1
5128+
path: ../src/webauthn/src/Denormalizer/UrlNormalizer.php
5129+
5130+
-
5131+
rawMessage: 'Method Webauthn\Denormalizer\UrlNormalizer::supportsNormalization() has parameter $format with null as default value.'
5132+
identifier: ergebnis.noParameterWithNullDefaultValue
5133+
count: 1
5134+
path: ../src/webauthn/src/Denormalizer/UrlNormalizer.php
5135+
50525136
-
50535137
rawMessage: 'Method Webauthn\Denormalizer\VerificationMethodANDCombinationsDenormalizer::getSupportedTypes() has parameter $format with a nullable type declaration.'
50545138
identifier: ergebnis.noParameterWithNullableTypeDeclaration
@@ -7269,6 +7353,96 @@ parameters:
72697353
count: 1
72707354
path: ../src/webauthn/src/MetadataService/Statement/Version.php
72717355

7356+
-
7357+
rawMessage: Constructor in Webauthn\PasskeyEndpointsResponse has parameter $enroll with default value.
7358+
identifier: ergebnis.noConstructorParameterWithDefaultValue
7359+
count: 1
7360+
path: ../src/webauthn/src/PasskeyEndpointsResponse.php
7361+
7362+
-
7363+
rawMessage: Constructor in Webauthn\PasskeyEndpointsResponse has parameter $manage with default value.
7364+
identifier: ergebnis.noConstructorParameterWithDefaultValue
7365+
count: 1
7366+
path: ../src/webauthn/src/PasskeyEndpointsResponse.php
7367+
7368+
-
7369+
rawMessage: Constructor in Webauthn\PasskeyEndpointsResponse has parameter $prfUsageDetails with default value.
7370+
identifier: ergebnis.noConstructorParameterWithDefaultValue
7371+
count: 1
7372+
path: ../src/webauthn/src/PasskeyEndpointsResponse.php
7373+
7374+
-
7375+
rawMessage: 'Method Webauthn\PasskeyEndpointsResponse::__construct() has parameter $enroll with a nullable type declaration.'
7376+
identifier: ergebnis.noParameterWithNullableTypeDeclaration
7377+
count: 1
7378+
path: ../src/webauthn/src/PasskeyEndpointsResponse.php
7379+
7380+
-
7381+
rawMessage: 'Method Webauthn\PasskeyEndpointsResponse::__construct() has parameter $enroll with null as default value.'
7382+
identifier: ergebnis.noParameterWithNullDefaultValue
7383+
count: 1
7384+
path: ../src/webauthn/src/PasskeyEndpointsResponse.php
7385+
7386+
-
7387+
rawMessage: 'Method Webauthn\PasskeyEndpointsResponse::__construct() has parameter $manage with a nullable type declaration.'
7388+
identifier: ergebnis.noParameterWithNullableTypeDeclaration
7389+
count: 1
7390+
path: ../src/webauthn/src/PasskeyEndpointsResponse.php
7391+
7392+
-
7393+
rawMessage: 'Method Webauthn\PasskeyEndpointsResponse::__construct() has parameter $manage with null as default value.'
7394+
identifier: ergebnis.noParameterWithNullDefaultValue
7395+
count: 1
7396+
path: ../src/webauthn/src/PasskeyEndpointsResponse.php
7397+
7398+
-
7399+
rawMessage: 'Method Webauthn\PasskeyEndpointsResponse::__construct() has parameter $prfUsageDetails with a nullable type declaration.'
7400+
identifier: ergebnis.noParameterWithNullableTypeDeclaration
7401+
count: 1
7402+
path: ../src/webauthn/src/PasskeyEndpointsResponse.php
7403+
7404+
-
7405+
rawMessage: 'Method Webauthn\PasskeyEndpointsResponse::__construct() has parameter $prfUsageDetails with null as default value.'
7406+
identifier: ergebnis.noParameterWithNullDefaultValue
7407+
count: 1
7408+
path: ../src/webauthn/src/PasskeyEndpointsResponse.php
7409+
7410+
-
7411+
rawMessage: 'Method Webauthn\PasskeyEndpointsResponse::create() has parameter $enroll with a nullable type declaration.'
7412+
identifier: ergebnis.noParameterWithNullableTypeDeclaration
7413+
count: 1
7414+
path: ../src/webauthn/src/PasskeyEndpointsResponse.php
7415+
7416+
-
7417+
rawMessage: 'Method Webauthn\PasskeyEndpointsResponse::create() has parameter $enroll with null as default value.'
7418+
identifier: ergebnis.noParameterWithNullDefaultValue
7419+
count: 1
7420+
path: ../src/webauthn/src/PasskeyEndpointsResponse.php
7421+
7422+
-
7423+
rawMessage: 'Method Webauthn\PasskeyEndpointsResponse::create() has parameter $manage with a nullable type declaration.'
7424+
identifier: ergebnis.noParameterWithNullableTypeDeclaration
7425+
count: 1
7426+
path: ../src/webauthn/src/PasskeyEndpointsResponse.php
7427+
7428+
-
7429+
rawMessage: 'Method Webauthn\PasskeyEndpointsResponse::create() has parameter $manage with null as default value.'
7430+
identifier: ergebnis.noParameterWithNullDefaultValue
7431+
count: 1
7432+
path: ../src/webauthn/src/PasskeyEndpointsResponse.php
7433+
7434+
-
7435+
rawMessage: 'Method Webauthn\PasskeyEndpointsResponse::create() has parameter $prfUsageDetails with a nullable type declaration.'
7436+
identifier: ergebnis.noParameterWithNullableTypeDeclaration
7437+
count: 1
7438+
path: ../src/webauthn/src/PasskeyEndpointsResponse.php
7439+
7440+
-
7441+
rawMessage: 'Method Webauthn\PasskeyEndpointsResponse::create() has parameter $prfUsageDetails with null as default value.'
7442+
identifier: ergebnis.noParameterWithNullDefaultValue
7443+
count: 1
7444+
path: ../src/webauthn/src/PasskeyEndpointsResponse.php
7445+
72727446
-
72737447
rawMessage: Class "Webauthn\PublicKeyCredential" is not allowed to extend "Webauthn\Credential".
72747448
identifier: ergebnis.noExtends
@@ -7968,6 +8142,12 @@ parameters:
79688142
count: 1
79698143
path: ../src/webauthn/src/SimpleFakeCredentialGenerator.php
79708144

8145+
-
8146+
rawMessage: Constructor in Webauthn\Url has parameter $params with default value.
8147+
identifier: ergebnis.noConstructorParameterWithDefaultValue
8148+
count: 1
8149+
path: ../src/webauthn/src/Url.php
8150+
79718151
-
79728152
rawMessage: 'Method Webauthn\Util\Base64::decode() is not final, but since the containing class is abstract, it should be.'
79738153
identifier: ergebnis.finalInAbstractClass
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Webauthn\Bundle\Controller;
6+
7+
use Symfony\Component\HttpFoundation\JsonResponse;
8+
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
9+
use Symfony\Component\Serializer\SerializerInterface;
10+
use Webauthn\PasskeyEndpointsResponse;
11+
12+
/**
13+
* Controller exposing the .well-known/passkey-endpoints endpoint.
14+
*
15+
* @see https://w3c.github.io/webappsec-passkey-endpoints/
16+
*/
17+
final readonly class PasskeyEndpointsController
18+
{
19+
public function __construct(
20+
private PasskeyEndpointsResponse $passkeyEndpoints,
21+
private SerializerInterface $serializer
22+
) {
23+
}
24+
25+
public function __invoke(): JsonResponse
26+
{
27+
$endpoints = $this->serializer->serialize($this->passkeyEndpoints, 'json', [
28+
AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => true,
29+
]);
30+
31+
return new JsonResponse($endpoints, 200, [], true);
32+
}
33+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Webauthn\Bundle\DependencyInjection\Compiler;
6+
7+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
8+
use Symfony\Component\DependencyInjection\ContainerBuilder;
9+
use Symfony\Component\DependencyInjection\Definition;
10+
use Symfony\Component\DependencyInjection\Reference;
11+
use Symfony\Component\Serializer\SerializerInterface;
12+
use Webauthn\Bundle\Controller\PasskeyEndpointsController;
13+
use Webauthn\Bundle\Routing\Loader;
14+
use Webauthn\PasskeyEndpointsResponse;
15+
use Webauthn\Url;
16+
17+
/**
18+
* Compiler pass to register the .well-known/passkey-endpoints controller and route.
19+
*
20+
* @see https://w3c.github.io/webappsec-passkey-endpoints/
21+
*/
22+
final class PasskeyEndpointsCompilerPass implements CompilerPassInterface
23+
{
24+
public function process(ContainerBuilder $container): void
25+
{
26+
if (! $container->hasParameter('webauthn.passkey_endpoints.enabled')) {
27+
return;
28+
}
29+
30+
$enabled = $container->getParameter('webauthn.passkey_endpoints.enabled');
31+
if ($enabled !== true) {
32+
return;
33+
}
34+
35+
$this->createPasskeyEndpointsResponse($container);
36+
$this->createControllerDefinition($container);
37+
}
38+
39+
private function createPasskeyEndpointsResponse(ContainerBuilder $container): void
40+
{
41+
$enroll = $container->getParameter('webauthn.passkey_endpoints.enroll');
42+
$manage = $container->getParameter('webauthn.passkey_endpoints.manage');
43+
$prfUsageDetails = $container->getParameter('webauthn.passkey_endpoints.prf_usage_details');
44+
45+
// Create Url definitions from string configuration
46+
$enrollUrl = $enroll !== null ? $this->createUrlDefinition($enroll) : null;
47+
$manageUrl = $manage !== null ? $this->createUrlDefinition($manage) : null;
48+
$prfUrl = $prfUsageDetails !== null ? $this->createUrlDefinition($prfUsageDetails) : null;
49+
50+
$responseDefinition = new Definition(PasskeyEndpointsResponse::class, [$enrollUrl, $manageUrl, $prfUrl]);
51+
52+
$container->setDefinition(PasskeyEndpointsResponse::class, $responseDefinition);
53+
}
54+
55+
/**
56+
* Creates a Url definition from configuration value (string or array) using the serializer to denormalize it.
57+
*
58+
* @param string|array<string, mixed> $value
59+
*/
60+
private function createUrlDefinition(string|array $value): Definition
61+
{
62+
$urlDefinition = new Definition(Url::class);
63+
$urlDefinition->setFactory([new Reference(SerializerInterface::class), 'denormalize']);
64+
$urlDefinition->setArguments([$value, Url::class, 'json']);
65+
66+
return $urlDefinition;
67+
}
68+
69+
private function createControllerDefinition(ContainerBuilder $container): void
70+
{
71+
if (! $container->hasDefinition(Loader::class)) {
72+
return;
73+
}
74+
75+
if (! $container->hasDefinition(PasskeyEndpointsResponse::class)) {
76+
return;
77+
}
78+
79+
$controllerDefinition = new Definition(
80+
PasskeyEndpointsController::class,
81+
[$container->getDefinition(PasskeyEndpointsResponse::class), new Reference(SerializerInterface::class)]
82+
);
83+
$controllerDefinition->setPublic(true);
84+
85+
$container->setDefinition(PasskeyEndpointsController::class, $controllerDefinition);
86+
87+
$loaderDefinition = $container->getDefinition(Loader::class);
88+
$loaderDefinition->addMethodCall('add', [
89+
'/.well-known/passkey-endpoints',
90+
null,
91+
PasskeyEndpointsController::class,
92+
'GET',
93+
]);
94+
}
95+
}

0 commit comments

Comments
 (0)