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

Skip to content

[Validator] Add additional versions (*_NO_PUBLIC, *_ONLY_PRIV & *_ONLY_RES) in IP address constraint #51777

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 32 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a98f828
Enhancement: Add *_ONLY_PRIV & *_ONLY_RES as possible versions in IP …
Ninos Sep 28, 2023
9551dc3
Updated: Changelog
Ninos Sep 28, 2023
2b1e888
Updated: UPGRADE-6.4.md
Ninos Sep 28, 2023
24c73a3
CS
Ninos Sep 28, 2023
2200cd8
Added: Tests for `Mac` constraint
Ninos Sep 28, 2023
3cb3119
Fixed: Tests for `Mac` constraint
Ninos Sep 28, 2023
99e03ea
Added: OnlyPrivate & OnlyReserved tests
Ninos Sep 28, 2023
c11c261
Enhancement: OnlyPrivate & OnlyReserved tests
Ninos Sep 28, 2023
12f126e
CS
Ninos Sep 28, 2023
e4e6a24
Update src/Symfony/Component/Validator/Constraints/Mac.php
Ninos Oct 5, 2023
0e6cf5e
Update src/Symfony/Component/Validator/Constraints/Ip.php
Ninos Oct 5, 2023
0b7910f
Update src/Symfony/Component/Validator/Constraints/MacValidator.php
Ninos Oct 5, 2023
719be6c
Update src/Symfony/Component/Validator/Constraints/Mac.php
Ninos Oct 5, 2023
a6eb4f2
Update src/Symfony/Component/Validator/Constraints/Mac.php
Ninos Oct 5, 2023
4b4c5e4
Update src/Symfony/Component/Validator/Constraints/Mac.php
Ninos Oct 5, 2023
9cf46fc
Update src/Symfony/Component/Validator/Constraints/Mac.php
Ninos Oct 5, 2023
9775156
CS
Ninos Oct 5, 2023
646fd5f
Removed: Mac constraint
Ninos Oct 6, 2023
26e9f2a
[Validator] Remove `Mac` constraint
Ninos Oct 6, 2023
6e84494
[Validator] Fix IpTest
Ninos Oct 6, 2023
cfff49a
[Validator] Renamed constants to full-word names
Ninos Nov 5, 2023
3f58f6b
[Validator] Renamed constants to full-word names
Ninos Nov 5, 2023
a0419db
[Validator] Possibility to use all `Ip` constraint versions in `Cidr`…
Ninos Nov 6, 2023
ed09494
[Validator] Possibility to use all `Ip` constraint versions in `Cidr`…
Ninos Nov 6, 2023
65b3184
[Validator] Possibility to use all `Ip` constraint versions in `Cidr`…
Ninos Nov 6, 2023
b066087
[Validator] Possibility to use all `Ip` constraint versions in `Cidr`…
Ninos Nov 6, 2023
1991107
[Validator] Fixed tests & added `*_NO_PUBLIC`
Ninos Nov 6, 2023
cbdd81f
[Validator] Fixed tests
Ninos Nov 6, 2023
601809c
[Validator] Added normalizer to `Cidr` + better string support
Ninos Nov 7, 2023
cc930ef
Merge branch '7.1' into constraints-networking
Ninos Nov 20, 2023
0d12319
[Validator] Small fixes
Ninos Nov 20, 2023
b58bb03
[Validator] Small fixes
Ninos Nov 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/Symfony/Component/Validator/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ CHANGELOG
* Remove `ValidatorBuilder::disableAnnotationMapping()`, use `ValidatorBuilder::disableAttributeMapping()` instead
* Remove `AnnotationLoader`, use `AttributeLoader` instead

7.1
---

* Add `*_NO_PUBLIC`, `*_ONLY_PRIVATE` and `*_ONLY_RESERVED` versions to `Ip` constraint
* Possibility to use all `Ip` constraint versions for `Cidr` constraint

6.4
---

Expand Down
42 changes: 36 additions & 6 deletions src/Symfony/Component/Validator/Constraints/Cidr.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\InvalidArgumentException;

/**
* Validates that a value is a valid CIDR notation.
Expand All @@ -32,9 +33,29 @@ class Cidr extends Constraint
];

private const NET_MAXES = [
Ip::ALL => 128,
Ip::V4 => 32,
Ip::V6 => 128,
Ip::ALL => 128,

Ip::V4_NO_PRIVATE => 32,
Ip::V6_NO_PRIVATE => 128,
Ip::ALL_NO_PRIVATE => 128,

Ip::V4_NO_RESERVED => 32,
Ip::V6_NO_RESERVED => 128,
Ip::ALL_NO_RESERVED => 128,

Ip::V4_ONLY_PUBLIC => 32,
Ip::V6_ONLY_PUBLIC => 128,
Ip::ALL_ONLY_PUBLIC => 128,

Ip::V4_ONLY_PRIVATE => 32,
Ip::V6_ONLY_PRIVATE => 128,
Ip::ALL_ONLY_PRIVATE => 128,

Ip::V4_ONLY_RESERVED => 32,
Ip::V6_ONLY_RESERVED => 128,
Ip::ALL_ONLY_RESERVED => 128,
];

public string $version = Ip::ALL;
Expand All @@ -43,29 +64,38 @@ class Cidr extends Constraint
public int $netmaskMin = 0;
public int $netmaskMax;

/** @var callable|null */
public $normalizer;

public function __construct(
array $options = null,
string $version = null,
int $netmaskMin = null,
int $netmaskMax = null,
string $message = null,
array $groups = null,
$payload = null
$payload = null,
callable $normalizer = null
) {
$this->version = $version ?? $options['version'] ?? $this->version;

if (!\array_key_exists($this->version, self::NET_MAXES)) {
throw new ConstraintDefinitionException(sprintf('The option "version" must be one of "%s".', implode('", "', array_keys(self::NET_MAXES))));
throw new ConstraintDefinitionException(sprintf('The option "version" must be one of "%s".', implode('", "', array_keys(static::NET_MAXES))));
}

$this->netmaskMin = $netmaskMin ?? $options['netmaskMin'] ?? $this->netmaskMin;
$this->netmaskMax = $netmaskMax ?? $options['netmaskMax'] ?? self::NET_MAXES[$this->version];
$this->netmaskMax = $netmaskMax ?? $options['netmaskMax'] ?? static::NET_MAXES[$this->version];
$this->message = $message ?? $this->message;
$this->normalizer = $normalizer ?? $this->normalizer;

unset($options['netmaskMin'], $options['netmaskMax'], $options['version']);

if ($this->netmaskMin < 0 || $this->netmaskMax > self::NET_MAXES[$this->version] || $this->netmaskMin > $this->netmaskMax) {
throw new ConstraintDefinitionException(sprintf('The netmask range must be between 0 and %d.', self::NET_MAXES[$this->version]));
if ($this->netmaskMin < 0 || $this->netmaskMax > static::NET_MAXES[$this->version] || $this->netmaskMin > $this->netmaskMax) {
throw new ConstraintDefinitionException(sprintf('The netmask range must be between 0 and %d.', static::NET_MAXES[$this->version]));
}

if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));
}

parent::__construct($options, $groups, $payload);
Expand Down
21 changes: 12 additions & 9 deletions src/Symfony/Component/Validator/Constraints/CidrValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,16 @@ public function validate($value, Constraint $constraint): void
return;
}

if (!\is_string($value)) {
if (!\is_scalar($value) && !$value instanceof \Stringable) {
throw new UnexpectedValueException($value, 'string');
}

$value = (string) $value;

if (null !== $constraint->normalizer) {
$value = ($constraint->normalizer)($value);
}

$cidrParts = explode('/', $value, 2);

if (!isset($cidrParts[1])
Expand All @@ -49,14 +55,7 @@ public function validate($value, Constraint $constraint): void
$ipAddress = $cidrParts[0];
$netmask = (int) $cidrParts[1];

$validV4 = Ip::V6 !== $constraint->version
&& filter_var($ipAddress, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)
&& $netmask <= 32;

$validV6 = Ip::V4 !== $constraint->version
&& filter_var($ipAddress, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6);

if (!$validV4 && !$validV6) {
if (!IpValidator::checkIP($ipAddress, $constraint->version)) {
$this->context
->buildViolation($constraint->message)
->setCode(Cidr::INVALID_CIDR_ERROR)
Expand All @@ -65,6 +64,10 @@ public function validate($value, Constraint $constraint): void
return;
}

if(filter_var($ipAddress, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4) && $constraint->netmaskMax > 32) {
$constraint->netmaskMax = 32;
}

if ($netmask < $constraint->netmaskMin || $netmask > $constraint->netmaskMax) {
$this->context
->buildViolation($constraint->netmaskRangeViolationMessage)
Expand Down
74 changes: 62 additions & 12 deletions src/Symfony/Component/Validator/Constraints/Ip.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*
* @author Bernhard Schussek <[email protected]>
* @author Joseph Bielawski <[email protected]>
* @author Ninos Ego <[email protected]>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Ip extends Constraint
Expand All @@ -28,39 +29,84 @@ class Ip extends Constraint
public const V6 = '6';
public const ALL = 'all';

// adds inverse FILTER_FLAG_NO_RES_RANGE and FILTER_FLAG_NO_PRIV_RANGE flags (skip both)
public const V4_NO_PUBLIC = '4_no_public';
public const V6_NO_PUBLIC = '6_no_public';
public const ALL_NO_PUBLIC = 'all_no_public';

// adds FILTER_FLAG_NO_PRIV_RANGE flag (skip private ranges)
public const V4_NO_PRIV = '4_no_priv';
public const V6_NO_PRIV = '6_no_priv';
public const ALL_NO_PRIV = 'all_no_priv';
public const V4_NO_PRIVATE = '4_no_private';
public const V6_NO_PRIVATE = '6_no_private';
public const ALL_NO_PRIVATE = 'all_no_private';

// adds FILTER_FLAG_NO_RES_RANGE flag (skip reserved ranges)
public const V4_NO_RES = '4_no_res';
public const V6_NO_RES = '6_no_res';
public const ALL_NO_RES = 'all_no_res';
public const V4_NO_RESERVED = '4_no_reserved';
public const V6_NO_RESERVED = '6_no_reserved';
public const ALL_NO_RESERVED = 'all_no_reserved';

// adds FILTER_FLAG_NO_PRIV_RANGE and FILTER_FLAG_NO_RES_RANGE flags (skip both)
public const V4_ONLY_PUBLIC = '4_public';
public const V6_ONLY_PUBLIC = '6_public';
public const ALL_ONLY_PUBLIC = 'all_public';

// adds inverse FILTER_FLAG_NO_PRIV_RANGE
public const V4_ONLY_PRIVATE = '4_private';
public const V6_ONLY_PRIVATE = '6_private';
public const ALL_ONLY_PRIVATE = 'all_private';

// adds inverse FILTER_FLAG_NO_RES_RANGE
public const V4_ONLY_RESERVED = '4_reserved';
public const V6_ONLY_RESERVED = '6_reserved';
public const ALL_ONLY_RESERVED = 'all_reserved';

public const INVALID_IP_ERROR = 'b1b427ae-9f6f-41b0-aa9b-84511fbb3c5b';

// BC: Aliases
public const V4_NO_PRIV = '4_no_priv';
public const V6_NO_PRIV = '6_no_priv';
public const ALL_NO_PRIV = 'all_no_priv';
public const V4_NO_RES = '4_no_res';
public const V6_NO_RES = '6_no_res';
public const ALL_NO_RES = 'all_no_res';

protected const ALIAS_VERSIONS = [
self::V4_NO_PRIV => self::V4_NO_PRIVATE,
self::V6_NO_PRIV => self::V6_NO_PRIVATE,
self::ALL_NO_PRIV => self::ALL_NO_PRIVATE,

self::V4_NO_RES => self::V4_NO_RESERVED,
self::V6_NO_RES => self::V6_NO_RESERVED,
self::ALL_NO_RES => self::ALL_NO_RESERVED,
];

protected const VERSIONS = [
self::V4,
self::V6,
self::ALL,

self::V4_NO_PRIV,
self::V6_NO_PRIV,
self::ALL_NO_PRIV,
self::V4_NO_PUBLIC,
self::V6_NO_PUBLIC,
self::ALL_NO_PUBLIC,

self::V4_NO_PRIVATE,
self::V6_NO_PRIVATE,
self::ALL_NO_PRIVATE,

self::V4_NO_RES,
self::V6_NO_RES,
self::ALL_NO_RES,
self::V4_NO_RESERVED,
self::V6_NO_RESERVED,
self::ALL_NO_RESERVED,

self::V4_ONLY_PUBLIC,
self::V6_ONLY_PUBLIC,
self::ALL_ONLY_PUBLIC,

self::V4_ONLY_PRIVATE,
self::V6_ONLY_PRIVATE,
self::ALL_ONLY_PRIVATE,

self::V4_ONLY_RESERVED,
self::V6_ONLY_RESERVED,
self::ALL_ONLY_RESERVED,
];

protected const ERROR_NAMES = [
Expand All @@ -86,6 +132,10 @@ public function __construct(
$this->message = $message ?? $this->message;
$this->normalizer = $normalizer ?? $this->normalizer;

if (isset(static::ALIAS_VERSIONS[$this->version])) {
$this->version = static::ALIAS_VERSIONS[$this->version];
}

if (!\in_array($this->version, static::VERSIONS, true)) {
throw new ConstraintDefinitionException(sprintf('The option "version" must be one of "%s".', implode('", "', static::VERSIONS)));
}
Expand Down
63 changes: 47 additions & 16 deletions src/Symfony/Component/Validator/Constraints/IpValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,53 @@
*
* @author Bernhard Schussek <[email protected]>
* @author Joseph Bielawski <[email protected]>
* @author Ninos Ego <[email protected]>
*/
class IpValidator extends ConstraintValidator
{
/**
* Checks whether an IP address is valid.
*
* @internal
*/
public static function checkIp(string $ip, mixed $version): bool
{
$flag = match ($version) {
Ip::V4, Ip::V4_NO_PUBLIC, Ip::V4_ONLY_PRIVATE, Ip::V4_ONLY_RESERVED => \FILTER_FLAG_IPV4,
Ip::V6, Ip::V6_NO_PUBLIC, Ip::V6_ONLY_PRIVATE, Ip::V6_ONLY_RESERVED => \FILTER_FLAG_IPV6,
Ip::V4_NO_PRIVATE => \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_PRIV_RANGE,
Ip::V6_NO_PRIVATE => \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_PRIV_RANGE,
Ip::ALL_NO_PRIVATE => \FILTER_FLAG_NO_PRIV_RANGE,
Ip::V4_NO_RESERVED => \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_RES_RANGE,
Ip::V6_NO_RESERVED => \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_RES_RANGE,
Ip::ALL_NO_RESERVED => \FILTER_FLAG_NO_RES_RANGE,
Ip::V4_ONLY_PUBLIC => \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE,
Ip::V6_ONLY_PUBLIC => \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE,
Ip::ALL_ONLY_PUBLIC => \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE,
default => 0,
};

if (!filter_var($ip, \FILTER_VALIDATE_IP, $flag)) {
return false;
}

$inverseFlag = match ($version) {
Ip::V4_NO_PUBLIC, Ip::V6_NO_PUBLIC, Ip::ALL_NO_PUBLIC => \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE,
Ip::V4_ONLY_PRIVATE, Ip::V6_ONLY_PRIVATE, Ip::ALL_ONLY_PRIVATE => \FILTER_FLAG_NO_PRIV_RANGE,
Ip::V4_ONLY_RESERVED, Ip::V6_ONLY_RESERVED, Ip::ALL_ONLY_RESERVED => \FILTER_FLAG_NO_RES_RANGE,
default => 0,
};

if ($inverseFlag && filter_var($ip, \FILTER_VALIDATE_IP, $inverseFlag)) {
return false;
}

return true;
}

/**
* @return void
*/
public function validate(mixed $value, Constraint $constraint): void
{
if (!$constraint instanceof Ip) {
Expand All @@ -44,26 +88,13 @@ public function validate(mixed $value, Constraint $constraint): void
$value = ($constraint->normalizer)($value);
}

$flag = match ($constraint->version) {
Ip::V4 => \FILTER_FLAG_IPV4,
Ip::V6 => \FILTER_FLAG_IPV6,
Ip::V4_NO_PRIV => \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_PRIV_RANGE,
Ip::V6_NO_PRIV => \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_PRIV_RANGE,
Ip::ALL_NO_PRIV => \FILTER_FLAG_NO_PRIV_RANGE,
Ip::V4_NO_RES => \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_RES_RANGE,
Ip::V6_NO_RES => \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_RES_RANGE,
Ip::ALL_NO_RES => \FILTER_FLAG_NO_RES_RANGE,
Ip::V4_ONLY_PUBLIC => \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE,
Ip::V6_ONLY_PUBLIC => \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE,
Ip::ALL_ONLY_PUBLIC => \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE,
default => 0,
};

if (!filter_var($value, \FILTER_VALIDATE_IP, $flag)) {
if(!self::checkIp($value, $constraint->version)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Ip::INVALID_IP_ERROR)
->addViolation();

return;
}
}
}
10 changes: 9 additions & 1 deletion src/Symfony/Component/Validator/Tests/Constraints/CidrTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,15 @@ public function testForV6()

public function testWithInvalidVersion()
{
$availableVersions = [Ip::ALL, Ip::V4, Ip::V6];
$availableVersions = [
Ip::V4, Ip::V6, Ip::ALL,
Ip::V4_NO_PUBLIC, Ip::V6_NO_PUBLIC, Ip::ALL_NO_PUBLIC,
Ip::V4_NO_PRIVATE, Ip::V6_NO_PRIVATE, Ip::ALL_NO_PRIVATE,
Ip::V4_NO_RESERVED, Ip::V6_NO_RESERVED, Ip::ALL_NO_RESERVED,
Ip::V4_ONLY_PUBLIC, Ip::V6_ONLY_PUBLIC, Ip::ALL_ONLY_PUBLIC,
Ip::V4_ONLY_PRIVATE, Ip::V6_ONLY_PRIVATE, Ip::ALL_ONLY_PRIVATE,
Ip::V4_ONLY_RESERVED, Ip::V6_ONLY_RESERVED, Ip::ALL_ONLY_RESERVED
];

self::expectException(ConstraintDefinitionException::class);
self::expectExceptionMessage(sprintf('The option "version" must be one of "%s".', implode('", "', $availableVersions)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,6 @@ public static function getWithInvalidNetmask(): array
return [
['192.168.1.0/-1'],
['0.0.0.0/foobar'],
['10.0.0.0/128'],
['123.45.67.178/aaa'],
['172.16.0.0//'],
['255.255.255.255/1/4'],
Expand All @@ -221,7 +220,6 @@ public static function getWithInvalidMasksAndIps(): array
{
return [
['0.0.0.0/foobar'],
['10.0.0.0/128'],
['123.45.67.178/aaa'],
['172.16.0.0//'],
['172.16.0.0/a/'],
Expand All @@ -240,6 +238,7 @@ public static function getOutOfRangeNetmask(): array
{
return [
['10.0.0.0/24', Ip::V4, 10, 20],
['10.0.0.0/128'],
['2001:0DB8:85A3:0000:0000:8A2E:0370:7334/24', Ip::V6, 10, 20],
];
}
Expand Down
Loading