Skip to content

Commit

Permalink
Support CSRF protection token of Symfony's form component (implements #…
Browse files Browse the repository at this point in the history
  • Loading branch information
stollr committed Dec 22, 2023
1 parent b7a5722 commit d3a4bfc
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 0 deletions.
14 changes: 14 additions & 0 deletions ModelDescriber/FormModelDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,20 @@ private function parseForm(OA\Schema $schema, FormInterface $form)

$this->findFormType($config, $property);
}

if ($form->getConfig()->getOption('csrf_protection', false)) {
$tokenFieldName = $form->getConfig()->getOption('csrf_field_name');

$property = Util::getProperty($schema, $tokenFieldName);
$property->type = 'string';
$property->description = 'CSRF token';

if (Generator::isDefault($schema->required)) {
$schema->required = [];
}

$schema->required[] = $tokenFieldName;
}
}

/**
Expand Down
78 changes: 78 additions & 0 deletions Tests/ModelDescriber/FormModelDescriberTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?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 Nelmio\ApiDocBundle\Tests\ModelDescriber\Fixtures\CsrfEnablingFormType;
use OpenApi\Annotations\Property;
use OpenApi\Attributes\OpenApi;
use OpenApi\Attributes\Schema;
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 = new Schema();
$modelRegistry = new ModelRegistry([], $api);

$describer = new FormModelDescriber($formFactoryMock, $annotationReader, []);
$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],
];
}
}

0 comments on commit d3a4bfc

Please sign in to comment.