Skip to content

fix: strict CIDR IP validation; bump version #295

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

Merged
merged 1 commit into from
Sep 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
78 changes: 56 additions & 22 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,50 @@
# Changelog

<!--

Note to self: Breaking changes must increment either

- minor version: as long as versions are in 0.y.z or
- major version: when versions are in in x.y.z (x>0)

-->

## 0.22.0 (2023-09-02)

_**What's Changed**_

> - _Breaking_:
> - API changes in `validators.ipv4` and `validators.ipv6` functions
> - `strict` parameter now correctly validates IP addresses strictly in CIDR notation
> - `host_bit` parameter distinguishes between network and host IP address

- fix: url validator considers urls with /#/ as valid by @adrienthiery in [#289](https://github.com/python-validators/validators/pull/289)
- Add note about ValidationFailure to ValidationError in changes.md by @tswfi in [#291](https://github.com/python-validators/validators/pull/291)
- fix: simple hostname validation regex by @joe733 in [#294](https://github.com/python-validators/validators/pull/294)
- fix: strict CIDR IP validation; bump version by @joe733 in [#295](https://github.com/python-validators/validators/pull/295)

_**New Contributors**_

- @adrienthiery made their first contribution in [#289](https://github.com/python-validators/validators/pull/289)
- @tswfi made their first contribution in [#291](https://github.com/python-validators/validators/pull/291)

**Full Changelog**: [`0.21.2...0.22.0`](https://github.com/python-validators/validators/compare/0.21.2...0.22.0)

## 0.21.2 (2023-08-07)

### What's Changed
_**What's Changed**_

> - _Breaking_:
> - `ValidationFailure` renamed to `ValidationError` in [`joe733@12ae1f5`](https://github.com/joe733/pyvalidators/commit/12ae1f5850555d11e1f1a2c03f597fd10610215a)

- feat: refactoring; updates; fixes; bump version by @joe733 in [#283](https://github.com/python-validators/validators/pull/283)
- *Breaking Changes*:
- `ValidationFailure` renamed to `ValidationError` in [joe733@12ae1f5](https://github.com/joe733/pyvalidators/commit/12ae1f5850555d11e1f1a2c03f597fd10610215a)
- feat: refactoring; updates; fixes; bump version by @joe733 in [#283](https://github.com/python-validators/validators/pull/283)(<https://github.com/joe733/pyvalidators/commit/12ae1f5850555d11e1f1a2c03f597fd10610215a>)
- build(deps): bump pymdown-extensions from 9.11 to 10.0 by @dependabot in [#273](https://github.com/python-validators/validators/pull/273)
- build(deps): bump requests from 2.28.2 to 2.31.0 by @dependabot in [#275](https://github.com/python-validators/validators/pull/275)
- add validator ETH addresses (ERC20) by @msamsami in [#276](https://github.com/python-validators/validators/pull/276)
- Added Country Code Validation by @aviiciii in [#280](https://github.com/python-validators/validators/pull/280)
- build(deps-dev): bump certifi from 2022.12.7 to 2023.7.22 by @dependabot in [#281](https://github.com/python-validators/validators/pull/281)

### New Contributors
_**New Contributors**_

- @dependabot made their first contribution in [#273](https://github.com/python-validators/validators/pull/273)
- @msamsami made their first contribution in [#276](https://github.com/python-validators/validators/pull/276)
Expand All @@ -33,6 +64,9 @@

## 0.21.0 (2023-03-25)

> - _Breaking_:
> - Couple of API changes, refer [documentation](https://python-validators.github.io/validators/)

- feat: add build for pypi workflow by @joe733 in [#255](https://github.com/python-validators/validators/pull/255)
- feat: @validator now catches `Exception` by @joe733 in [#254](https://github.com/python-validators/validators/pull/254)
- maint: improves `i18n` package by @joe733 in [#252](https://github.com/python-validators/validators/pull/252)
Expand Down Expand Up @@ -125,11 +159,11 @@

## 0.14.0 (2019-08-21)

- Added new validators ``ipv4_cidr``, ``ipv6_cidr`` (#117, pull request courtesy woodruffw)
- Added new validators `ipv4_cidr`, `ipv6_cidr` (#117, pull request courtesy woodruffw)

## 0.13.0 (2019-05-20)

- Added new validator: ``es_doi``, ``es_nif``, ``es_cif``, ``es_nie`` (#121, pull request courtesy kingbuzzman)
- Added new validator: `es_doi`, `es_nif`, `es_cif`, `es_nie` (#121, pull request courtesy kingbuzzman)

## 0.12.6 (2019-05-08)

Expand Down Expand Up @@ -184,7 +218,7 @@

## 0.10.3 (2016-06-13)

- Added ``public`` parameter to url validator (#26, pull request courtesy Iconceicao)
- Added `public` parameter to url validator (#26, pull request courtesy Iconceicao)

## 0.10.2 (2016-06-11)

Expand All @@ -197,48 +231,48 @@

## 0.10.0 (2016-01-09)

- Added support for internationalized domain names in ``domain`` validator
- Added support for internationalized domain names in `domain` validator

## 0.9.0 (2015-10-10)

- Added new validator: ``domain``
- Added new validator: `domain`
- Added flake8 and isort checks in travis config

## 0.8.0 (2015-06-24)

- Added new validator: ``iban``
- Added new validator: `iban`

## 0.7.0 (2014-09-07)

- Fixed errors in code examples.
- Fixed ``TypeError`` when using ``between`` validator with ``datetime`` objects
- Fixed `TypeError` when using `between` validator with `datetime` objects
like in the code example.
- Changed validators to always return ``True`` instead of a truthy object when
- Changed validators to always return `True` instead of a truthy object when
the validation succeeds.
- Fixed ``truthy`` validator to work like it's name suggests. Previously it
worked like ``falsy``.
- Fixed `truthy` validator to work like it's name suggests. Previously it
worked like `falsy`.

## 0.6.0 (2014-06-25)

- Added new validator: ``slug``
- Added new validator: `slug`

## 0.5.0 (2013-10-31)

- Renamed ``finnish_business_id`` to ``fi_business_id``
- Added new validator: ``fi_ssn``
- Renamed `finnish_business_id` to `fi_business_id`
- Added new validator: `fi_ssn`

## 0.4.0 (2013-10-29)

- Added new validator: ``finnish_business_id``
- Added new validator: `finnish_business_id`

## 0.3.0 (2013-10-27)

- ``number_range`` -> ``between``
- `number_range` -> `between`

## 0.2.0 (2013-10-22)

- Various new validators: ``ipv4``, ``ipv6``, ``length``, ``number_range``,
``mac_address``, ``url``, ``uuid``
- Various new validators: `ipv4`, `ipv6`, `length`, `number_range`,
`mac_address`, `url`, `uuid`

## 0.1.0 (2013-10-18)

Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

| Version | Supported |
| ---------- | ------------------ |
| `>=0.21.2` | :white_check_mark: |
| `>=0.22.0` | :white_check_mark: |

## Reporting a Vulnerability

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "validators"
version = "0.21.2"
version = "0.22.0"
description = "Python Data Validation for Humans™"
authors = [{ name = "Konsta Vesterinen", email = "[email protected]" }]
license = { text = "MIT" }
Expand Down
2 changes: 1 addition & 1 deletion src/validators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,4 @@
"validator",
)

__version__ = "0.21.2"
__version__ = "0.22.0"
36 changes: 21 additions & 15 deletions src/validators/ip_address.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@


@validator
def ipv4(value: str, /, *, cidr: bool = True, strict: bool = False):
def ipv4(value: str, /, *, cidr: bool = True, strict: bool = False, host_bit: bool = True):
"""Returns whether a given value is a valid IPv4 address.

From Python version 3.9.5 leading zeros are no longer tolerated
Expand All @@ -36,11 +36,12 @@ def ipv4(value: str, /, *, cidr: bool = True, strict: bool = False):
value:
IP address string to validate.
cidr:
IP address string may contain CIDR annotation
IP address string may contain CIDR notation
strict:
If strict is True and host bits are set in the supplied address.
Otherwise, the host bits are masked out to determine the
appropriate network address. ref [IPv4Network][2].
IP address string is strictly in CIDR notation
host_bit:
If `False` and host bits (along with network bits) _are_ set in the supplied
address, this function raises a validation error. ref [IPv4Network][2].
[2]: https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Network

Returns:
Expand All @@ -58,15 +59,17 @@ def ipv4(value: str, /, *, cidr: bool = True, strict: bool = False):
if not value:
return False
try:
if cidr and value.count("/") == 1:
return IPv4Network(value, strict=strict)
if cidr:
if strict and value.count("/") != 1:
raise ValueError("IPv4 address was expected in CIDR notation")
return IPv4Network(value, strict=not host_bit)
return IPv4Address(value)
except (AddressValueError, NetmaskValueError):
except (ValueError, AddressValueError, NetmaskValueError):
return False


@validator
def ipv6(value: str, /, *, cidr: bool = True, strict: bool = False):
def ipv6(value: str, /, *, cidr: bool = True, strict: bool = False, host_bit: bool = True):
"""Returns if a given value is a valid IPv6 address.

Including IPv4-mapped IPv6 addresses. The initial version of ipv6 validator
Expand All @@ -88,9 +91,10 @@ def ipv6(value: str, /, *, cidr: bool = True, strict: bool = False):
cidr:
IP address string may contain CIDR annotation
strict:
If strict is True and host bits are set in the supplied address.
Otherwise, the host bits are masked out to determine the
appropriate network address. ref [IPv6Network][2].
IP address string is strictly in CIDR notation
host_bit:
If `False` and host bits (along with network bits) _are_ set in the supplied
address, this function raises a validation error. ref [IPv6Network][2].
[2]: https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv6Network

Returns:
Expand All @@ -108,8 +112,10 @@ def ipv6(value: str, /, *, cidr: bool = True, strict: bool = False):
if not value:
return False
try:
if cidr and value.count("/") == 1:
return IPv6Network(value, strict=strict)
if cidr:
if strict and value.count("/") != 1:
raise ValueError("IPv6 address was expected in CIDR notation")
return IPv6Network(value, strict=not host_bit)
return IPv6Address(value)
except (AddressValueError, NetmaskValueError):
except (ValueError, AddressValueError, NetmaskValueError):
return False
88 changes: 68 additions & 20 deletions tests/test_ip_address.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@
("127.0.0.1",),
("123.5.77.88",),
("12.12.12.12",),
# w/ cidr
("127.0.0.1/0",),
("123.5.77.88/8",),
("12.12.12.12/32",),
],
)
def test_returns_true_on_valid_ipv4_address(address: str):
Expand All @@ -25,6 +21,22 @@ def test_returns_true_on_valid_ipv4_address(address: str):
assert not ipv6(address)


@pytest.mark.parametrize(
("address", "cidr", "strict", "host_bit"),
[
("127.0.0.1/0", True, True, True),
("123.5.77.88", True, False, True),
("12.12.12.0/24", True, True, False),
],
)
def test_returns_true_on_valid_ipv4_cidr_address(
address: str, cidr: bool, strict: bool, host_bit: bool
):
"""Test returns true on valid ipv4 CIDR address."""
assert ipv4(address, cidr=cidr, strict=strict, host_bit=host_bit)
assert not ipv6(address, cidr=cidr, strict=strict, host_bit=host_bit)


@pytest.mark.parametrize(
("address",),
[
Expand All @@ -33,17 +45,29 @@ def test_returns_true_on_valid_ipv4_address(address: str):
("900.200.100.75",),
("0127.0.0.1",),
("abc.0.0.1",),
# w/ cidr
("1.1.1.1/-1",),
("1.1.1.1/33",),
("1.1.1.1/foo",),
],
)
def test_returns_failed_validation_on_invalid_ipv4_address(address: str):
"""Test returns failed validation on invalid ipv4 address."""
assert isinstance(ipv4(address), ValidationError)


@pytest.mark.parametrize(
("address", "cidr", "strict", "host_bit"),
[
("1.1.1.1/1", False, True, True),
("1.1.1.1/33", True, False, True),
("1.1.1.1/24", True, True, False),
("1.1.1.1/-1", True, True, True),
],
)
def test_returns_failed_validation_on_invalid_ipv4_cidr_address(
address: str, cidr: bool, strict: bool, host_bit: bool
):
"""Test returns failed validation on invalid ipv4 CIDR address."""
assert isinstance(ipv4(address, cidr=cidr, strict=strict, host_bit=host_bit), ValidationError)


@pytest.mark.parametrize(
("address",),
[
Expand All @@ -56,14 +80,6 @@ def test_returns_failed_validation_on_invalid_ipv4_address(address: str):
("::192.168.30.2",),
("0000:0000:0000:0000:0000::",),
("0:a:b:c:d:e:f::",),
# w/ cidr
("::1/128",),
("::1/0",),
("dead:beef:0:0:0:0:42:1/8",),
("abcd:ef::42:1/32",),
("0:0:0:0:0:ffff:1.2.3.4/16",),
("2001:0db8:85a3:0000:0000:8a2e:0370:7334/64",),
("::192.168.30.2/128",),
],
)
def test_returns_true_on_valid_ipv6_address(address: str):
Expand All @@ -72,6 +88,26 @@ def test_returns_true_on_valid_ipv6_address(address: str):
assert not ipv4(address)


@pytest.mark.parametrize(
("address", "cidr", "strict", "host_bit"),
[
("::1/128", True, True, True),
("::1/0", True, True, True),
("dead:beef:0:0:0:0:42:1/8", True, True, True),
("abcd:ef::42:1/32", True, True, True),
("0:0:0:0:0:ffff:1.2.3.4/16", True, True, True),
("2001:0db8:85a3:0000:0000:8a2e:0370:7334/64", True, True, True),
("::192.168.30.2/128", True, True, True),
],
)
def test_returns_true_on_valid_ipv6_cidr_address(
address: str, cidr: bool, strict: bool, host_bit: bool
):
"""Test returns true on valid ipv6 CIDR address."""
assert ipv6(address, cidr=cidr, strict=strict, host_bit=host_bit)
assert not ipv4(address, cidr=cidr, strict=strict, host_bit=host_bit)


@pytest.mark.parametrize(
("address",),
[
Expand All @@ -91,12 +127,24 @@ def test_returns_true_on_valid_ipv6_address(address: str):
("::1:2::",),
("8::1:2::9",),
("02001:0000:1234:0000:0000:C1C0:ABCD:0876",),
# w/ cidr
("::1/129",),
("::1/-1",),
("::1/foo",),
],
)
def test_returns_failed_validation_on_invalid_ipv6_address(address: str):
"""Test returns failed validation on invalid ipv6 address."""
assert isinstance(ipv6(address), ValidationError)


@pytest.mark.parametrize(
("address", "cidr", "strict", "host_bit"),
[
("::1/128", False, True, True),
("::1/129", True, False, True),
("dead:beef:0:0:0:0:42:1/8", True, True, False),
("::1/-130", True, True, True),
],
)
def test_returns_failed_validation_on_invalid_ipv6_cidr_address(
address: str, cidr: bool, strict: bool, host_bit: bool
):
"""Test returns failed validation on invalid ipv6 CIDR address."""
assert isinstance(ipv6(address, cidr=cidr, strict=strict, host_bit=host_bit), ValidationError)