10000 [Validator][RecursiveContextualValidator] Prevent validated hash coll… · symfony/symfony@6f28e2d · GitHub
[go: up one dir, main page]

Skip to content

Commit 6f28e2d

Browse files
committed
[Validator][RecursiveContextualValidator] Prevent validated hash collisions
1 parent 30d8496 commit 6f28e2d

File tree

2 files changed

+60
-3
lines changed

2 files changed

+60
-3
lines changed

src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,20 @@
1212
namespace Symfony\Component\Validator\Tests\Validator;
1313

1414
use Symfony\Component\Translation\IdentityTranslator;
15+
use Symfony\Component\Validator\Constraint;
1516
use Symfony\Component\Validator\Constraints\All;
1617
use Symfony\Component\Validator\Constraints\Collection;
1718
use Symfony\Component\Validator\Constraints\GroupSequence;
19+
use Symfony\Component\Validator\Constraints\IsFalse;
20+
use Symfony\Component\Validator\Constraints\IsNull;
1821
use Symfony\Component\Validator\Constraints\IsTrue;
1922
use Symfony\Component\Validator\Constraints\Length;
2023
use Symfony\Component\Validator\Constraints\NotBlank;
2124
use Symfony\Component\Validator\Constraints\NotNull;
2225
use Symfony\Component\Validator\Constraints\Optional;
2326
use Symfony\Component\Validator\Constraints\Required;
2427
use Symfony\Component\Validator\Constraints\Valid;
28+
use Symfony\Component\Validator\ConstraintValidator;
2529
use Symfony\Component\Validator\ConstraintValidatorFactory;
2630
use Symfony\Component\Validator\Context\ExecutionContextFactory;
2731
use Symfony\Component\Validator\Mapping\ClassMetadata;
@@ -200,4 +204,47 @@ public function testOptionalConstraintIsIgnored()
200204

201205
$this->assertCount(0, $violations);
202206
}
207+
208+
public function testValidatedConstraintsHashesDontCollide()
209+
{
210+
$metadata = new ClassMetadata(Entity::class);
211+
$metadata->addPropertyConstraint('initialized', new NotNull(['groups' => 'should_pass']));
212+
$metadata->addPropertyConstraint('initialized', new IsNull(['groups' => 'should_fail']));
213+
214+
$this->metadataFactory->addMetadata($metadata);
215+
216+
$entity = new Entity();
217+
$entity->data = new \stdClass();
218+
219+
$this->assertCount(2, $this->validator->validate($entity, new TestConstraintHashesDontCollide()));
220+
}
221+
}
222+
223+
final class TestConstraintHashesDontCollide extends Constraint
224+
{
225+
}
226+
227+
final class TestConstraintHashesDontCollideValidator extends ConstraintValidator
228+
{
229+
/**
230+
* {@inheritdoc}
231+
*/
232+
public function validate($value, Constraint $constraint)
233+
{
234+
if (!$value instanceof Entity) {
235+
throw new \LogicException();
236+
}
237+
238+
$this->context->getValidator()
239+
->inContext($this->context)
240+
->atPath('data')
241+
->validate($value, new NotNull())
242+
->validate($value, new NotNull())
243+
->validate($value, new IsFalse());
244+
245+
$this->context->getValidator()
246+
->inContext($this->context)
247+
->validate($value, null, new GroupSequence(['should_pass']))
248+
->validate($value, null, new GroupSequence(['should_fail']));
249+
}
203250
}

src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
4949
private $validatorFactory;
5050
private $objectInitializers;
5151

52+
private $validatedObjectsReferences = [];
53+
private $validatedConstraintsReferences = [];
54+
private $initializedObjectsReferences = [];
55+
5256
/**
5357
* Creates a validator for the given context.
5458
*
@@ -443,6 +447,10 @@ private function validateClassNode($object, $cacheKey, ClassMetadataInterface $m
443447
{
444448
$context->setNode($object, $object, $metadata, $propertyPath);
445449

450+
if (!isset($this->initializedObjectsReferences[$cacheKey])) {
451+
$this->initializedObjectsReferences[$cacheKey] = $object;
452+
}
453+
446454
if (!$context->isObjectInitialized($cacheKey)) {
447455
foreach ($this->objectInitializers as $initializer) {
448456
$initializer->initialize($object);
@@ -456,9 +464,7 @@ private function validateClassNode($object, $cacheKey, ClassMetadataInterface $m
456464
// to cascade the "Default" group when traversing the group
457465
// sequence
458466
$defaultOverridden = false;
459-
460-
// Use the object hash for group sequences
461-
$groupHash = \is_object($group) ? spl_object_hash($group) : $group;
467+
$groupHash = serialize($group);
462468

463469
if ($context->isGroupValidated($cacheKey, $groupHash)) {
464470
// Skip this group when validating the properties and when
@@ -803,6 +809,10 @@ private function validateInGroup($value, $cacheKey, MetadataInterface $metadata,
803809
// that constraints belong to multiple validated groups
804810
if (null !== $cacheKey) {
805811
$constraintHash = spl_object_hash($constraint);
812+
if (!isset($this->validatedConstraintsReferences[$constraintHash])) {
813+
$this->validatedConstraintsReferences[$constraintHash] = $constraint;
814+
}
815+
806816
// instanceof Valid: In case of using a Valid constraint with many groups
807817
// it makes a reference object get validated by each group
808818
if ($constraint instanceof Composite || $constraint instanceof Valid) {

0 commit comments

Comments
 (0)
0