-
-
Notifications
You must be signed in to change notification settings - Fork 833
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support CSRF protection token of Symfony's form component (#2132)
* Support CSRF protection token of Symfony's form component (implements #2120) * Made unit test of form model describer compatible to php versions prior to PHP 8.0 * Added functional test for csrf token description in form models * Fixed code style in tests * Handle enabled csrf_protection in form describer if form extension not loaded * Added and improved tests for csrf form token description * Fixed StyleCI issues in test controller * update baseline --------- Co-authored-by: DjordyKoert <[email protected]>
- Loading branch information
1 parent
d7f9b80
commit b96c263
Showing
12 changed files
with
362 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<?php | ||
|
||
namespace Nelmio\ApiDocBundle\Tests\Functional; | ||
|
||
use Symfony\Component\HttpKernel\KernelInterface; | ||
|
||
class CsrfProtectionFunctionalTest extends WebTestCase | ||
{ | ||
protected function setUp(): void | ||
{ | ||
parent::setUp(); | ||
static::bootKernel(); | ||
} | ||
|
||
protected static function createKernel(array $options = []): KernelInterface | ||
{ | ||
return new TestKernel(TestKernel::USE_FORM_CSRF); | ||
} | ||
|
||
public function testTokenPropertyExistsPerDefaultIfEnabledPerFrameworkConfig(): void | ||
{ | ||
// Make sure that test precondition is correct. | ||
$isCsrfFormExtensionEnabled = self::getContainer()->getParameter('form.type_extension.csrf.enabled'); | ||
$this->assertTrue($isCsrfFormExtensionEnabled, 'The test needs the csrf form extension to be enabled.'); | ||
|
||
$this->assertEquals([ | ||
'type' => 'object', | ||
'properties' => [ | ||
'quz' => [ | ||
'$ref' => '#/components/schemas/User', | ||
], | ||
'_token' => [ | ||
'description' => 'CSRF token', | ||
'type' => 'string', | ||
], | ||
], | ||
'required' => ['quz', '_token'], | ||
'schema' => 'FormWithModel', | ||
], json_decode($this->getModel('FormWithModel')->toJson(), true)); | ||
} | ||
|
||
public function testTokenPropertyExistsIfCsrfProtectionIsEnabled(): void | ||
{ | ||
$this->assertEquals([ | ||
'type' => 'object', | ||
'properties' => [ | ||
'name' => [ | ||
'type' => 'string', | ||
], | ||
'_token' => [ | ||
'description' => 'CSRF token', | ||
'type' => 'string', | ||
], | ||
], | ||
'required' => ['name', '_token'], | ||
'schema' => 'FormWithCsrfProtectionEnabledType', | ||
], json_decode($this->getModel('FormWithCsrfProtectionEnabledType')->toJson(), true)); | ||
} | ||
|
||
public function testTokenPropertyNotExistsIfCsrfProtectionIsDisabled(): void | ||
{ | ||
$this->assertEquals([ | ||
'type' => 'object', | ||
'properties' => [ | ||
'name' => [ | ||
'type' => 'string', | ||
], | ||
], | ||
'required' => ['name'], | ||
'schema' => 'FormWithCsrfProtectionDisabledType', | ||
], json_decode($this->getModel('FormWithCsrfProtectionDisabledType')->toJson(), true)); | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
Tests/Functional/Form/FormWithCsrfProtectionDisabledType.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?php | ||
|
||
namespace Nelmio\ApiDocBundle\Tests\Functional\Form; | ||
|
||
use Symfony\Component\Form\AbstractType; | ||
use Symfony\Component\Form\Extension\Core\Type\TextType; | ||
use Symfony\Component\Form\FormBuilderInterface; | ||
use Symfony\Component\OptionsResolver\OptionsResolver; | ||
|
||
class FormWithCsrfProtectionDisabledType extends AbstractType | ||
{ | ||
public function buildForm(FormBuilderInterface $builder, array $options): void | ||
{ | ||
$builder->add('name', TextType::class); | ||
} | ||
|
||
public function configureOptions(OptionsResolver $resolver): void | ||
{ | ||
$resolver->setDefault('csrf_protection', false); | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
Tests/Functional/Form/FormWithCsrfProtectionEnabledType.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?php | ||
|
||
namespace Nelmio\ApiDocBundle\Tests\Functional\Form; | ||
|
||
use Symfony\Component\Form\AbstractType; | ||
use Symfony\Component\Form\Extension\Core\Type\TextType; | ||
use Symfony\Component\Form\FormBuilderInterface; | ||
use Symfony\Component\OptionsResolver\OptionsResolver; | ||
|
||
class FormWithCsrfProtectionEnabledType extends AbstractType | ||
{ | ||
public function buildForm(FormBuilderInterface $builder, array $options): void | ||
{ | ||
$builder->add('name', TextType::class); | ||
} | ||
|
||
public function configureOptions(OptionsResolver $resolver): void | ||
{ | ||
$resolver->setDefault('csrf_protection', true); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
<?php | ||
|
||
namespace Nelmio\ApiDocBundle\Tests\ModelDescriber; | ||
|
||
use Doctrine\Common\Annotations\Reader; | ||
use Nelmio\ApiDocBundle\Model\Model; | ||
use Nelmio\ApiDocBundle\Model\ModelRegistry; | ||
use Nelmio\ApiDocBundle\ModelDescriber\FormModelDescriber; | ||
use OpenApi\Annotations\Property; | ||
use OpenApi\Attributes\OpenApi; | ||
use OpenApi\Generator; | ||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\Form\Extension\Core\Type\FormType; | ||
use Symfony\Component\Form\FormConfigInterface; | ||
use Symfony\Component\Form\FormFactoryInterface; | ||
use Symfony\Component\Form\FormInterface; | ||
use Symfony\Component\PropertyInfo\Type; | ||
|
||
class FormModelDescriberTest extends TestCase | ||
{ | ||
/** | ||
* @dataProvider provideCsrfProtectionOptions | ||
*/ | ||
public function testDescribeCreatesTokenPropertyDependingOnOptions(bool $csrfProtectionEnabled, string $tokenName, bool $expectProperty): void | ||
{ | ||
$formConfigMock = $this->createMock(FormConfigInterface::class); | ||
$formConfigMock->expects($this->exactly($csrfProtectionEnabled ? 2 : 1)) | ||
->method('getOption') | ||
->willReturnMap([ | ||
['csrf_protection', false, $csrfProtectionEnabled], | ||
['csrf_field_name', null, $tokenName], | ||
]); | ||
|
||
$formMock = $this->createMock(FormInterface::class); | ||
$formMock->expects($this->exactly($csrfProtectionEnabled ? 2 : 1)) | ||
->method('getConfig') | ||
->willReturn($formConfigMock); | ||
|
||
$formFactoryMock = $this->createMock(FormFactoryInterface::class); | ||
$formFactoryMock->expects($this->once()) | ||
->method('create') | ||
->willReturn($formMock); | ||
|
||
$annotationReader = $this->createMock(Reader::class); | ||
|
||
$api = new OpenApi(); | ||
$model = new Model(new Type(Type::BUILTIN_TYPE_OBJECT, false, FormType::class)); | ||
$schema = $this->initSchema(); | ||
$modelRegistry = new ModelRegistry([], $api); | ||
|
||
$describer = new FormModelDescriber($formFactoryMock, $annotationReader, [], false, true); | ||
$describer->setModelRegistry($modelRegistry); | ||
|
||
$describer->describe($model, $schema); | ||
|
||
if ($expectProperty) { | ||
$filteredProperties = array_filter($schema->properties, function (Property $property) use ($tokenName) { | ||
return $property->property === $tokenName; | ||
}); | ||
|
||
$this->assertCount(1, $filteredProperties); | ||
} else { | ||
$this->assertSame(Generator::UNDEFINED, $schema->properties); | ||
} | ||
} | ||
|
||
public function provideCsrfProtectionOptions(): array | ||
{ | ||
return [ | ||
[true, '_token', true], | ||
[true, '_another_token', true], | ||
[false, '_token', false], | ||
]; | ||
} | ||
|
||
private function initSchema(): \OpenApi\Annotations\Schema | ||
{ | ||
if (PHP_VERSION_ID < 80000) { | ||
return new \OpenApi\Annotations\Schema([]); | ||
} | ||
|
||
return new \OpenApi\Attributes\Schema(); // union types, used in schema attribute require PHP >= 8.0.0 | ||
} | ||
} |
Oops, something went wrong.