Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cd820ec

Browse files
committed
[Validator] Add CidrValidator to allow validation of CIDR notations
1 parent 2d0a08b commit cd820ec

File tree

8 files changed

+565
-0
lines changed

8 files changed

+565
-0
lines changed

src/Symfony/Component/Validator/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
5.4
55
---
66

7+
* Add a `Cidr` constraint to validate Cidr notations
78
* Add a `CssColor` constraint to validate CSS colors
89
* Add support for `ConstraintViolationList::createFromMessage()`
910
* Add error's uid to `Count` and `Length` constraints with "exactly" option enabled
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
16+
17+
/**
18+
* Validates that a value is a valid CIDR notation.
19+
*
20+
* @Annotation
21+
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
22+
*
23+
* @author Sorin Pop <[email protected]>
24+
* @author Calin Bolea <[email protected]>
25+
*/
26+
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
27+
class Cidr extends Constraint
28+
{
29+
public const INVALID_CIDR_ERROR = '5649e53a-5afb-47c5-a360-ffbab3be8567';
30+
31+
protected static $netmaskMaxes = [
32+
Ip::ALL => 128,
33+
Ip::V4 => 32,
34+
Ip::V6 => 128,
35+
];
36+
37+
public $version = Ip::ALL;
38+
39+
public $message = 'This value is not a valid CIDR notation.';
40+
41+
public $netmaskMin = 0;
42+
43+
public $netmaskMax;
44+
45+
public function __construct(
46+
array $options = null,
47+
string $version = null,
48+
int $netmaskMin = null,
49+
int $netmaskMax = null,
50+
string $message = null,
51+
array $groups = null,
52+
$payload = null
53+
) {
54+
$this->version = $version ?? $options['version'] ?? $this->version;
55+
56+
if (!\in_array($this->version, array_keys(self::$netmaskMaxes))) {
57+
throw new ConstraintDefinitionException(sprintf('The option "version" must be one of "%s".', implode('", "', array_keys(self::$netmaskMaxes))));
58+
}
59+
60+
$this->netmaskMin = $netmaskMin ?? $options['netmaskMin'] ?? $this->netmaskMin;
61+
$this->netmaskMax = $netmaskMax ?? $options['netmaskMax'] ?? self::$netmaskMaxes[$this->version];
62+
$this->message = $message ?? $this->message;
63+
64+
unset($options['netmaskMin'], $options['netmaskMax'], $options['version']);
65+
66+
if ($this->netmaskMin < 0 || $this->netmaskMax > self::$netmaskMaxes[$this->version] || $this->netmaskMin > $this->netmaskMax) {
67+
throw new ConstraintDefinitionException(sprintf('The netmask range must be between 0 and %d.', self::$netmaskMaxes[$this->version]));
68+
}
69+
70+
parent::__construct($options, $groups, $payload);
71+
}
72+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
use Symfony\Component\Validator\ConstraintValidator;
16+
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
17+
use Symfony\Component\Validator\Exception\UnexpectedValueException;
18+
19+
class CidrValidator extends ConstraintValidator
20+
{
21+
public function validate($value, Constraint $constraint): void
22+
{
23+
if (!$constraint instanceof Cidr) {
24+
throw new UnexpectedTypeException($constraint, Cidr::class);
25+
}
26+
27+
if (null === $value || '' === $value) {
28+
return;
29+
}
30+
31+
if (!\is_string($value)) {
32+
throw new UnexpectedValueException($value, 'string');
33+
}
34+
35+
$cidrParts = explode('/', $value, 2);
36+
37+
if (!isset($cidrParts[1]) || !ctype_digit($cidrParts[1])) {
38+
$this->context
39+
->buildViolation($constraint->message)
40+
->setCode(Cidr::INVALID_CIDR_ERROR)
41+
->addViolation();
42+
43+
return;
44+
}
45+
46+
if ('' === $cidrParts[0]) {
47+
$this->context
48+
->buildViolation($constraint->message)
49+
->setCode(Cidr::INVALID_CIDR_ERROR)
50+
->addViolation();
51+
52+
return;
53+
}
54+
55+
$ipAddress = $cidrParts[0];
56+
$netmask = (int) $cidrParts[1];
57+
58+
if (Ip::V6 !== $constraint->version
59+
&& filter_var($ipAddress, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)
60+
&& $netmask <= 32
61+
&& $netmask >= $constraint->netmaskMin
62+
&& $netmask <= $constraint->netmaskMax) {
63+
return;
64+
}
65+
66+
if (Ip::V4 !== $constraint->version
67+
&& filter_var($ipAddress, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)
68+
&& $netmask >= $constraint->netmaskMin
69+
&& $netmask <= $constraint->netmaskMax) {
70+
return;
71+
}
72+
73+
$this->context
74+
->buildViolation($constraint->message)
75+
->setCode(Cidr::INVALID_CIDR_ERROR)
76+
->addViolation();
77+
}
78+
}

src/Symfony/Component/Validator/Resources/translations/validators.en.xlf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,10 @@
394394
<source>This value is not a valid CSS color.</source>
395395
<target>This value is not a valid CSS color.</target>
396396
</trans-unit>
397+
<trans-unit id="102">
398+
<source>This value is not a valid CIDR notation.</source>
399+
<target>This value is not a valid CIDR notation.</target>
400+
</trans-unit>
397401
</body>
398402
</file>
399403
</xliff>

src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,10 @@
394394
<source>This value is not a valid CSS color.</source>
395395
<target>Cette valeur n'est pas une couleur CSS valide.</target>
396396
</trans-unit>
397+
<trans-unit id="102">
398+
<source>This value is not a valid CIDR notation.</source>
399+
<target>Cette valeur n'est pas une notation CIDR valide.</target>
400+
</trans-unit>
397401
</body>
398402
</file>
399403
</xliff>

src/Symfony/Component/Validator/Resources/translations/validators.ro.xlf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,14 @@
390390
<source>This value should be a valid expression.</source>
391391
<target>Această valoare ar trebui să fie o expresie validă.</target>
392392
</trans-unit>
393+
<trans-unit id="101">
394+
<source>This value is not a valid CSS color.</source>
395+
<target>Această valoare nu este o culoare CSS validă.</target>
396+
</trans-unit>
397+
<trans-unit id="102">
398+
<source>This value is not a valid CIDR notation.</source>
399+
<target>Această valoare nu este o notație CIDR validă.</target>
400+
</trans-unit>
393401
</body>
394402
</file>
395403
</xliff>
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<?php
2+
3+
namespace Symfony\Component\Validator\Tests\Constraints;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Symfony\Component\Validator\Constraints\Cidr;
7+
use Symfony\Component\Validator\Constraints\Ip;
8+
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
9+
use Symfony\Component\Validator\Mapping\ClassMetadata;
10+
use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader;
11+
12+
class CidrTest extends TestCase
13+
{
14+
public function testForAll()
15+
{
16+
$cidrConstraint = new Cidr();
17+
18+
self::assertEquals(Ip::ALL, $cidrConstraint->version);
19+
self::assertEquals(0, $cidrConstraint->netmaskMin);
20+
self::assertEquals(128, $cidrConstraint->netmaskMax);
21+
}
22+
23+
public function testForV4()
24+
{
25+
$cidrConstraint = new Cidr(['version' => Ip::V4]);
26+
27+
self::assertEquals(Ip::V4, $cidrConstraint->version);
28+
self::assertEquals(0, $cidrConstraint->netmaskMin);
29+
self::assertEquals(32, $cidrConstraint->netmaskMax);
30+
}
31+
32+
public function testForV6()
33+
{
34+
$cidrConstraint = new Cidr(['version' => Ip::V6]);
35+
36+
self::assertEquals(Ip::V6, $cidrConstraint->version);
37+
self::assertEquals(0, $cidrConstraint->netmaskMin);
38+
self::assertEquals(128, $cidrConstraint->netmaskMax);
39+
}
40+
41+
public function testWithInvalidVersion()
42+
{
43+
$availableVersions = [Ip::ALL, Ip::V4, Ip::V6];
44+
45+
self::expectException(ConstraintDefinitionException::class);
46+
self::expectExceptionMessage(sprintf('The option "version" must be one of "%s".', implode('", "', $availableVersions)));
47+
48+
new Cidr(['version' => '8']);
49+
}
50+
51+
/**
52+
* @dataProvider getValidMinMaxValues
53+
*/
54+
public function testWithValidMinMaxValues(string $ipVersion, int $netmaskMin, int $netmaskMax)
55+
{
56+
$cidrConstraint = new Cidr([
57+
'version' => $ipVersion,
58+
'netmaskMin' => $netmaskMin,
59+
'netmaskMax' => $netmaskMax,
60+
]);
61+
62+
self::assertEquals($ipVersion, $cidrConstraint->version);
63+
self::assertEquals($netmaskMin, $cidrConstraint->netmaskMin);
64+
self::assertEquals($netmaskMax, $cidrConstraint->netmaskMax);
65+
}
66+
67+
/**
68+
* @dataProvider getInvalidMinMaxValues
69+
*/
70+
public function testWithInvalidMinMaxValues(string $ipVersion, int $netmaskMin, int $netmaskMax)
71+
{
72+
$expectedMax = Ip::V4 == $ipVersion ? 32 : 128;
73+
74+
self::expectException(ConstraintDefinitionException::class);
75+
self::expectExceptionMessage(sprintf('The netmask range must be between 0 and %d.', $expectedMax));
76+
77+
new Cidr([
78+
'version' => $ipVersion,
79+
'netmaskMin' => $netmaskMin,
80+
'netmaskMax' => $netmaskMax,
81+
]);
82+
}
83+
84+
public function getInvalidMinMaxValues(): array
85+
{
86+
return [
87+
[Ip::ALL, -1, 23],
88+
[Ip::ALL, 23, 130],
89+
[Ip::ALL, 2, -4],
90+
[Ip::ALL, -12, -40],
91+
[Ip::V4, 0, 33],
92+
[Ip::V4, 2, -10],
93+
[Ip::V4, -4, 128],
94+
[Ip::V4, -5, -1],
95+
[Ip::V6, 5, 200],
96+
[Ip::V6, -1, 120],
97+
[Ip::V6, 0, -10],
98+
[Ip::V6, -15, -20],
99+
];
100+
}
101+
102+
public function getValidMinMaxValues(): array
103+
{
104+
return [
105+
[Ip::ALL, 0, 23],
106+
[Ip::ALL, 23, 120],
107+
[Ip::V4, 0, 5],
108+
[Ip::V4, 2, 10],
109+
[Ip::V6, 0, 43],
110+
[Ip::V6, 33, 100],
111+
];
112+
}
113+
114+
/**
115+
* @requires PHP 8
116+
*/
117+
public function testAttributes()
118+
{
119+
$metadata = new ClassMetadata(CidrDummy::class);
120+
$loader = new AnnotationLoader();
121+
self::assertTrue($loader->loadClassMetadata($metadata));
122+
123+
[$aConstraint] = $metadata->properties['a']->getConstraints();
124+
self::assertSame(Ip::ALL, $aConstraint->version);
125+
self::assertSame(0, $aConstraint->netmaskMin);
126+
self::assertSame(128, $aConstraint->netmaskMax);
127+
128+
[$bConstraint] = $metadata->properties['b']->getConstraints();
129+
self::assertSame(Ip::V6, $bConstraint->version);
130+
self::assertSame('myMessage', $bConstraint->message);
131+
self::assertSame(10, $bConstraint->netmaskMin);
132+
self::assertSame(126, $bConstraint->netmaskMax);
133+
self::assertSame(['Default', 'CidrDummy'], $bConstraint->groups);
134+
135+
[$cConstraint] = $metadata->properties['c']->getConstraints();
136+
self::assertSame(['my_group'], $cConstraint->groups);
137+
self::assertSame('some attached data', $cConstraint->payload);
138+
}
139+
}
140+
141+
class CidrDummy
142+
{
143+
#[Cidr]
144+
private $a;
145+
146+
#[Cidr(version: Ip::V6, message: 'myMessage', netmaskMin: 10, netmaskMax: 126)]
147+
private $b;
148+
149+
#[Cidr(groups: ['my_group'], payload: 'some attached data')]
150+
private $c;
151+
}

0 commit comments

Comments
 (0)