Skip to content

Commit 27c6853

Browse files
committed
Add integration tests for validator constraints
1 parent 1a0f8f2 commit 27c6853

File tree

7 files changed

+142
-7
lines changed

7 files changed

+142
-7
lines changed

app/composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@
1212
"lcobucci/jwt": "^5.0",
1313
"spomky-labs/otphp": "^11.0",
1414
"symfony/dotenv": "^6.4 || ^7.0",
15+
"symfony/form": "^6.4 || ^7.0",
1516
"symfony/mailer": "^6.4 || ^7.0",
1617
"symfony/monolog-bundle": "^3.1",
1718
"symfony/rate-limiter": "^6.4 || ^7.0",
1819
"symfony/runtime": "^6.4 || ^7.0",
1920
"symfony/security-bundle": "^6.4 || ^7.0",
2021
"symfony/translation": "^6.4 || ^7.0",
2122
"symfony/twig-bundle": "^6.4 || ^7.0",
23+
"symfony/validator": "^6.4 || ^7.0",
2224
"symfony/web-profiler-bundle": "^6.4 || ^7.0",
2325
"symfony/yaml": "^6.4 || ^7.0"
2426
},

app/config/reference.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@
146146
* cookie_name?: scalar|null, // The name of the cookie to use when using stateless protection. // Default: "csrf-token"
147147
* },
148148
* form?: bool|array{ // Form configuration
149-
* enabled?: bool, // Default: false
149+
* enabled?: bool, // Default: true
150150
* csrf_protection?: array{
151151
* enabled?: scalar|null, // Default: null
152152
* token_id?: scalar|null, // Default: null
@@ -328,7 +328,7 @@
328328
* }>,
329329
* },
330330
* validation?: bool|array{ // Validation configuration
331-
* enabled?: bool, // Default: false
331+
* enabled?: bool, // Default: true
332332
* cache?: scalar|null, // Deprecated: Setting the "framework.validation.cache.cache" configuration option is deprecated. It will be removed in version 8.0.
333333
* enable_attributes?: bool, // Default: true
334334
* static_method?: list<scalar|null>,
@@ -474,7 +474,7 @@
474474
* max_host_connections?: int, // The maximum number of connections to a single host.
475475
* default_options?: array{
476476
* headers?: array<string, mixed>,
477-
* vars?: list<mixed>,
477+
* vars?: array<string, mixed>,
478478
* max_redirects?: int, // The maximum number of redirects to follow.
479479
* http_version?: scalar|null, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version.
480480
* resolve?: array<string, scalar|null>,
@@ -497,7 +497,7 @@
497497
* md5?: mixed,
498498
* },
499499
* crypto_method?: scalar|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants.
500-
* extra?: list<mixed>,
500+
* extra?: array<string, mixed>,
501501
* rate_limiter?: scalar|null, // Rate limiter name to use for throttling requests. // Default: null
502502
* caching?: bool|array{ // Caching configuration.
503503
* enabled?: bool, // Default: false
@@ -550,7 +550,7 @@
550550
* md5?: mixed,
551551
* },
552552
* crypto_method?: scalar|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants.
553-
* extra?: list<mixed>,
553+
* extra?: array<string, mixed>,
554554
* rate_limiter?: scalar|null, // Rate limiter name to use for throttling requests. // Default: null
555555
* caching?: bool|array{ // Caching configuration.
556556
* enabled?: bool, // Default: false
@@ -1125,9 +1125,11 @@
11251125
* handlers?: array<string, array{ // Default: []
11261126
* type: scalar|null,
11271127
* id?: scalar|null,
1128+
* enabled?: bool, // Default: true
11281129
* priority?: scalar|null, // Default: 0
11291130
* level?: scalar|null, // Default: "DEBUG"
11301131
* bubble?: bool, // Default: true
1132+
* interactive_only?: bool, // Default: false
11311133
* app_name?: scalar|null, // Default: null
11321134
* fill_extra_context?: bool, // Default: false
11331135
* include_stacktraces?: bool, // Default: false
@@ -1173,6 +1175,7 @@
11731175
* include_extra?: scalar|null, // Default: false
11741176
* icon_emoji?: scalar|null, // Default: null
11751177
* webhook_url?: scalar|null,
1178+
* exclude_fields?: list<scalar|null>,
11761179
* team?: scalar|null,
11771180
* notify?: scalar|null, // Default: false
11781181
* nickname?: scalar|null, // Default: "Monolog"
@@ -1205,6 +1208,7 @@
12051208
* disable_notification?: bool|null, // Default: null
12061209
* split_long_messages?: bool, // Default: false
12071210
* delay_between_messages?: bool, // Default: false
1211+
* topic?: int, // Default: null
12081212
* factor?: int, // Default: 1
12091213
* tags?: list<scalar|null>,
12101214
* console_formater_options?: mixed, // Deprecated: "monolog.handlers..console_formater_options.console_formater_options" is deprecated, use "monolog.handlers..console_formater_options.console_formatter_options" instead.
@@ -1216,6 +1220,7 @@
12161220
* hostname?: scalar|null,
12171221
* port?: scalar|null, // Default: 12201
12181222
* chunk_size?: scalar|null, // Default: 1420
1223+
* encoder?: "json"|"compressed_json",
12191224
* },
12201225
* mongo?: string|array{
12211226
* id?: scalar|null,
@@ -1226,8 +1231,17 @@
12261231
* database?: scalar|null, // Default: "monolog"
12271232
* collection?: scalar|null, // Default: "logs"
12281233
* },
1234+
* mongodb?: string|array{
1235+
* id?: scalar|null, // ID of a MongoDB\Client service
1236+
* uri?: scalar|null,
1237+
* username?: scalar|null,
1238+
* password?: scalar|null,
1239+
* database?: scalar|null, // Default: "monolog"
1240+
* collection?: scalar|null, // Default: "logs"
1241+
* },
12291242
* elasticsearch?: string|array{
12301243
* id?: scalar|null,
1244+
* hosts?: list<scalar|null>,
12311245
* host?: scalar|null,
12321246
* port?: scalar|null, // Default: 9200
12331247
* transport?: scalar|null, // Default: "Http"

app/src/Controller/MembersController.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
namespace App\Controller;
66

7+
use App\Form\TwoFactorFormData;
78
use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface as GoogleAuthenticatorTwoFactorInterface;
89
use Scheb\TwoFactorBundle\Model\Totp\TwoFactorInterface as TotpTwoFactorInterface;
910
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
11+
use Symfony\Component\HttpFoundation\Request;
1012
use Symfony\Component\HttpFoundation\Response;
1113
use Symfony\Component\Routing\Attribute\Route;
1214
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
@@ -23,4 +25,25 @@ public function membersArea(TokenStorageInterface $tokenStorage): Response
2325
'displayQrCodeTotp' => $user instanceof TotpTwoFactorInterface && $user->isTotpAuthenticationEnabled(),
2426
]);
2527
}
28+
29+
#[Route('/members/validators', name: 'validators_form')]
30+
public function validators(Request $request): Response
31+
{
32+
$formData = new TwoFactorFormData();
33+
$form = $this->createFormBuilder($formData)
34+
->add('googleTotpCode')
35+
->add('totpCode')
36+
->getForm();
37+
38+
$isValid = null;
39+
$form->handleRequest($request);
40+
if ($form->isSubmitted()) {
41+
$isValid = $form->isValid();
42+
}
43+
44+
return $this->render('members/validators.html.twig', [
45+
'form' => $form,
46+
'isValid' => $isValid,
47+
]);
48+
}
2649
}

app/src/Form/TwoFactorFormData.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Form;
6+
7+
use Scheb\TwoFactorBundle\Security\TwoFactor\Validator\Constraints\UserGoogleTotpCode;
8+
use Scheb\TwoFactorBundle\Security\TwoFactor\Validator\Constraints\UserTotpCode;
9+
10+
class TwoFactorFormData
11+
{
12+
#[UserGoogleTotpCode]
13+
public string $googleTotpCode;
14+
15+
#[UserTotpCode]
16+
public string $totpCode;
17+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{% extends "base.html.twig" %}
2+
3+
{% block body %}
4+
<h2>Form Validators</h2>
5+
{% if isValid %}
6+
<p class="notice">All codes valid!</p>
7+
{% endif %}
8+
{{ form_start(form) }}
9+
{{ form_widget(form) }}
10+
<p><button type="submit" name="submit-button">Check Codes</button></p>
11+
{{ form_end(form) }}
12+
{% endblock %}

app/tests/TestCase.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ abstract class TestCase extends WebTestCase
3131
private const TRUSTED_DEVICE_COOKIE_NAME = 'trusted_device';
3232
private const REMEMBER_ME_COOKIE_NAME = 'REMEMBERME';
3333

34-
private KernelBrowser $client;
34+
protected KernelBrowser $client;
3535

3636
// //////////////////// CONFIGURATION
3737

@@ -224,6 +224,11 @@ protected function navigateTo2faForm(): Crawler
224224
return $this->client->request('GET', '/2fa');
225225
}
226226

227+
protected function navigateToValidatorsForm(): Crawler
228+
{
229+
return $this->client->request('GET', '/members/validators');
230+
}
231+
227232
// //////////////////// ASSERTS
228233

229234
protected function assertLoggerHasInfo(string $message): void
@@ -337,7 +342,7 @@ protected function assertHasTrustedDeviceCookieSet(): void
337342
$this->assertNotNull($this->client->getCookieJar()->get(self::TRUSTED_DEVICE_COOKIE_NAME), 'Trusted device cookie must be set');
338343
}
339344

340-
private function assertResponseStatusCode(int $code): void
345+
protected function assertResponseStatusCode(int $code): void
341346
{
342347
$this->assertEquals(
343348
$code,
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use App\Tests\TestCase;
6+
use Symfony\Component\DomCrawler\Crawler;
7+
8+
class ValidatorConstraintsTest extends TestCase
9+
{
10+
public function testValidatorConstraintsWithWrongCodes(): void
11+
{
12+
// Login
13+
$this->setAll2faProvidersEnabled(false);
14+
$this->performLogin();
15+
16+
$submittedPage = $this->submitValidatorForm('wrongCode', 'wrongCode');
17+
18+
$this->assertValidatedWrongCodes($submittedPage);
19+
}
20+
21+
public function testValidatorConstraintsWithCorrectCodes(): void
22+
{
23+
// Login
24+
$this->setAll2faProvidersEnabled(false);
25+
$this->performLogin();
26+
27+
$submittedPage = $this->submitValidatorForm($this->getGoogleAuthenticatorCode(), $this->getTotpCode());
28+
29+
$this->assertValidatedCorrectCodes($submittedPage);
30+
}
31+
32+
private function submitValidatorForm(string $googleTotpCode, string $totpCode): Crawler
33+
{
34+
$formPage = $this->navigateToValidatorsForm();
35+
36+
$form = $formPage->selectButton('submit-button')->form();
37+
$form['form[googleTotpCode]'] = $googleTotpCode;
38+
$form['form[totpCode]'] = $totpCode;
39+
40+
return $this->client->submit($form);
41+
}
42+
43+
private function assertValidatedWrongCodes(Crawler $submitPage): void
44+
{
45+
$this->assertResponseStatusCode(422); // Unprocessable Entity
46+
$this->assertStringContainsString(
47+
'The verification code is not valid',
48+
$submitPage->html(),
49+
'The page must show an error message'
50+
);
51+
}
52+
53+
private function assertValidatedCorrectCodes(Crawler $submitPage): void
54+
{
55+
$this->assertResponseStatusCode(200); // Unprocessable Entity
56+
$this->assertStringContainsString(
57+
'All codes valid!',
58+
$submitPage->html(),
59+
'The page must show the success message'
60+
);
61+
}
62+
}

0 commit comments

Comments
 (0)