Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 73 additions & 7 deletions .ci-tools/phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,54 @@ parameters:
count: 1
path: ../src/symfony/src/CredentialOptionsBuilder/ProfileBasedCreationOptionsBuilder.php

-
rawMessage: Constructor in Webauthn\Bundle\CredentialOptionsBuilder\ProfileBasedCreationOptionsBuilder has parameter $overridePolicy with default value.
identifier: ergebnis.noConstructorParameterWithDefaultValue
count: 1
path: ../src/symfony/src/CredentialOptionsBuilder/ProfileBasedCreationOptionsBuilder.php

-
rawMessage: 'Method Webauthn\Bundle\CredentialOptionsBuilder\ProfileBasedCreationOptionsBuilder::buildAuthenticatorSelectionCriteria() has a nullable return type declaration.'
identifier: ergebnis.noNullableReturnTypeDeclaration
count: 1
path: ../src/symfony/src/CredentialOptionsBuilder/ProfileBasedCreationOptionsBuilder.php

-
rawMessage: 'Method Webauthn\Bundle\CredentialOptionsBuilder\ProfileBasedCreationOptionsBuilder::getEffectiveAttestation() has a nullable return type declaration.'
identifier: ergebnis.noNullableReturnTypeDeclaration
count: 1
path: ../src/symfony/src/CredentialOptionsBuilder/ProfileBasedCreationOptionsBuilder.php

-
rawMessage: 'Method Webauthn\Bundle\CredentialOptionsBuilder\ProfileBasedCreationOptionsBuilder::getEffectiveAttestation() should return string|null but returns mixed.'
identifier: return.type
count: 1
path: ../src/symfony/src/CredentialOptionsBuilder/ProfileBasedCreationOptionsBuilder.php

-
rawMessage: 'Method Webauthn\Bundle\CredentialOptionsBuilder\ProfileBasedCreationOptionsBuilder::getEffectiveExtensions() has a nullable return type declaration.'
identifier: ergebnis.noNullableReturnTypeDeclaration
count: 1
path: ../src/symfony/src/CredentialOptionsBuilder/ProfileBasedCreationOptionsBuilder.php

-
rawMessage: 'Parameter #1 $authenticatorAttachment of static method Webauthn\AuthenticatorSelectionCriteria::create() expects string|null, mixed given.'
identifier: argument.type
count: 1
path: ../src/symfony/src/CredentialOptionsBuilder/ProfileBasedCreationOptionsBuilder.php

-
rawMessage: 'Parameter #2 $userVerification of static method Webauthn\AuthenticatorSelectionCriteria::create() expects string, mixed given.'
identifier: argument.type
count: 1
path: ../src/symfony/src/CredentialOptionsBuilder/ProfileBasedCreationOptionsBuilder.php

-
rawMessage: 'Parameter #3 $residentKey of static method Webauthn\AuthenticatorSelectionCriteria::create() expects string|null, mixed given.'
identifier: argument.type
count: 1
path: ../src/symfony/src/CredentialOptionsBuilder/ProfileBasedCreationOptionsBuilder.php

-
rawMessage: '''
Parameter $credential of anonymous function has typehint with deprecated class Webauthn\PublicKeyCredentialSource:
Expand Down Expand Up @@ -243,6 +291,12 @@ parameters:
count: 1
path: ../src/symfony/src/CredentialOptionsBuilder/ProfileBasedRequestOptionsBuilder.php

-
rawMessage: Constructor in Webauthn\Bundle\CredentialOptionsBuilder\ProfileBasedRequestOptionsBuilder has parameter $overridePolicy with default value.
identifier: ergebnis.noConstructorParameterWithDefaultValue
count: 1
path: ../src/symfony/src/CredentialOptionsBuilder/ProfileBasedRequestOptionsBuilder.php

-
rawMessage: 'Method Webauthn\Bundle\CredentialOptionsBuilder\ProfileBasedRequestOptionsBuilder::__construct() has parameter $fakeCredentialGenerator with a nullable type declaration.'
identifier: ergebnis.noParameterWithNullableTypeDeclaration
Expand All @@ -255,6 +309,12 @@ parameters:
count: 1
path: ../src/symfony/src/CredentialOptionsBuilder/ProfileBasedRequestOptionsBuilder.php

-
rawMessage: 'Method Webauthn\Bundle\CredentialOptionsBuilder\ProfileBasedRequestOptionsBuilder::getEffectiveExtensions() has a nullable return type declaration.'
identifier: ergebnis.noNullableReturnTypeDeclaration
count: 1
path: ../src/symfony/src/CredentialOptionsBuilder/ProfileBasedRequestOptionsBuilder.php

-
rawMessage: 'Method Webauthn\Bundle\CredentialOptionsBuilder\ProfileBasedRequestOptionsBuilder::getFromRequest() has parameter $userEntity that is passed by reference.'
identifier: ergebnis.noParameterPassedByReference
Expand All @@ -279,6 +339,12 @@ parameters:
count: 1
path: ../src/symfony/src/CredentialOptionsBuilder/ProfileBasedRequestOptionsBuilder.php

-
rawMessage: 'Parameter #3 $userVerification of method Webauthn\Bundle\Service\PublicKeyCredentialRequestOptionsFactory::create() expects string|null, mixed given.'
identifier: argument.type
count: 1
path: ../src/symfony/src/CredentialOptionsBuilder/ProfileBasedRequestOptionsBuilder.php

-
rawMessage: '''
Parameter $credential of anonymous function has typehint with deprecated class Webauthn\PublicKeyCredentialSource:
Expand Down Expand Up @@ -591,12 +657,6 @@ parameters:
count: 14
path: ../src/symfony/src/DependencyInjection/Factory/Security/WebauthnFactory.php

-
rawMessage: 'Method Webauthn\Bundle\DependencyInjection\Factory\Security\WebauthnFactory::addConfiguration() has parameter $builder with generic class Symfony\Component\Config\Definition\Builder\NodeDefinition but does not specify its types: TParent'
identifier: missingType.generics
count: 1
path: ../src/symfony/src/DependencyInjection/Factory/Security/WebauthnFactory.php

-
rawMessage: 'Method Webauthn\Bundle\DependencyInjection\Factory\Security\WebauthnFactory::createAssertionControllersAndRoutes() has a parameter $container with a type declaration of Symfony\Component\DependencyInjection\ContainerBuilder, but containers should not be injected.'
identifier: ergebnis.noParameterWithContainerTypeDeclaration
Expand Down Expand Up @@ -1131,7 +1191,7 @@ parameters:
-
rawMessage: 'Parameter #2 $value of method Symfony\Component\DependencyInjection\Container::setParameter() expects array|bool|float|int|string|UnitEnum|null, mixed given.'
identifier: argument.type
count: 9
count: 10
path: ../src/symfony/src/DependencyInjection/WebauthnExtension.php

-
Expand Down Expand Up @@ -1404,6 +1464,12 @@ parameters:
count: 1
path: ../src/symfony/src/Exception/MissingUserEntityException.php

-
rawMessage: Constructor in Webauthn\Bundle\Policy\ClientOverridePolicy has parameter $policies with default value.
identifier: ergebnis.noConstructorParameterWithDefaultValue
count: 1
path: ../src/symfony/src/Policy/ClientOverridePolicy.php

-
rawMessage: 'Method Webauthn\Bundle\Repository\CanGenerateUserEntity::generateUserEntity() has parameter $displayName with a nullable type declaration.'
identifier: ergebnis.noParameterWithNullableTypeDeclaration
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ yarn-error.log
/tmp*
/.ci-tools/coverage
/package-lock.json
.grepai/
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

namespace Webauthn\Bundle\CredentialOptionsBuilder;

use function count;
use InvalidArgumentException;
use function is_array;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
Expand All @@ -14,15 +16,14 @@
use Webauthn\AuthenticationExtensions\AuthenticationExtensions;
use Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\Bundle\Dto\PublicKeyCredentialCreationOptionsRequest;
use Webauthn\Bundle\Policy\ClientOverridePolicy;
use Webauthn\Bundle\Repository\PublicKeyCredentialSourceRepositoryInterface;
use Webauthn\Bundle\Service\PublicKeyCredentialCreationOptionsFactory;
use Webauthn\CredentialRecord;
use Webauthn\PublicKeyCredentialCreationOptions;
use Webauthn\PublicKeyCredentialDescriptor;
use Webauthn\PublicKeyCredentialSource;
use Webauthn\PublicKeyCredentialUserEntity;
use function count;
use function is_array;

final readonly class ProfileBasedCreationOptionsBuilder implements PublicKeyCredentialCreationOptionsBuilder
{
Expand All @@ -32,6 +33,7 @@ public function __construct(
private PublicKeyCredentialSourceRepositoryInterface $credentialSourceRepository,
private PublicKeyCredentialCreationOptionsFactory $publicKeyCredentialCreationOptionsFactory,
private string $profile,
private ClientOverridePolicy $overridePolicy = new ClientOverridePolicy(),
) {
}

Expand All @@ -47,34 +49,88 @@ public function getFromRequest(
$excludedCredentials = $hideExistingExcludedCredentials === true ? [] : $this->getCredentials($userEntity);
$optionsRequest = $this->getServerPublicKeyCredentialCreationOptionsRequest($content);

$residentKey = $optionsRequest->residentKey ?? null;
$authenticatorSelection = AuthenticatorSelectionCriteria::create(
$optionsRequest->authenticatorAttachment,
$optionsRequest->userVerification ?? AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED,
$residentKey,
);
$extensions = null;
if (is_array($optionsRequest->extensions)) {
$extensions = AuthenticationExtensions::create(array_map(
static fn (string $name, mixed $data): AuthenticationExtension => AuthenticationExtension::create(
$name,
$data
),
array_keys($optionsRequest->extensions),
$optionsRequest->extensions
));
}
// Apply override policy to determine effective values
$authenticatorSelection = $this->buildAuthenticatorSelectionCriteria($optionsRequest);
$attestation = $this->getEffectiveAttestation($optionsRequest);
$extensions = $this->getEffectiveExtensions($optionsRequest);

return $this->publicKeyCredentialCreationOptionsFactory->create(
$this->profile,
$userEntity,
$excludedCredentials,
$authenticatorSelection,
$optionsRequest->attestation,
$attestation,
$extensions
);
}

private function buildAuthenticatorSelectionCriteria(
PublicKeyCredentialCreationOptionsRequest $optionsRequest
): ?AuthenticatorSelectionCriteria {
// Check if any authenticator selection override is allowed
$hasOverrides = $this->overridePolicy->canOverride('user_verification') ||
$this->overridePolicy->canOverride('authenticator_attachment') ||
$this->overridePolicy->canOverride('resident_key');

if (! $hasOverrides) {
return null; // Use profile configuration
}

// Build criteria considering override policy
$userVerification = $this->overridePolicy->getEffectiveValue(
'user_verification',
$optionsRequest->userVerification,
null // Will be handled by factory fallback to profile
);

$authenticatorAttachment = $this->overridePolicy->getEffectiveValue(
'authenticator_attachment',
$optionsRequest->authenticatorAttachment,
null
);

$residentKey = $this->overridePolicy->getEffectiveValue(
'resident_key',
$optionsRequest->residentKey,
null
);

// Only create if we have at least one override value
if ($userVerification !== null || $authenticatorAttachment !== null || $residentKey !== null) {
return AuthenticatorSelectionCriteria::create(
$authenticatorAttachment,
$userVerification ?? AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED,
$residentKey,
);
}

return null; // Use profile configuration
}

private function getEffectiveAttestation(PublicKeyCredentialCreationOptionsRequest $optionsRequest): ?string
{
return $this->overridePolicy->getEffectiveValue(
'attestation_conveyance',
$optionsRequest->attestation,
null // Will fallback to profile
);
}

private function getEffectiveExtensions(
PublicKeyCredentialCreationOptionsRequest $optionsRequest
): ?AuthenticationExtensions {
if (! $this->overridePolicy->canOverride('extensions') || ! is_array($optionsRequest->extensions)) {
return null; // Use profile extensions
}

$extensions = [];
foreach ($optionsRequest->extensions as $name => $data) {
$extensions[] = AuthenticationExtension::create($name, $data);
}

return AuthenticationExtensions::create($extensions);
}

/**
* @return PublicKeyCredentialDescriptor[]
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Webauthn\Bundle\CredentialOptionsBuilder;

use function count;
use function is_array;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
Expand All @@ -13,6 +15,7 @@
use Webauthn\AuthenticationExtensions\AuthenticationExtension;
use Webauthn\AuthenticationExtensions\AuthenticationExtensions;
use Webauthn\Bundle\Dto\ServerPublicKeyCredentialRequestOptionsRequest;
use Webauthn\Bundle\Policy\ClientOverridePolicy;
use Webauthn\Bundle\Repository\PublicKeyCredentialSourceRepositoryInterface;
use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepositoryInterface;
use Webauthn\Bundle\Service\PublicKeyCredentialRequestOptionsFactory;
Expand All @@ -22,8 +25,6 @@
use Webauthn\PublicKeyCredentialRequestOptions;
use Webauthn\PublicKeyCredentialSource;
use Webauthn\PublicKeyCredentialUserEntity;
use function count;
use function is_array;

final readonly class ProfileBasedRequestOptionsBuilder implements PublicKeyCredentialRequestOptionsBuilder
{
Expand All @@ -35,6 +36,7 @@ public function __construct(
private PublicKeyCredentialRequestOptionsFactory $publicKeyCredentialRequestOptionsFactory,
private string $profile,
private null|FakeCredentialGenerator $fakeCredentialGenerator = null,
private ClientOverridePolicy $overridePolicy = new ClientOverridePolicy(),
) {
}

Expand All @@ -46,17 +48,7 @@ public function getFromRequest(
$format === 'json' || throw new BadRequestHttpException('Only JSON content type allowed');
$content = $request->getContent();
$optionsRequest = $this->getServerPublicKeyCredentialRequestOptionsRequest($content);
$extensions = null;
if (is_array($optionsRequest->extensions)) {
$extensions = AuthenticationExtensions::create(array_map(
static fn (string $name, mixed $data): AuthenticationExtension => AuthenticationExtension::create(
$name,
$data
),
array_keys($optionsRequest->extensions),
$optionsRequest->extensions
));
}

$userEntity = $optionsRequest->username === null ? null : $this->userEntityRepository->findOneByUsername(
$optionsRequest->username
);
Expand All @@ -70,14 +62,38 @@ public function getFromRequest(
default => $this->getCredentials($userEntity),
};

// Apply override policy to determine effective values
$userVerification = $this->overridePolicy->getEffectiveValue(
'user_verification',
$optionsRequest->userVerification,
null // Will fallback to profile
);

$extensions = $this->getEffectiveExtensions($optionsRequest);

return $this->publicKeyCredentialRequestOptionsFactory->create(
$this->profile,
$allowedCredentials,
$optionsRequest->userVerification,
$userVerification,
$extensions
);
}

private function getEffectiveExtensions(
ServerPublicKeyCredentialRequestOptionsRequest $optionsRequest
): ?AuthenticationExtensions {
if (! $this->overridePolicy->canOverride('extensions') || ! is_array($optionsRequest->extensions)) {
return null; // Use profile extensions
}

$extensions = [];
foreach ($optionsRequest->extensions as $name => $data) {
$extensions[] = AuthenticationExtension::create($name, $data);
}

return AuthenticationExtensions::create($extensions);
}

/**
* @return PublicKeyCredentialDescriptor[]
*/
Expand Down
4 changes: 2 additions & 2 deletions src/symfony/src/DataCollector/WebauthnCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Webauthn\Bundle\DataCollector;

use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
Expand All @@ -21,8 +23,6 @@
use Webauthn\Event\AuthenticatorAssertionResponseValidationSucceededEvent;
use Webauthn\Event\AuthenticatorAttestationResponseValidationFailedEvent;
use Webauthn\Event\AuthenticatorAttestationResponseValidationSucceededEvent;
use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR;

class WebauthnCollector extends DataCollector implements EventSubscriberInterface
{
Expand Down
Loading
Loading