8000 [Validator][RecursiveContextualValidator] Prevent validated hash coll… · symfony/symfony@146634d · GitHub
[go: up one dir, main page]

Skip to content

Commit 146634d

Browse files
committed
[Validator][RecursiveContextualValidator] Prevent validated hash collisions
1 parent 17bbaa5 commit 146634d

File tree

2 files changed

+72
-1
lines changed

2 files changed

+72
-1
lines changed

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@
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;
1820
use Symfony\Component\Validator\Constraints\IsTrue;
1921
use Symfony\Component\Validator\Constraints\Length;
2022
use Symfony\Component\Validator\Constraints\NotBlank;
2123
use Symfony\Component\Validator\Constraints\NotNull;
24+
use Symfony\Component\Validator\ConstraintValidator;
2225
use Symfony\Component\Validator\ConstraintValidatorFactory;
2326
use Symfony\Component\Validator\Context\ExecutionContextFactory;
2427
use Symfony\Component\Validator\Mapping\ClassMetadata;
@@ -157,4 +160,36 @@ public function testAllConstraintValidateAllGroupsForNestedConstraints()
157160
$this->assertInstanceOf(NotBlank::class, $violations->get(0)->getConstraint());
158161
$this->assertInstanceOf(Length::class, $violations->get(1)->getConstraint());
159162
}
163+
164+
public function testValidatedConstraintsHashesDontCollide()
165+
{
166+
$entity = new Entity();
167+
$entity->data = new \stdClass();
168+
169+
$this->assertSame(1, $this->validator->validate($entity, new TestConstraintHashesDontCollide())->count());
170+
}
171+
}
172+
173+
final class TestConstraintHashesDontCollide extends Constraint
174+
{
175+
}
176+
177+
final class TestConstraintHashesDontCollideValidator extends ConstraintValidator
178+
{
179+
/**
180+
* {@inheritdoc}
181+
*/
182+
public function validate($value, Constraint $constraint)
183+
{
184+
if (!$value instanceof Entity) {
185+
throw new \LogicException();
186+
}
187+
188+
$this->context->getValidator()
189+
->inContext($this->context)
190+
->atPath('data')
191+
->validate($value, new NotNull())
192+
->validate($value, new NotNull())
193+
->validate($value, new IsFalse());
194+
}
160195
}

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

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
4848
private $validatorFactory;
4949
private $objectInitializers;
5050

51+
private $validatedObjectsReferences = [];
52+
private $validatedConstraintsReferences = [];
53+
private $initializedObjectsReferences = [];
54+
5155
/**
5256
* Creates a validator for the given context.
5357
*
@@ -128,6 +132,8 @@ public function validate($value, $constraints = null, $groups = null)
128132
$this->context->setConstraint($previousConstraint);
129133
}
130134

135+
$this->clearReferences();
136+
131137
return $this;
132138
}
133139

@@ -145,6 +151,8 @@ public function validate($value, $constraints = null, $groups = null)
145151
$this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
146152
$this->context->setGroup($previousGroup);
147153

154+
$this->clearReferences();
155+
148156
return $this;
149157
}
150158

@@ -161,6 +169,8 @@ public function validate($value, $constraints = null, $groups = null)
161169
$this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
162170
$this->context->setGroup($previousGroup);
163171

172+
$this->clearReferences();
173+
164174
return $this;
165175
}
166176

@@ -208,6 +218,8 @@ public function validateProperty($object, $propertyName, $groups = null)
208218
$this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
209219
$this->context->setGroup($previousGroup);
210220

221+
$this->clearReferences();
222+
211223
return $this;
212224
}
213225

@@ -261,6 +273,8 @@ public function validatePropertyValue($objectOrClass, $propertyName, $value, $gr
261273
$this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
262274
$this->context->setGroup($previousGroup);
263275

276+
$this->clearReferences();
277+
264278
return $this;
265279
}
266280

@@ -442,6 +456,10 @@ private function validateClassNode($object, $cacheKey, ClassMetadataInterface $m
442456
{
443457
$context->setNode($object, $object, $metadata, $propertyPath);
444458

459+
if (!isset($this->initializedObjectsReferences[$cacheKey])) {
460+
$this->initializedObjectsReferences[$cacheKey] = $object;
461+
}
462+
445463
if (!$context->isObjectInitialized($cacheKey)) {
446464
foreach ($this->objectInitializers as $initializer) {
447465
$initializer->initialize($object);
@@ -457,7 +475,15 @@ private function validateClassNode($object, $cacheKey, ClassMetadataInterface $m
457475
$defaultOverridden = false;
458476

459477
// Use the object hash for group sequences
460-
$groupHash = \is_object($group) ? spl_object_hash($group) : $group;
478+
if (\is_object($group)) {
479+
$groupHash = spl_object_hash($group);
480+
481+
if (!isset($this->validatedObjectsReferences[$groupHash])) {
482+
$this->validatedObjectsReferences[$groupHash] = $group;
483+
}
484+
} else {
485+
$groupHash = $group;
486+
}
461487

462488
if ($context->isGroupValidated($cacheKey, $groupHash)) {
463489
// Skip this group when validating the properties and when
@@ -790,6 +816,10 @@ private function validateInGroup($value, $cacheKey, MetadataInterface $metadata,
790816
// that constraints belong to multiple validated groups
791817
if (null !== $cacheKey) {
792818
$constraintHash = spl_object_hash($constraint);
819+
if (!isset($this->validatedConstraintsReferences[$constraintHash])) {
820+
$this->validatedConstraintsReferences[$constraintHash] = $constraint;
821+
}
822+
793823
// instanceof Valid: In case of using a Valid constraint with many groups
794824
// it makes a reference object get validated by each group
795825
if ($constraint instanceof Composite || $constraint instanceof Valid) {
@@ -815,4 +845,10 @@ private function validateInGroup($value, $cacheKey, MetadataInterface $metadata,
815845
$validator->validate($value, $constraint);
816846
}
817847
}
848+
849+
private function clearReferences()
850+
{
851+
$this->validatedObjectsReferences = [];
852+
$this->validatedConstraintsReferences = [];
853+
}
818854
}

0 commit comments

Comments
 (0)
0