mirror of
https://github.com/peeringdb/peeringdb.git
synced 2024-05-11 05:55:09 +00:00
* fix issue where ix-f import would raise suggestions ipaddresses not that ixlan (#764) * IX-F Suggestions: Leaving the editor and returning to it via back button issues (#765) * IX-F importer: Clicking "Preview" (IXP Update Tools) on /net/ page resulted in 170 ticket resolutions (#769) More robust testing * black formatting (was lost after pyupgrade) * Add regex searching to deskpro ticket subjects * Change operational error * IX-F suggestions: consolidate delete+add (#770) * Add reset functions to commandline tool * Fix commandline tool bugs * Fix reset commandline tool bugs * add commandline tool * Ixlan needs to be set for import commandline tool * Add email model * Add admin view to emails * Allow network and ix to be null * save emails as part of ixf import * Add email model * Add email delete * add iregex search and better comments * fix ixlan selection for import * redefine migration dependencies for this branch * only enable search w start and end char * Add caption to regex search * Remove delete all ixfmemberdata option * [beta] IX-F importer: don't bother about missing IPv{4,6} address when network is not doing IPv{4,6} (#771) * Add cmdline tests * Resolve email conflicts * Add cmd tool reset tests * add autocomplete to commandline tool * Fix email bugs * Fix email migrations * Fix typos * [beta] IX-F importer: prevent Admin Committee overload by initially limiting importer to IXes enabled by AC (#772) * Finalize regex search for emails and deskprotickets * Fix keyword bug * fix typo * protocol-conflict will now be handled in the notification consolidation 771 changes where if the network indicates neither ipv4 nor ipv6 support, it is handled as supporting both (eg the network didnt configure these at all) realised that the importer command re instantiates the `Importer` class for each ixlan it processes, so moved the sending of consolidated notifications (#772) out of the `update` function and into the command itself after its done processing all the ixlans. This means for tests you will need to call `importer.notify_proposals` after `importer.update` to test the consolidated notifications. fixed several MultipleObjectsReturned errors when network switch protocol support in between imports * should be checking for "ix" in the form data (#773) * Fix cmd ixf tests * fix issue in log_peer * Add commit check for reset tool * fix importer bugs * remove dupe IXFImportEmail definition * ixfimportemail support ix__name and net__name searching * ticket resolution responses * Add commit to command ixf import changes * fix modify entry header * remove whitespace in notification about remote data changes * Begin updating tests * ixf-import command line tool to queue * refactor conflict inserts * Update import protocol tests, including tests for 770 * More test edits * Change cmd tests * better ixfmemberdata error handling and fix some test data * dont reset the same ixfmemberdata requirement * fix many bugs add many tests * remove debug message * fix bug during import when consolidating delete+add * fix perfomance issue in IXFMemberData listing * dont show reset flags on prod env * Add regex search tests * Add 772 tests * remove debug output * fix `test_resolve_deskpro_ticket` test * black formatting * remove dupe import * fix issue with unique constraint error handling * add test for ixp / network ip protocol notification * add missing test data Co-authored-by: Stefan Pratter <stefan@20c.com> Co-authored-by: Elliot Frank <elliot@20c.com>
255 lines
7.0 KiB
Python
255 lines
7.0 KiB
Python
"""
|
|
peeringdb model / field validators
|
|
"""
|
|
import re
|
|
import ipaddress
|
|
import phonenumbers
|
|
|
|
from django.conf import settings
|
|
from django.core.exceptions import ValidationError
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
from peeringdb_server.inet import network_is_pdb_valid, IRR_SOURCE
|
|
import peeringdb_server.models
|
|
|
|
|
|
def validate_phonenumber(phonenumber, country=None):
|
|
"""
|
|
Validate a phonenumber to E.164
|
|
|
|
Arguments:
|
|
- phonenumber (str)
|
|
|
|
Raises:
|
|
- ValidationError if phone number isn't valid E.164 and cannot
|
|
be made E.164 valid
|
|
|
|
Returns:
|
|
- str: validated phonenumber
|
|
"""
|
|
|
|
if not phonenumber:
|
|
return ""
|
|
|
|
try:
|
|
parsed_number = phonenumbers.parse(phonenumber, country)
|
|
validated_number = phonenumbers.format_number(
|
|
parsed_number, phonenumbers.PhoneNumberFormat.E164
|
|
)
|
|
return f"{validated_number}"
|
|
except phonenumbers.phonenumberutil.NumberParseException as exc:
|
|
raise ValidationError(_("Not a valid phone number (E.164)"))
|
|
|
|
|
|
def validate_prefix(prefix):
|
|
"""
|
|
validate ip prefix
|
|
|
|
Arguments:
|
|
- prefix: ipaddress.IPv4Network or an ipaddress.IPv6Network
|
|
|
|
Raises:
|
|
- ValidationError on failed validation
|
|
|
|
Returns:
|
|
- ipaddress.ip_network instance
|
|
"""
|
|
|
|
if isinstance(prefix, str):
|
|
try:
|
|
prefix = ipaddress.ip_network(prefix)
|
|
except ValueError as exc:
|
|
raise ValidationError(_("Invalid prefix: {}").format(prefix))
|
|
return prefix
|
|
|
|
|
|
def validate_address_space(prefix):
|
|
"""
|
|
validate an ip prefix according to peeringdb specs
|
|
|
|
Arguments:
|
|
- prefix: ipaddress.IPv4Network or an ipaddress.IPv6Network
|
|
|
|
Raises:
|
|
- ValidationError on failed validation
|
|
"""
|
|
|
|
prefix = validate_prefix(prefix)
|
|
|
|
if not network_is_pdb_valid(prefix):
|
|
raise ValidationError(_("Address space invalid: {}").format(prefix))
|
|
|
|
prefixlen_min = getattr(settings, f"DATA_QUALITY_MIN_PREFIXLEN_V{prefix.version}")
|
|
prefixlen_max = getattr(settings, f"DATA_QUALITY_MAX_PREFIXLEN_V{prefix.version}")
|
|
|
|
if prefix.prefixlen < prefixlen_min:
|
|
raise ValidationError(
|
|
_("Maximum allowed prefix length is {}").format(prefixlen_min)
|
|
)
|
|
elif prefix.prefixlen > prefixlen_max:
|
|
raise ValidationError(
|
|
_("Minimum allowed prefix length is {}").format(prefixlen_max)
|
|
)
|
|
|
|
|
|
def validate_info_prefixes4(value):
|
|
if not value:
|
|
value = 0
|
|
if value > settings.DATA_QUALITY_MAX_PREFIX_V4_LIMIT:
|
|
raise ValidationError(
|
|
_("Maximum value allowed {}").format(
|
|
settings.DATA_QUALITY_MAX_PREFIX_V4_LIMIT
|
|
)
|
|
)
|
|
if value < 0:
|
|
raise ValidationError(_("Negative value not allowed"))
|
|
return value
|
|
|
|
|
|
def validate_info_prefixes6(value):
|
|
if not value:
|
|
value = 0
|
|
if value > settings.DATA_QUALITY_MAX_PREFIX_V6_LIMIT:
|
|
raise ValidationError(
|
|
_("Maximum value allowed {}").format(
|
|
settings.DATA_QUALITY_MAX_PREFIX_V6_LIMIT
|
|
)
|
|
)
|
|
|
|
if value < 0:
|
|
raise ValidationError(_("Negative value not allowed"))
|
|
return value
|
|
|
|
|
|
def validate_prefix_overlap(prefix):
|
|
"""
|
|
validate that a prefix does not overlap with another prefix
|
|
on an already existing ixlan
|
|
|
|
Arguments:
|
|
- prefix: ipaddress.IPv4Network or an ipaddress.IPv6Network
|
|
|
|
Raises:
|
|
- ValidationError on failed validation
|
|
"""
|
|
|
|
prefix = validate_prefix(prefix)
|
|
qs = peeringdb_server.models.IXLanPrefix.objects.filter(
|
|
protocol=f"IPv{prefix.version}", status="ok"
|
|
)
|
|
qs = qs.exclude(prefix=prefix)
|
|
for ixpfx in qs:
|
|
if ixpfx.prefix.overlaps(prefix):
|
|
raise ValidationError(
|
|
_(
|
|
"Prefix overlaps with {}'s prefix: {}".format(
|
|
ixpfx.ixlan.ix.name, ixpfx.prefix
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
def validate_irr_as_set(value):
|
|
"""
|
|
Validates irr as-set string
|
|
|
|
- the as-set/rs-set name has to conform to RFC 2622 (5.1 and 5.2)
|
|
- the source may be specified by AS-SET@SOURCE or SOURCE::AS-SET
|
|
- multiple values must be separated by either comma, space or comma followed by space
|
|
|
|
Arguments:
|
|
|
|
- value: irr as-set string
|
|
|
|
Returns:
|
|
|
|
- str: validated irr as-set string
|
|
|
|
"""
|
|
|
|
if not isinstance(value, str):
|
|
raise ValueError(_("IRR AS-SET value must be string type"))
|
|
|
|
# split multiple values
|
|
|
|
# normalize value separation to commas
|
|
value = value.replace(", ", ",")
|
|
value = value.replace(" ", ",")
|
|
|
|
validated = []
|
|
|
|
# validate
|
|
for item in value.split(","):
|
|
item = item.upper()
|
|
source = None
|
|
as_set = None
|
|
|
|
# <name>@<source>
|
|
parts_match = re.match(r"^([\w\d\-:]+)@(\w+)$", item)
|
|
if parts_match:
|
|
source = parts_match.group(2)
|
|
as_set = parts_match.group(1)
|
|
|
|
# <source>::<name>
|
|
else:
|
|
parts_match = re.match(r"^(\w+)::([\w\d\-:]+)$", item)
|
|
if parts_match:
|
|
source = parts_match.group(1)
|
|
as_set = parts_match.group(2)
|
|
else:
|
|
sourceless_match = re.match(r"^([\w\d\-:]+)$", item)
|
|
if not sourceless_match:
|
|
raise ValidationError(
|
|
_(
|
|
"Invalid formatting: {} - should be AS-SET, ASx, AS-SET@SOURCE or SOURCE::AS-SET"
|
|
).format(item)
|
|
)
|
|
as_set = sourceless_match.group(1)
|
|
|
|
if source and source not in IRR_SOURCE:
|
|
raise ValidationError(_("Unknown IRR source: {}").format(source))
|
|
|
|
# validate set name and as hierarchy
|
|
as_parts = as_set.split(":")
|
|
|
|
if len(as_parts) > settings.DATA_QUALITY_MAX_IRR_DEPTH:
|
|
raise ValidationError(
|
|
_("Maximum AS-SET hierarchy depth: {}").format(
|
|
settings.DATA_QUALITY_MAX_IRR_DEPTH
|
|
)
|
|
)
|
|
|
|
set_found = False
|
|
typ = None
|
|
types = []
|
|
|
|
for part in as_parts:
|
|
match_set = re.match(r"^(AS|RS)-[\w\d\-]+$", part)
|
|
match_as = re.match(r"^(AS)[\d]+$", part)
|
|
|
|
# set name found
|
|
|
|
if match_set:
|
|
set_found = True
|
|
types.append(match_set.group(1))
|
|
elif not match_as:
|
|
raise ValidationError(
|
|
_(
|
|
"Invalid formatting: {} - should be RS-SET, AS-SET or AS123"
|
|
).format(part)
|
|
)
|
|
|
|
if len(list(set(types))) > 1:
|
|
raise ValidationError(
|
|
_("All parts of an hierarchical name have to be of the same type")
|
|
)
|
|
|
|
if not set_found and len(as_parts) > 1:
|
|
raise ValidationError(
|
|
_("At least one component must be an actual set name")
|
|
)
|
|
|
|
validated.append(item)
|
|
|
|
return " ".join(validated)
|