# This file is part of dnmgmt, a number resource management system # Licensed under GNU General Public License v3 or later # Written by Sebastian Lohff (seba@someserver.de) from django.db import models from django.db.models import Q from django.urls import reverse from dncore.models import User from .validators import HandleValidator, HandleValidatorWithSuffix import ipaddress class WhoisObject(models.Model): class Meta: abstract = True handleSuffix = "" handle = models.SlugField(max_length=32, unique=True, verbose_name='handle', validators=[HandleValidator()]) created = models.DateTimeField(auto_now_add=True) last_modified = models.DateTimeField(auto_now_add=True) #def __init__(self, *args, **kwargs): # super(WhoisObject, self).__init__(*args, **kwargs) # if getattr(self, "handle"): # field = self._meta.get_field("handle") # if HandleValidatorWithSuffix not in map(type, field.validators): # print(self.handle, "NOOOOT") # field.validators.append(HandleValidatorWithSuffix(self.handleSuffix)) # else: # print(self.handle, list(map(type, field.validators))) def getPK(self): return self.handle def __str__(self): return self.handle def getAppName(self): return "whoisdb" def getClassName(self): return self._meta.object_name def genHandle(self, main=None): if not main: main = self.name return self.genGenericHandle(main) @classmethod def genGenericHandle(clazz, main): prefix = "" if " " in main: parts = main.split(" ") prefix = "".join(map(lambda _x: _x[0], parts)) if len(prefix) < 3 and len(parts[-1]) > 1: prefix += parts[-1][1:4 - len(prefix)] else: prefix = main[0:3] prefix = prefix.upper() i = 1 handle = "%s%%d-%s" % (prefix, clazz.handleSuffix) while True: try: prefix clazz.objects.get(handle=handle % i) i += 1 except clazz.DoesNotExist: break return handle % i def getNoDeleteReasons(self): raise NotImplementedError("Delete reason checking is not implemented for this model") def canBeDeleted(self): return not bool(self.getNoDeleteReasons()) def handleAuto(self, name=None): if self.handle == "AUTO": self.handle = self.genHandle(name) class Maintainer(WhoisObject): handleSuffix = "MNT" auth = models.ManyToManyField(User) handle = models.SlugField(max_length=32, unique=True, verbose_name='handle', validators=[HandleValidatorWithSuffix('MNT')], help_text="Must end with -MNT, eg FOO3-MNT") description = models.CharField(max_length=64, blank=True, help_text="Short description what this maintainer is for") admin_c = models.ManyToManyField("Contact", verbose_name="Administrative Contact") rir = models.BooleanField(default=False) lir = models.BooleanField(default=False) # autoInclude = models.BooleanField(default=True) def get_absolute_url(self): return reverse("whoisdb:handle-detail", kwargs={"handle": self.handle}) def getNoDeleteReasons(self): reasons = [] # FIXME: Tempfix for circular dependency problem import domains.models mntables = [Contact, ASBlock, ASNumber, InetNum, domains.models.Domain, domains.models.Nameserver] for mntable in mntables: candidates = mntable.objects.filter(mnt_by=self).annotate(mntCount=models.Count('mnt_by')).filter(mntCount__lte=1) for candidate in candidates: reasons.append("Object %s would have no maintainers left." % candidate.handle) return reasons def canEdit(self, user): return user in self.auth.all() class MntdObject(WhoisObject): class Meta: abstract = True mnt_by = models.ManyToManyField(Maintainer, help_text="You can select multiple maintainers here") def canEdit(self, user): if not hasattr(user, "maintainer_set"): return False mnts = user.maintainer_set.all() objmnts = self.mnt_by.all() if hasattr(self, "mnt_lower"): objmnts |= self.mnt_lower.all() for objmnt in objmnts: if objmnt in mnts: return True return False @classmethod def getMntQueryset(clazz, mnts, instance, attr=None): mntQ = Q(mnt_by__in=mnts) if hasattr(clazz, "mnt_lower"): mntQ |= Q(mnt_lower__in=mnts) qs = clazz.objects.filter(mntQ) if attr and instance and instance.pk: if type(instance._meta.get_field(attr)) == models.ManyToManyField: qs |= getattr(instance, attr).all() elif getattr(instance, attr) is not None: qs |= clazz.objects.filter(pk=getattr(instance, attr).pk) return qs.distinct() class Contact(MntdObject): handleSuffix = "DN" TYPE_PERSON = 'PERSON' TYPE_ROLE = 'ROLE' TYPE = (('person', TYPE_PERSON), ('role', TYPE_ROLE)) TYPE = (('person', TYPE_PERSON),) name = models.CharField(max_length=128) type = models.CharField(max_length=10, choices=TYPE, default=TYPE_PERSON) def get_absolute_url(self): return reverse("whoisdb:handle-detail", kwargs={"handle": self.handle}) def getNoDeleteReasons(self): reasons = [] contactables = [Maintainer, ASBlock, ASNumber, InetNum] for contactable in contactables: candidates = contactable.objects.filter(admin_c=self).annotate(contactCount=models.Count('admin_c')).filter(contactCount__lte=1) for candidate in candidates: reasons.append("Object %s would have no contact left." % candidate.handle) return reasons class ASBlock(MntdObject): handleSuffix = "ASB" parent_block = models.ForeignKey("ASBlock", models.CASCADE, null=True, blank=True, default=None) name = models.CharField(max_length=32) asBegin = models.PositiveIntegerField() asEnd = models.PositiveIntegerField() description = models.CharField(max_length=64, blank=True) admin_c = models.ManyToManyField("Contact") mnt_lower = models.ManyToManyField(Maintainer, related_name='lower_asblock_set', blank=True) def contains(self, block): return self.asBegin <= block.asBegin <= self.asEnd and self.asBegin <= block.asEnd <= self.asEnd def getResource(self): return "%s - %s" % (self.asBegin, self.asEnd) def get_absolute_url(self): return reverse("whoisdb:handle-detail", kwargs={"handle": self.handle}) def getNoDeleteReasons(self): reasons = [] if self.asblock_set.count() > 0: reasons.append("The AS block is referenced by the following other blocks: %s" % (", ".join(map(lambda _x: _x.handle, self.asblock_set.all())))) if self.asnumber_set.count() > 0: reasons.append("The AS block is referenced by the following as numbers: %s" % (", ".join(map(lambda _x: _x.handle, self.asnumber_set.all())))) return reasons class ASNumber(MntdObject): handleSuffix = "AS" number = models.PositiveIntegerField(unique=True, db_index=True) volatile = models.BooleanField(default=False, help_text="Check if this AS is not going to be online 24/7 (for example on a laptop)") asblock = models.ForeignKey(ASBlock, models.CASCADE) name = models.CharField(max_length=32) description = models.CharField(max_length=64, blank=True) admin_c = models.ManyToManyField("Contact") mnt_lower = models.ManyToManyField(Maintainer, related_name='lower_asnumber_set', blank=True) def get_absolute_url(self): return reverse("whoisdb:handle-detail", kwargs={"handle": self.handle}) def getNoDeleteReasons(self): reasons = [] return reasons def getResource(self): return str(self.number) class InetNum(MntdObject): class Meta: unique_together = ( ("address", "netmask"), ) handleSuffix = "NET" IPv4 = "ipv4" IPv6 = "ipv6" PROTO = ((IPv4, 'IPv4'), (IPv6, 'IPv6')) protocol = models.CharField(max_length=4, choices=PROTO) address = models.GenericIPAddressField(db_index=True) netmask = models.PositiveIntegerField() parent_range = models.ForeignKey("InetNum", models.CASCADE, null=True, blank=True, default=None) name = models.CharField(max_length=64) description = models.CharField(max_length=64, blank=True) origin_as = models.ManyToManyField(ASNumber, blank=True) admin_c = models.ManyToManyField("Contact") mnt_lower = models.ManyToManyField(Maintainer, related_name='lower_inetnum_set', blank=True) def getResource(self): return self.prefix() def prefix(self): """ Helper function, mainly used in templates """ return "%s/%s" % (self.address, self.netmask) def getNetwork(self): return ipaddress.ip_network(self.prefix()) def get_absolute_url(self): return reverse("whoisdb:handle-detail", kwargs={"handle": self.handle}) def getNoDeleteReasons(self): reasons = [] if self.inetnum_set.all().count() > 0: reasons.append("The following networks depend on this network: %s" % ", ".join(map(lambda _x: _x.handle, self.inetnum_set.all()))) return reasons