dnmgmt/whoisdb/forms.py

352 lines
12 KiB
Python
Raw Normal View History

2017-02-23 22:28:06 +01:00
from django import forms
from django.db.models import Case, When, IntegerField
2017-02-23 22:28:06 +01:00
from .models import Maintainer, Contact, InetNum, ASBlock, ASNumber
2017-03-22 04:06:24 +01:00
from .validators import HandleValidatorWithSuffix, IP46CIDRValidator
2017-03-26 05:23:00 +02:00
from .formfields import MultiTextInput
2017-02-28 19:44:15 +01:00
2017-03-01 03:21:03 +01:00
import ipaddress
2017-03-14 20:18:57 +01:00
class WhoisObjectFormMixin(object):
2017-02-28 19:44:15 +01:00
def __init__(self, user, *args, **kwargs):
2017-03-14 20:18:57 +01:00
super(WhoisObjectFormMixin, self).__init__(*args, **kwargs)
2017-02-28 19:44:15 +01:00
self._user = user
instance = getattr(self, 'instance', None)
if instance and instance.pk:
2017-03-21 02:36:07 +01:00
self._create = False
2017-03-22 04:06:24 +01:00
#self.fields['handle'].disabled = True
2017-03-21 02:36:07 +01:00
else:
self._create = True
2017-02-28 19:44:15 +01:00
2017-03-22 04:06:24 +01:00
self.fields['handle'].help_text = "Handle for this object in uppercase with a suffix of -%s" % instance.handleSuffix
# only show users contacts and already present contacts
2017-03-21 02:36:07 +01:00
mnts = self._user.maintainer_set.all()
if 'admin_c' in self.fields:
2017-03-21 02:36:07 +01:00
self.fields['admin_c'].queryset = Contact.getMntQueryset(mnts, self.instance, "admin_c")
2017-02-28 19:44:15 +01:00
def clean_handle(self):
2017-03-22 04:06:24 +01:00
HandleValidatorWithSuffix(self.instance.handleSuffix)(self.cleaned_data['handle'])
return self.cleaned_data['handle']
2017-02-28 19:44:15 +01:00
def clean(self):
2017-03-14 20:18:57 +01:00
cleaned_data = super(WhoisObjectFormMixin, self).clean()
2017-03-01 03:21:03 +01:00
if cleaned_data.get("handle") == "AUTO" and not self.errors:
2017-03-22 04:06:24 +01:00
name = cleaned_data.get("name")
if name is None:
name = self._user.username
cleaned_data['handle'] = self._meta.model.genGenericHandle(name)
2017-02-23 22:28:06 +01:00
2017-03-01 03:21:03 +01:00
return cleaned_data
class MntFormMixin(object):
protectedFields = []
2017-03-22 04:06:24 +01:00
def __init__(self, lower=False, *args, **kwargs):
super(MntFormMixin, self).__init__(*args, **kwargs)
self._editLower = lower
if self._editLower:
for key in self.protectedFields:
self.fields[key].disabled = True
instance = getattr(self, "instance", None)
if not hasattr(self, "_create"):
self._create = not (instance and instance.pk)
mntWhens = [When(auth=self._user, then=2)]
if not self._create:
mntWhens.append(When(handle__in=instance.mnt_by.all().values_list("handle"), then=1))
mntQs = Maintainer.objects.annotate(card=Case(*mntWhens, default=0, output_field=IntegerField())).order_by("-card")
# NOTE: We cannot use distinct on a field as some db backends don't support it
#self.fields["mnt_by"].queryset = mntQs.distinct("handle")
self.fields["mnt_by"].queryset = mntQs
2017-03-21 02:36:07 +01:00
if "mnt_lower" in self.fields:
self.fields["mnt_lower"].queryset = mntQs
2017-03-22 04:06:24 +01:00
def clean(self):
cleaned_data = super(MntFormMixin, self).clean()
if not self.errors and self._create:
mnts = self._user.maintainer_set.all()
for mnt in cleaned_data['mnt_by']:
if mnt in mnts:
break
else:
raise forms.ValidationError("On object creation at least one maintainer needs to be under your control")
2017-03-21 02:36:07 +01:00
class MntForm(WhoisObjectFormMixin, forms.ModelForm):
2017-02-28 19:44:15 +01:00
class Meta:
model = Maintainer
2017-03-26 05:23:00 +02:00
fields = ['handle', 'description', 'admin_c', 'auth']
widgets = {'auth': MultiTextInput()}
help_texts = {
'auth': 'Enter names of users which can edit this object (space separated; '
'and yes, validation is somewhat broken (values disappear on error - just reload))'
}
def __init__(self, *args, **kwargs):
super(MntForm, self).__init__(*args, **kwargs)
print(args, kwargs)
#if self._create:
# self.fields['auth'].text("noot")
2017-02-28 19:44:15 +01:00
2017-03-01 03:21:03 +01:00
2017-02-28 19:44:15 +01:00
class MntInitialForm(MntForm):
2017-02-23 22:28:06 +01:00
class Meta:
model = Maintainer
fields = ['handle', 'description']
2017-03-01 03:21:03 +01:00
2017-03-14 20:18:57 +01:00
class ContactForm(WhoisObjectFormMixin, forms.ModelForm):
2017-02-23 22:28:06 +01:00
class Meta:
model = Contact
2017-03-14 20:18:57 +01:00
fields = ['handle', 'name', 'mnt_by']
2017-02-23 22:28:06 +01:00
2017-02-28 19:44:15 +01:00
def __init__(self, *args, **kwargs):
2017-02-23 22:28:06 +01:00
super(ContactForm, self).__init__(*args, **kwargs)
2017-02-28 19:44:15 +01:00
2017-03-22 04:06:24 +01:00
if "mnt_by" in self.fields:
self.fields['mnt_by'].queryset = Maintainer.objects.filter(auth=self._user).distinct()
2017-03-14 20:18:57 +01:00
2017-03-01 03:21:03 +01:00
2017-02-28 19:44:15 +01:00
class ContactInitialForm(ContactForm):
class Meta:
model = Contact
fields = ['handle', 'name']
2017-03-01 03:21:03 +01:00
2017-03-14 20:18:57 +01:00
class InetNumForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
2017-03-01 03:21:03 +01:00
prefix = forms.CharField()
2017-03-22 04:06:24 +01:00
protectedFields = ['protocol', 'parent_range', 'mnt_by', 'prefix']
2017-03-01 03:21:03 +01:00
2017-02-28 19:44:15 +01:00
class Meta:
model = InetNum
2017-03-27 00:33:29 +02:00
fields = ['handle', 'protocol', 'parent_range', 'prefix', 'name', 'description', 'origin_as', 'mnt_by', 'mnt_lower', 'admin_c']
2017-02-28 19:44:15 +01:00
def __init__(self, *args, **kwargs):
2017-03-01 03:21:03 +01:00
super(InetNumForm, self).__init__(*args, **kwargs)
2017-02-28 19:44:15 +01:00
if self._editLower:
2017-03-01 03:21:03 +01:00
for key in self.protectedFields:
self.fields[key].disabled = True
2017-03-21 02:36:07 +01:00
mnts = self._user.maintainer_set.all()
#self.fields['parent_range'].queryset = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts))
#if not self._create:
# self.fields['parent_range'].queryset |= InetNum.objects.filter(pk=self.instance.pk)
#self.fields['parent_range'].queryset = self.fields['parent_range'].queryset.distinct()
self.fields['parent_range'].queryset = InetNum.getMntQueryset(mnts, self.instance, "parent_range")
2017-03-27 00:33:29 +02:00
self.fields['origin_as'].queryset = ASNumber.getMntQueryset(mnts, self.instance, "origin_as")
2017-03-01 03:21:03 +01:00
def clean_prefix(self):
# make sure this is a subnet we're getting
2017-03-22 04:06:24 +01:00
net = self.cleaned_data['prefix'].lower()
IP46CIDRValidator(net)
2017-03-01 03:21:03 +01:00
try:
net = ipaddress.ip_network(net)
except ValueError as e:
raise forms.ValidationError(str(e))
return net
2017-02-28 19:44:15 +01:00
2017-03-14 20:18:57 +01:00
def clean_parent_range(self):
parent_range = self.cleaned_data.get('parent_range', None)
2017-03-22 04:06:24 +01:00
2017-03-27 00:33:29 +02:00
if parent_range.origin_as.count() > 0:
raise forms.ValidationError("Parent range has origin as set")
2017-03-14 20:18:57 +01:00
# allow parent range to be unset for already present objects
2017-03-22 04:06:24 +01:00
if not parent_range and (self._create or not self._create and self.instance.parent_range):
2017-03-14 20:18:57 +01:00
raise forms.ValidationError("Parent range must be set")
2017-03-21 02:36:07 +01:00
if not self._create and parent_range:
# make sure we don't have circular dependencies
obj = parent_range
while obj.parent_range:
if obj.pk == self.instance.pk:
raise forms.ValidationError("No circular dependencies allowed")
obj = obj.parent_range
return parent_range
2017-02-28 19:44:15 +01:00
def clean(self):
2017-03-01 03:21:03 +01:00
cleaned_data = super(InetNumForm, self).clean()
2017-03-14 20:18:57 +01:00
if not self._editLower:
if not self.errors:
2017-03-27 00:33:29 +02:00
if not self._create and self.cleaned_data['origin_as']:
if self.instance.inetnum_set.count() > 0:
ranges = ", ".join(map(str, self.instance.inetnum_set.all()))
raise forms.ValidationError("You cannot set an origin as if there are already existing subranges (%s)" % (ranges))
2017-03-01 03:21:03 +01:00
prefix = cleaned_data['prefix']
parent = cleaned_data['parent_range']
2017-03-14 20:18:57 +01:00
if parent:
parentNet = parent.getNetwork()
2017-03-01 03:21:03 +01:00
2017-03-14 20:18:57 +01:00
if cleaned_data['protocol'] != parent.protocol:
raise forms.ValidationError("Protocol type for prefix must be same as parent network")
2017-03-01 03:21:03 +01:00
2017-03-14 20:18:57 +01:00
# check if in parent block
if prefix.network_address not in parentNet or prefix.prefixlen < parentNet.prefixlen:
raise forms.ValidationError("Prefix must be inside parent network range")
2017-03-01 03:21:03 +01:00
2017-03-27 12:18:02 +02:00
2017-03-14 20:18:57 +01:00
# check if parent block has net that overlaps with us
for otherNet in parent.inetnum_set.all():
if self.instance and self.instance.pk == otherNet.pk:
continue
2017-03-01 03:21:03 +01:00
2017-03-14 20:18:57 +01:00
if otherNet.getNetwork().overlaps(prefix):
raise forms.ValidationError("The given prefix overlaps with network %s" % otherNet.handle)
2017-03-01 03:21:03 +01:00
2017-03-27 12:18:02 +02:00
# check if subnets to this subnet are (still) in current network
if not self._create:
for subnet in self.instance.inetnum_set.all():
if subnet.getNetwork().network_address not in self.instance.getNetwork():
raise forms.ValidationError("Subnet %s with %s is not in block anymore" % (subnet, subnet.getNetwork()))
2017-03-01 03:21:03 +01:00
self.instance.address = str(prefix.network_address)
self.instance.netmask = prefix.prefixlen
return cleaned_data
2017-03-22 04:06:24 +01:00
2017-03-14 20:18:57 +01:00
class ASBlockForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
2017-03-22 04:06:24 +01:00
protectedFields = ['parent_block', 'asBegin', 'asEnd', 'mnt_by']
# FIXME: Filter blocks
class Meta:
model = ASBlock
2017-03-20 00:16:33 +01:00
fields = ['handle', 'parent_block', 'asBegin', 'asEnd', 'name', 'description', 'mnt_by', 'mnt_lower', 'admin_c']
def __init__(self, *args, **kwargs):
super(ASBlockForm, self).__init__(*args, **kwargs)
if not self.instance or self.instance and self.instance and self.instance.parent_block:
self.fields["parent_block"].required = True
if self.instance and self.instance.pk:
self.fields["parent_block"].disabled = True
2017-03-21 02:36:07 +01:00
mnts = self._user.maintainer_set.all()
self.fields['parent_block'].queryset = ASBlock.getMntQueryset(mnts, self.instance, "parent_block")
2017-03-22 04:06:24 +01:00
def clean_parent_block(self):
parent_block = self.cleaned_data.get('parent_block', None)
# allow parent range to be unset for already present objects
if not parent_block and (self._create or not self._create and self.instance.parent_block):
raise forms.ValidationError("Parent block must be set")
if not self._create and parent_block:
# make sure we don't have circular dependencies
obj = parent_block
while obj.parent_block:
if obj.pk == self.instance.pk:
raise forms.ValidationError("No circular dependencies allowed")
obj = obj.parent_block
return parent_block
def clean(self):
cleaned_data = super(ASBlockForm, self).clean()
if not self.errors:
asBegin = cleaned_data['asBegin']
asEnd = cleaned_data['asEnd']
parent = cleaned_data['parent_block']
# check if somebody is already using this block
2017-03-22 04:06:24 +01:00
# check if in range
if asBegin > asEnd:
raise forms.ValidationError("AS beginning must be smaller or equal to AS end")
if parent:
if parent.asnumber_set.count() > 0:
raise forms.ValidationError("The parent AS block is already references by following AS number objects: %s" % (", ".join(map(lambda _x: _x.handle, parent.asnumber_set.all())),))
# check if same range
if not (asBegin >= parent.asBegin and asEnd <= parent.asEnd):
raise forms.ValidationError("AS beginning and end must be inside the range of the parent AS block")
if parent.asBegin == asBegin and parent.asEnd == asEnd:
raise forms.ValidationError("The range of this block cannot be the same range AS the parent AS block")
# check for overlap with other asblocks
for block in parent.asblock_set.all():
if self.instance and self.instance.pk and block.pk == self.instance.pk:
continue
if block.asBegin <= asBegin <= block.asEnd or block.asBegin <= asEnd <= block.asEnd or \
2017-03-22 04:06:24 +01:00
asBegin <= block.asBegin <= asEnd or asBegin <= block.asEnd <= asEnd:
raise forms.ValidationError("Block overlaps with block %s" % block.handle)
2017-03-27 12:18:02 +02:00
if not self._create:
# check if subblocks are still in range
for subblock in self.instance.asblock_set.all():
if not (asBegin <= subblock.asBegin <= asEnd and asBegin <= subblock.asEnd <= asEnd):
raise forms.ValidationError("Subblock %s (%s - %s) is not contained in this block anymore" % (subblock, subblock.asBegin, subblock.asEnd))
# check if asnumbers are still in range
for asnumber in self.instance.asnumber_set.all():
if not (asBegin <= asnumber.number <= asEnd):
raise forms.ValidationError("AS %s (%s) is not contained in this block anymore" % (asnumber, asnumber.number))
return cleaned_data
2017-03-22 04:06:24 +01:00
2017-03-14 20:18:57 +01:00
class ASNumberForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
2017-03-22 04:06:24 +01:00
protectedFields = ['asblock', 'number', 'mnt_by']
class Meta:
model = ASNumber
fields = ['handle', 'asblock', 'number', 'volatile', 'name', 'description', 'mnt_by', 'mnt_lower', 'admin_c']
def __init__(self, *args, **kwargs):
super(ASNumberForm, self).__init__(*args, **kwargs)
if not (self.instance and self.instance.pk):
self.fields["asblock"].required = True
else:
self.fields["asblock"].disabled = True
2017-03-21 02:36:07 +01:00
mnts = self._user.maintainer_set.all()
self.fields['asblock'].queryset = ASBlock.getMntQueryset(mnts, self.instance, "asblock")
def clean(self):
cleaned_data = super(ASNumberForm, self).clean()
if not self.errors:
number = cleaned_data['number']
block = cleaned_data['asblock']
# belongs to asblock?
if number < block.asBegin or number > block.asEnd:
raise forms.ValidationError("AS number is not inside AS block")
# does an entry already exist?
try:
otherAS = ASNumber.objects.get(number=number)
if not (self.instance and self.instance.pk and self.instance.pk == otherAS.pk):
raise forms.ValidationError("This AS number is already represented by %s" % otherAS.handle)
except ASNumber.DoesNotExist:
pass
# has already other asblock?
if block.asblock_set.count() > 0:
raise forms.ValidationError("The given AS block is already references by following sub AS blocks: %s" % (", ".join(map(lambda _x: _x.handle, block.asblock_set.all())),))