diff --git a/.github/workflows/integrate.yml b/.github/workflows/integrate.yml index 2e86a2db..4a8ca1fa 100644 --- a/.github/workflows/integrate.yml +++ b/.github/workflows/integrate.yml @@ -56,7 +56,6 @@ jobs: operating-system: - "ubuntu-latest" php-version: - - "8.1" - "8.2" - "8.3" dependencies: diff --git a/composer.json b/composer.json index 57fd6bc7..134cb92f 100644 --- a/composer.json +++ b/composer.json @@ -42,12 +42,10 @@ } }, "require": { - "php": ">=8.1", + "php": ">=8.2", "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "lcobucci/clock": "^2.2|^3.0", - "nyholm/psr7": "^1.5", "paragonie/constant_time_encoding": "^2.6", "psr/clock": "^1.0", "psr/event-dispatcher": "^1.0", @@ -58,6 +56,7 @@ "spomky-labs/cbor-php": "^3.0", "spomky-labs/pki-framework": "^1.0", "symfony/config": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", "symfony/dependency-injection": "^6.4|^7.0", "symfony/deprecation-contracts": "^3.2", "symfony/framework-bundle": "^6.4|^7.0", @@ -69,7 +68,7 @@ "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "web-auth/cose-lib": "^4.2.3", - "web-token/jwt-signature": "^3.1" + "web-token/jwt-signature": "^3.2.8" }, "replace": { "web-auth/webauthn-lib": "self.version", @@ -86,7 +85,7 @@ } }, "suggest": { - "psr/clock-implementation": "As of 4.5.x, the PSR Clock implementation will replace lcobucci/clock", + "psr/clock-implementation": "For datetime dependency injection", "psr/log-implementation": "Recommended to receive logs from the library", "symfony/security-bundle": "Symfony firewall using a JSON API (perfect for script applications)", "web-token/jwt-key-mgmt": "Mandatory for fetching Metadata Statement from distant sources", @@ -96,8 +95,9 @@ }, "require-dev": { "doctrine/dbal": "^3.4", - "doctrine/doctrine-bundle": "^2.7", - "doctrine/orm": "^2.13", + "doctrine/doctrine-bundle": "^2.8", + "doctrine/persistence": "^3.1", + "doctrine/orm": "^2.14", "ekino/phpstan-banned-code": "^1.0", "infection/infection": "^0.27", "matthiasnoback/symfony-dependency-injection-test": "^4.3|^5.0", @@ -125,9 +125,9 @@ "symfony/yaml": "^6.4|^7.0", "symplify/easy-coding-standard": "^12.0", "web-token/jwt-key-mgmt": "^3.1", - "web-token/jwt-signature-algorithm-ecdsa": "^3.1", - "web-token/jwt-signature-algorithm-eddsa": "^3.1", - "web-token/jwt-signature-algorithm-rsa": "^3.1" + "web-token/jwt-signature-algorithm-ecdsa": "^3.2.8", + "web-token/jwt-signature-algorithm-eddsa": "^3.2.8", + "web-token/jwt-signature-algorithm-rsa": "^3.2.8" }, "extra": { "thanks": { diff --git a/src/metadata-service/composer.json b/src/metadata-service/composer.json index 48b42c9b..edb7384c 100644 --- a/src/metadata-service/composer.json +++ b/src/metadata-service/composer.json @@ -20,9 +20,8 @@ } ], "require": { - "php": ">=8.1", + "php": ">=8.2", "ext-json": "*", - "lcobucci/clock": "^2.2|^3.0", "paragonie/constant_time_encoding": "^2.6", "psr/clock": "^1.0", "psr/event-dispatcher": "^1.0", @@ -30,7 +29,8 @@ "psr/http-factory": "^1.0", "psr/log": "^1.0|^2.0|^3.0", "spomky-labs/pki-framework": "^1.0", - "symfony/deprecation-contracts": "^3.2" + "symfony/deprecation-contracts": "^3.2", + "symfony/clock": "^6.4|^7.0" }, "autoload": { "psr-4": { diff --git a/src/symfony/composer.json b/src/symfony/composer.json index 81f3a27f..b315aa13 100644 --- a/src/symfony/composer.json +++ b/src/symfony/composer.json @@ -23,8 +23,7 @@ } ], "require": { - "php": ">=8.1", - "nyholm/psr7": "^1.5", + "php": ">=8.2", "psr/event-dispatcher": "^1.0", "spomky-labs/cbor-bundle": "^3.0", "symfony/config": "^6.4|^7.0", @@ -37,7 +36,7 @@ "symfony/serializer": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "web-auth/webauthn-lib": "self.version", - "web-token/jwt-signature": "^3.1" + "web-token/jwt-signature": "^3.2.8" }, "extra": { "thanks": { diff --git a/src/symfony/src/Resources/config/services.php b/src/symfony/src/Resources/config/services.php index d77331ac..eaf80c64 100644 --- a/src/symfony/src/Resources/config/services.php +++ b/src/symfony/src/Resources/config/services.php @@ -2,9 +2,9 @@ declare(strict_types=1); -use Lcobucci\Clock\SystemClock; use Psr\Http\Message\RequestFactoryInterface; use Psr\Log\NullLogger; +use Symfony\Component\Clock\NativeClock; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -65,8 +65,7 @@ $container ->set('webauthn.clock.default') - ->class(SystemClock::class) - ->factory([SystemClock::class, 'fromSystemTimezone']) + ->class(NativeClock::class) ; $container diff --git a/src/webauthn/composer.json b/src/webauthn/composer.json index e2f788ce..450a288e 100644 --- a/src/webauthn/composer.json +++ b/src/webauthn/composer.json @@ -20,7 +20,7 @@ } ], "require": { - "php": ">=8.1", + "php": ">=8.2", "ext-json": "*", "ext-openssl": "*", "ext-mbstring": "*", diff --git a/src/webauthn/src/AttestationStatement/TPMAttestationStatementSupport.php b/src/webauthn/src/AttestationStatement/TPMAttestationStatementSupport.php index 8d31c45f..809a542b 100644 --- a/src/webauthn/src/AttestationStatement/TPMAttestationStatementSupport.php +++ b/src/webauthn/src/AttestationStatement/TPMAttestationStatementSupport.php @@ -12,9 +12,6 @@ use Cose\Key\OkpKey; use Cose\Key\RsaKey; use DateTimeImmutable; -use DateTimeZone; -use Lcobucci\Clock\Clock; -use Lcobucci\Clock\SystemClock; use ParagonIE\ConstantTime\Base64UrlSafe; use Psr\Clock\ClockInterface; use Psr\EventDispatcher\EventDispatcherInterface; @@ -40,21 +37,11 @@ final class TPMAttestationStatementSupport implements AttestationStatementSupport, CanDispatchEvents { - private readonly Clock|ClockInterface $clock; - private EventDispatcherInterface $dispatcher; - public function __construct(null|Clock|ClockInterface $clock = null) - { - if ($clock === null) { - trigger_deprecation( - 'web-auth/metadata-service', - '4.5.0', - 'The parameter "$clock" will become mandatory in 5.0.0. Please set a valid PSR Clock implementation instead of "null".' - ); - $clock = new SystemClock(new DateTimeZone('UTC')); - } - $this->clock = $clock; + public function __construct( + private readonly ClockInterface $clock + ) { $this->dispatcher = new NullEventDispatcher(); } @@ -63,7 +50,7 @@ public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): v $this->dispatcher = $eventDispatcher; } - public static function create(null|Clock|ClockInterface $clock = null): self + public static function create(ClockInterface $clock): self { return new self($clock); } diff --git a/tests/symfony/config/config.yml b/tests/symfony/config/config.yml index 10c12e23..4bc3b6cf 100644 --- a/tests/symfony/config/config.yml +++ b/tests/symfony/config/config.yml @@ -4,12 +4,17 @@ parameters: framework: test: true secret: 'test' + handle_all_throwables: true serializer: enabled: true validation: enabled: true + email_validation_mode: html5 session: storage_factory_id: 'session.storage.factory.mock_file' + save_path: "session.handler.native_file" + cookie_samesite: "lax" + cookie_secure: "auto" fragments: ~ http_method_override: true http_client: @@ -17,6 +22,11 @@ framework: router: utf8: true resource: "%kernel.project_dir%/tests/symfony/config/routing.php" + uid: + default_uuid_version: 7 + time_based_uuid_version: 7 + php_errors: + log: true services: _defaults: @@ -85,7 +95,6 @@ services: arguments: - '@Webauthn\MetadataService\Service\ChainedMetadataServices' - doctrine: dbal: driver: 'pdo_mysql' @@ -97,6 +106,7 @@ doctrine: url: '%env(resolve:DATABASE_URL)%' orm: + enable_lazy_ghost_objects: true auto_generate_proxy_classes: true naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware auto_mapping: true diff --git a/tests/symfony/functional/Attestation/AdditionalAuthenticatorTest.php b/tests/symfony/functional/Attestation/AdditionalAuthenticatorTest.php index 488a041a..ea04e96b 100644 --- a/tests/symfony/functional/Attestation/AdditionalAuthenticatorTest.php +++ b/tests/symfony/functional/Attestation/AdditionalAuthenticatorTest.php @@ -35,28 +35,16 @@ */ final class AdditionalAuthenticatorTest extends WebTestCase { - private KernelBrowser $client; - - private OptionsStorage $storage; - - protected function setUp(): void - { - $this->client = static::createClient([], [ - 'HTTPS' => 'on', - ]); - - $this->storage = static::getContainer()->get(CustomSessionStorage::class); - } - #[Test] public function anExistingUserCanAskForOptionsUsingTheDedicatedController(): void { - $this->logIn(); - $this->client->request(Request::METHOD_POST, '/devices/add/options', [], [], [ + $client = static::createClient([], ['HTTPS' => 'on']); + $this->logIn($client); + $client->request(Request::METHOD_POST, '/devices/add/options', [], [], [ 'CONTENT_TYPE' => 'application/json', 'HTTP_HOST' => 'test.com', ], json_encode([], JSON_THROW_ON_ERROR)); - $response = $this->client->getResponse(); + $response = $client->getResponse(); $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR); static::assertResponseIsSuccessful(); @@ -92,11 +80,12 @@ public function thePublicKeyCredentialDataCanBeLoaded(): void #[Depends('thePublicKeyCredentialDataCanBeLoaded')] public function withTheOptionAnExistingUserCanRegisterNewAnotherAuthenticator(): void { + $client = static::createClient([], ['HTTPS' => 'on']); /** @var PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository */ $publicKeyCredentialSourceRepository = self::$kernel ->getContainer() ->get(PublicKeyCredentialSourceRepository::class); - $this->logIn(); + $this->logIn($client); $publicKeyCredentialUserEntity = PublicKeyCredentialUserEntity::create('test@foo.com', random_bytes( 64 @@ -112,7 +101,8 @@ public function withTheOptionAnExistingUserCanRegisterNewAnotherAuthenticator(): [PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ES256)] ); - $this->storage->store(Item::create( + $storage = static::getContainer()->get(CustomSessionStorage::class); + $storage->store(Item::create( $publicKeyCredentialCreationOptions, $publicKeyCredentialCreationOptions->user )); @@ -124,7 +114,7 @@ public function withTheOptionAnExistingUserCanRegisterNewAnotherAuthenticator(): $publicKeyCredentialSourceRepository->findAllForUserEntity($publicKeyCredentialCreationOptions->user) ); $body = '{"id":"mMihuIx9LukswxBOMjMHDf6EAONOy7qdWhaQQ7dOtViR2cVB_MNbZxURi2cvgSvKSILb3mISe9lPNG9sYgojuY5iNinYOg6hRVxmm0VssuNG2pm1-RIuTF9DUtEJZEEK","type":"public-key","rawId":"mMihuIx9LukswxBOMjMHDf6EAONOy7qdWhaQQ7dOtViR2cVB/MNbZxURi2cvgSvKSILb3mISe9lPNG9sYgojuY5iNinYOg6hRVxmm0VssuNG2pm1+RIuTF9DUtEJZEEK","response":{"clientDataJSON":"eyJjaGFsbGVuZ2UiOiI5V3FncFJJWXZHTUNVWWlGVDIwbzFVN2hTRDE5M2sxMXp1NHRLUDd3UmNyRTI2enMxemM0TEh5UGludlBHUzg2d3U2YkR2cHdidDhYcDJiUTNWQlJTUSIsImNsaWVudEV4dGVuc2lvbnMiOnt9LCJoYXNoQWxnb3JpdGhtIjoiU0hBLTI1NiIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0Ojg0NDMiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0","attestationObject":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjkSZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2NBAAAAAAAAAAAAAAAAAAAAAAAAAAAAYJjIobiMfS7pLMMQTjIzBw3+hADjTsu6nVoWkEO3TrVYkdnFQfzDW2cVEYtnL4ErykiC295iEnvZTzRvbGIKI7mOYjYp2DoOoUVcZptFbLLjRtqZtfkSLkxfQ1LRCWRBCqUBAgMmIAEhWCAcPxwKyHADVjTgTsat4R/Jax6PWte50A8ZasMm4w6RxCJYILt0FCiGwC6rBrh3ySNy0yiUjZpNGAhW+aM9YYyYnUTJ"}}'; - $this->client->request( + $client->request( Request::METHOD_POST, '/devices/add', [], @@ -135,7 +125,7 @@ public function withTheOptionAnExistingUserCanRegisterNewAnotherAuthenticator(): ], $body ); - $response = $this->client->getResponse(); + $response = $client->getResponse(); $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR); static::assertResponseStatusCodeSame(201); @@ -155,12 +145,13 @@ public function withTheOptionAnExistingUserCanRegisterNewAnotherAuthenticator(): #[Test] public function anExistingUserCanGetOptionsTestItsAuthenticators(): void { - $this->logIn(); - $this->client->request(Request::METHOD_POST, '/devices/test/options', [], [], [ + $client = static::createClient([], ['HTTPS' => 'on']); + $this->logIn($client); + $client->request(Request::METHOD_POST, '/devices/test/options', [], [], [ 'CONTENT_TYPE' => 'application/json', 'HTTP_HOST' => 'test.com', ], json_encode([], JSON_THROW_ON_ERROR)); - $response = $this->client->getResponse(); + $response = $client->getResponse(); $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR); static::assertResponseIsSuccessful(); @@ -172,7 +163,7 @@ public function anExistingUserCanGetOptionsTestItsAuthenticators(): void static::assertSame('ok', $data['status']); } - private function logIn(): void + private function logIn(KernelBrowser $client): void { /** @var SerializerInterface $serializer */ $serializer = static::getContainer()->get(SerializerInterface::class); @@ -231,13 +222,13 @@ private function logIn(): void $domains = array_unique( array_map( static fn (Cookie $cookie) => $cookie->getName() === $session->getName() ? $cookie->getDomain() : '', - $this->client->getCookieJar() + $client->getCookieJar() ->all() ) ) ?: ['']; foreach ($domains as $domain) { $cookie = new Cookie($session->getName(), $session->getId(), null, null, $domain); - $this->client->getCookieJar() + $client->getCookieJar() ->set($cookie); } } diff --git a/tests/symfony/functional/Attestation/PackedAttestationStatementTest.php b/tests/symfony/functional/Attestation/PackedAttestationStatementTest.php index f99f1219..4a8ac45b 100644 --- a/tests/symfony/functional/Attestation/PackedAttestationStatementTest.php +++ b/tests/symfony/functional/Attestation/PackedAttestationStatementTest.php @@ -28,7 +28,6 @@ final class PackedAttestationStatementTest extends KernelTestCase #[Test] public function aPackedAttestationWithSelfStatementCanBeVerified(): void { - self::bootKernel(); $publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::create( PublicKeyCredentialRpEntity::create('My Application'), PublicKeyCredentialUserEntity::create( diff --git a/tests/symfony/functional/Firewall/RegistrationAreaTest.php b/tests/symfony/functional/Firewall/RegistrationAreaTest.php index da79de6b..47873b03 100644 --- a/tests/symfony/functional/Firewall/RegistrationAreaTest.php +++ b/tests/symfony/functional/Firewall/RegistrationAreaTest.php @@ -6,11 +6,9 @@ use Cose\Algorithms; use PHPUnit\Framework\Attributes\Test; -use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Request; use Webauthn\Bundle\Security\Storage\Item; -use Webauthn\Bundle\Security\Storage\OptionsStorage; use Webauthn\PublicKeyCredentialCreationOptions; use Webauthn\PublicKeyCredentialParameters; use Webauthn\PublicKeyCredentialRpEntity; @@ -29,30 +27,19 @@ */ final class RegistrationAreaTest extends WebTestCase { - private KernelBrowser $client; - - private OptionsStorage $storage; - - protected function setUp(): void - { - $this->client = static::createClient([], [ - 'HTTPS' => 'on', - ]); - - $this->storage = static::getContainer()->get(CustomSessionStorage::class); - } - #[Test] public function aRequestWithoutUsernameCanBeProcessed(): void { $content = [ 'displayName' => 'FOO', ]; - $this->client->request(Request::METHOD_POST, '/api/register/options', [], [], [ + + $client = static::createClient([], ['HTTPS' => 'on']); + $client->request(Request::METHOD_POST, '/api/register/options', [], [], [ 'CONTENT_TYPE' => 'application/json', 'HTTP_HOST' => 'test.com', ], json_encode($content)); - $response = $this->client->getResponse(); + $response = $client->getResponse(); $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR); static::assertArrayHasKey('status', $data); @@ -66,11 +53,12 @@ public function aRequestWithoutDisplayNameCanBeProcessed(): void $content = [ 'username' => 'foo', ]; - $this->client->request(Request::METHOD_POST, '/api/register/options', [], [], [ + $client = static::createClient([], ['HTTPS' => 'on']); + $client->request(Request::METHOD_POST, '/api/register/options', [], [], [ 'CONTENT_TYPE' => 'application/json', 'HTTP_HOST' => 'test.com', ], json_encode($content)); - $response = $this->client->getResponse(); + $response = $client->getResponse(); $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR); static::assertArrayHasKey('status', $data); @@ -91,11 +79,12 @@ public function aValidRequestProcessed(): void ], 'attestation' => 'indirect', ]; - $this->client->request(Request::METHOD_POST, '/api/register/options', [], [], [ + $client = static::createClient([], ['HTTPS' => 'on']); + $client->request(Request::METHOD_POST, '/api/register/options', [], [], [ 'CONTENT_TYPE' => 'application/json', 'HTTP_HOST' => 'test.com', ], json_encode($content)); - $response = $this->client->getResponse(); + $response = $client->getResponse(); $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR); static::assertArrayHasKey('status', $data); @@ -126,11 +115,12 @@ public function aValidRequestProcessedOnOtherHost(): void 'requireResidentKey' => true, ], ]; - $this->client->request(Request::METHOD_POST, '/api/register/options', [], [], [ + $client = static::createClient([], ['HTTPS' => 'on']); + $client->request(Request::METHOD_POST, '/api/register/options', [], [], [ 'CONTENT_TYPE' => 'application/json', 'HTTP_HOST' => 'foo.com', ], json_encode($content)); - $response = $this->client->getResponse(); + $response = $client->getResponse(); $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR); static::assertArrayHasKey('status', $data); @@ -166,11 +156,12 @@ public function aValidRequestProcessedWithExtensions(): void 'def' => '123', ], ]; - $this->client->request(Request::METHOD_POST, '/api/register/options', [], [], [ + $client = static::createClient([], ['HTTPS' => 'on']); + $client->request(Request::METHOD_POST, '/api/register/options', [], [], [ 'CONTENT_TYPE' => 'application/json', 'HTTP_HOST' => 'test.com', ], json_encode($content)); - $response = $this->client->getResponse(); + $response = $client->getResponse(); $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR); static::assertArrayHasKey('status', $data); @@ -205,11 +196,12 @@ public function aRegistrationOptionsRequestCanBeAcceptedForExistingUsers(): void 'username' => 'admin', 'displayName' => 'Admin', ]; - $this->client->request(Request::METHOD_POST, '/api/register/options', [], [], [ + $client = static::createClient([], ['HTTPS' => 'on']); + $client->request(Request::METHOD_POST, '/api/register/options', [], [], [ 'CONTENT_TYPE' => 'application/json', 'HTTP_HOST' => 'test.com', ], json_encode($content)); - $response = $this->client->getResponse(); + $response = $client->getResponse(); $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR); static::assertArrayHasKey('status', $data); @@ -222,11 +214,12 @@ public function aRegistrationResultRequestCannotBeAcceptedIfNoOptionsAreAvailabl { $content = '{"id":"mMihuIx9LukswxBOMjMHDf6EAONOy7qdWhaQQ7dOtViR2cVB_MNbZxURi2cvgSvKSILb3mISe9lPNG9sYgojuY5iNinYOg6hRVxmm0VssuNG2pm1-RIuTF9DUtEJZEEK","type":"public-key","rawId":"mMihuIx9LukswxBOMjMHDf6EAONOy7qdWhaQQ7dOtViR2cVB/MNbZxURi2cvgSvKSILb3mISe9lPNG9sYgojuY5iNinYOg6hRVxmm0VssuNG2pm1+RIuTF9DUtEJZEEK","response":{"clientDataJSON":"eyJjaGFsbGVuZ2UiOiI5V3FncFJJWXZHTUNVWWlGVDIwbzFVN2hTRDE5M2sxMXp1NHRLUDd3UmNyRTI2enMxemM0TEh5UGludlBHUzg2d3U2YkR2cHdidDhYcDJiUTNWQlJTUSIsImNsaWVudEV4dGVuc2lvbnMiOnt9LCJoYXNoQWxnb3JpdGhtIjoiU0hBLTI1NiIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0Ojg0NDMiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0","attestationObject":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjkSZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2NBAAAAAAAAAAAAAAAAAAAAAAAAAAAAYJjIobiMfS7pLMMQTjIzBw3+hADjTsu6nVoWkEO3TrVYkdnFQfzDW2cVEYtnL4ErykiC295iEnvZTzRvbGIKI7mOYjYp2DoOoUVcZptFbLLjRtqZtfkSLkxfQ1LRCWRBCqUBAgMmIAEhWCAcPxwKyHADVjTgTsat4R/Jax6PWte50A8ZasMm4w6RxCJYILt0FCiGwC6rBrh3ySNy0yiUjZpNGAhW+aM9YYyYnUTJ"}}'; - $this->client->request(Request::METHOD_POST, '/api/register', [], [], [ + $client = static::createClient([], ['HTTPS' => 'on']); + $client->request(Request::METHOD_POST, '/api/register', [], [], [ 'CONTENT_TYPE' => 'application/json', 'HTTP_HOST' => 'test.com', ], $content); - $response = $this->client->getResponse(); + $response = $client->getResponse(); $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR); static::assertArrayHasKey('status', $data); @@ -255,17 +248,19 @@ public function aValidRegistrationResultRequestIsCorrectlyManaged(): void $content = '{"id":"mMihuIx9LukswxBOMjMHDf6EAONOy7qdWhaQQ7dOtViR2cVB_MNbZxURi2cvgSvKSILb3mISe9lPNG9sYgojuY5iNinYOg6hRVxmm0VssuNG2pm1-RIuTF9DUtEJZEEK","type":"public-key","rawId":"mMihuIx9LukswxBOMjMHDf6EAONOy7qdWhaQQ7dOtViR2cVB/MNbZxURi2cvgSvKSILb3mISe9lPNG9sYgojuY5iNinYOg6hRVxmm0VssuNG2pm1+RIuTF9DUtEJZEEK","response":{"clientDataJSON":"eyJjaGFsbGVuZ2UiOiI5V3FncFJJWXZHTUNVWWlGVDIwbzFVN2hTRDE5M2sxMXp1NHRLUDd3UmNyRTI2enMxemM0TEh5UGludlBHUzg2d3U2YkR2cHdidDhYcDJiUTNWQlJTUSIsImNsaWVudEV4dGVuc2lvbnMiOnt9LCJoYXNoQWxnb3JpdGhtIjoiU0hBLTI1NiIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0Ojg0NDMiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0","attestationObject":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjkSZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2NBAAAAAAAAAAAAAAAAAAAAAAAAAAAAYJjIobiMfS7pLMMQTjIzBw3+hADjTsu6nVoWkEO3TrVYkdnFQfzDW2cVEYtnL4ErykiC295iEnvZTzRvbGIKI7mOYjYp2DoOoUVcZptFbLLjRtqZtfkSLkxfQ1LRCWRBCqUBAgMmIAEhWCAcPxwKyHADVjTgTsat4R/Jax6PWte50A8ZasMm4w6RxCJYILt0FCiGwC6rBrh3ySNy0yiUjZpNGAhW+aM9YYyYnUTJ"}}'; - $pkcsRepository = $this->client->getContainer() + $client = static::createClient([], ['HTTPS' => 'on']); + $pkcsRepository = $client->getContainer() ->get(PublicKeyCredentialSourceRepository::class); $pkcsRepository->clearCredentials(); - $this->storage->store(Item::create($publicKeyCredentialCreationOptions, $publicKeyCredentialUserEntity)); + $storage = static::getContainer()->get(CustomSessionStorage::class); + $storage->store(Item::create($publicKeyCredentialCreationOptions, $publicKeyCredentialUserEntity)); - $this->client->request(Request::METHOD_POST, '/api/register', [], [], [ + $client->request(Request::METHOD_POST, '/api/register', [], [], [ 'CONTENT_TYPE' => 'application/json', 'HTTP_HOST' => 'localhost', ], $content); - $response = $this->client->getResponse(); + $response = $client->getResponse(); $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR); static::assertArrayHasKey('status', $data); @@ -274,12 +269,12 @@ public function aValidRegistrationResultRequestIsCorrectlyManaged(): void static::assertArrayHasKey('errorMessage', $data); static::assertSame('', $data['errorMessage']); - $pkueRepository = $this->client->getContainer() + $pkueRepository = $client->getContainer() ->get(PublicKeyCredentialUserEntityRepository::class); $user = $pkueRepository->findOneByUsername('test@foo.com'); static::assertInstanceOf(User::class, $user); - static::assertTrue($this->client->getRequest()->getSession()->has('_security_main')); - static::assertTrue($this->client->getResponse()->headers->has('set-cookie')); + static::assertTrue($client->getRequest()->getSession()->has('_security_main')); + static::assertTrue($client->getResponse()->headers->has('set-cookie')); } } diff --git a/tests/symfony/functional/Firewall/SecuredAreaTest.php b/tests/symfony/functional/Firewall/SecuredAreaTest.php index 7ccef63b..8f9d96c8 100644 --- a/tests/symfony/functional/Firewall/SecuredAreaTest.php +++ b/tests/symfony/functional/Firewall/SecuredAreaTest.php @@ -22,23 +22,11 @@ */ final class SecuredAreaTest extends WebTestCase { - private KernelBrowser $client; - - private OptionsStorage $storage; - - protected function setUp(): void - { - $this->client = static::createClient([], [ - 'HTTPS' => 'on', - ]); - - $this->storage = static::getContainer()->get(CustomSessionStorage::class); - } - #[Test] public function aClientCannotAccessToTheResourceIfUserIsNotAuthenticated(): void { - $this->client->request('GET', '/admin', [], [], [ + $client = static::createClient([], ['HTTPS' => 'on']); + $client->request('GET', '/admin', [], [], [ 'HTTPS' => 'on', ]); @@ -51,14 +39,15 @@ public function aClientCanSubmitUsernameToGetWebauthnOptions(): void $body = [ 'username' => 'admin', ]; - $this->client->request('POST', '/api/login/options', [], [], [ + $client = static::createClient([], ['HTTPS' => 'on']); + $client->request('POST', '/api/login/options', [], [], [ 'CONTENT_TYPE' => 'application/json', 'HTTP_HOST' => 'test.com', 'HTTPS' => 'on', ], json_encode($body, JSON_THROW_ON_ERROR)); static::assertResponseIsSuccessful(); - $json = json_decode($this->client->getResponse()->getContent(), true, 512, JSON_THROW_ON_ERROR); + $json = json_decode($client->getResponse()->getContent(), true, 512, JSON_THROW_ON_ERROR); static::assertArrayHasKey('challenge', $json); static::assertArrayHasKey('rpId', $json); static::assertArrayHasKey('userVerification', $json); @@ -70,8 +59,8 @@ public function aClientCanSubmitUsernameToGetWebauthnOptions(): void public function aUserCannotBeBeAuthenticatedInAbsenceOfOptions(): void { $assertion = '{"id":"eHouz_Zi7-BmByHjJ_tx9h4a1WZsK4IzUmgGjkhyOodPGAyUqUp_B9yUkflXY3yHWsNtsrgCXQ3HjAIFUeZB-w","type":"public-key","rawId":"eHouz/Zi7+BmByHjJ/tx9h4a1WZsK4IzUmgGjkhyOodPGAyUqUp/B9yUkflXY3yHWsNtsrgCXQ3HjAIFUeZB+w==","response":{"authenticatorData":"SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAAew","clientDataJSON":"eyJjaGFsbGVuZ2UiOiJHMEpiTExuZGVmM2EwSXkzUzJzU1FBOHVPNFNPX3plNkZaTUF1UEk2LXhJIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6ODQ0MyIsInR5cGUiOiJ3ZWJhdXRobi5nZXQifQ","signature":"MEUCIEY/vcNkbo/LdMTfLa24ZYLlMMVMRd8zXguHBvqud9AJAiEAwCwpZpvcMaqCrwv85w/8RGiZzE+gOM61ffxmgEDeyhM=","userHandle":null}}'; - - $this->client->request('POST', '/api/login', [], [], [ + $client = static::createClient([], ['HTTPS' => 'on']); + $client->request('POST', '/api/login', [], [], [ 'CONTENT_TYPE' => 'application/json', 'HTTP_HOST' => 'test.com', ], $assertion); @@ -79,7 +68,7 @@ public function aUserCannotBeBeAuthenticatedInAbsenceOfOptions(): void self::assertResponseStatusCodeSame(Response::HTTP_UNAUTHORIZED); static::assertSame( '{"status":"error","errorMessage":"No public key credential options available for this session."}', - $this->client->getResponse() + $client->getResponse() ->getContent() ); } @@ -87,6 +76,7 @@ public function aUserCannotBeBeAuthenticatedInAbsenceOfOptions(): void #[Test] public function aUserCanBeAuthenticatedAndAccessToTheProtectedResource(): void { + $client = static::createClient([], ['HTTPS' => 'on']); $publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create( base64_decode('G0JbLLndef3a0Iy3S2sSQA8uO4SO/ze6FZMAuPI6+xI=', true) ); @@ -102,14 +92,15 @@ public function aUserCanBeAuthenticatedAndAccessToTheProtectedResource(): void ), ]; - $this->storage->store(Item::create( + $storage = static::getContainer()->get(CustomSessionStorage::class); + $storage->store(Item::create( $publicKeyCredentialRequestOptions, PublicKeyCredentialUserEntity::create('admin', 'foo', 'Foo BAR (-_-)') )); $assertion = '{"id":"eHouz_Zi7-BmByHjJ_tx9h4a1WZsK4IzUmgGjkhyOodPGAyUqUp_B9yUkflXY3yHWsNtsrgCXQ3HjAIFUeZB-w","type":"public-key","rawId":"eHouz/Zi7+BmByHjJ/tx9h4a1WZsK4IzUmgGjkhyOodPGAyUqUp/B9yUkflXY3yHWsNtsrgCXQ3HjAIFUeZB+w==","response":{"authenticatorData":"SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAAew","clientDataJSON":"eyJjaGFsbGVuZ2UiOiJHMEpiTExuZGVmM2EwSXkzUzJzU1FBOHVPNFNPX3plNkZaTUF1UEk2LXhJIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6ODQ0MyIsInR5cGUiOiJ3ZWJhdXRobi5nZXQifQ","signature":"MEUCIEY/vcNkbo/LdMTfLa24ZYLlMMVMRd8zXguHBvqud9AJAiEAwCwpZpvcMaqCrwv85w/8RGiZzE+gOM61ffxmgEDeyhM=","userHandle":null}}'; - $this->client->request('POST', '/api/login', [], [], [ + $client->request('POST', '/api/login', [], [], [ 'CONTENT_TYPE' => 'application/json', 'HTTP_HOST' => 'localhost', ], $assertion); @@ -117,14 +108,14 @@ public function aUserCanBeAuthenticatedAndAccessToTheProtectedResource(): void static::assertResponseIsSuccessful(); static::assertSame( '{"status":"ok","errorMessage":"","userIdentifier":"admin"}', - $this->client->getResponse() + $client->getResponse() ->getContent() ); - static::assertTrue($this->client->getRequest()->getSession()->has('_security_main')); + static::assertTrue($client->getRequest()->getSession()->has('_security_main')); - $this->client->request('GET', '/admin'); + $client->request('GET', '/admin'); - static::assertSame('["Hello admin"]', $this->client->getResponse()->getContent()); + static::assertSame('["Hello admin"]', $client->getResponse()->getContent()); static::assertResponseIsSuccessful(); } }