|
|
|
@ -23,44 +23,59 @@ |
|
|
|
|
|
|
|
from __future__ import unicode_literals |
|
|
|
|
|
|
|
from datetime import timedelta |
|
|
|
import re |
|
|
|
from netaddr import mac_bare, EUI, IPSet, IPRange, IPNetwork, IPAddress |
|
|
|
|
|
|
|
from django.db import models |
|
|
|
from django.db.models.signals import post_save, pre_delete, post_delete |
|
|
|
from django.db.models.signals import post_save, post_delete |
|
|
|
from django.dispatch import receiver |
|
|
|
from django.forms import ValidationError |
|
|
|
from django.utils.functional import cached_property |
|
|
|
from django.utils import timezone |
|
|
|
from django.core.validators import MaxValueValidator |
|
|
|
|
|
|
|
from macaddress.fields import MACAddressField |
|
|
|
from netaddr import mac_bare, EUI, IPSet, IPRange, IPNetwork, IPAddress |
|
|
|
from django.core.validators import MinValueValidator,MaxValueValidator |
|
|
|
import re |
|
|
|
from reversion import revisions as reversion |
|
|
|
from datetime import timedelta |
|
|
|
|
|
|
|
|
|
|
|
class Machine(models.Model): |
|
|
|
""" Class définissant une machine, object parent user, objets fils interfaces""" |
|
|
|
""" Class définissant une machine, object parent user, objets fils |
|
|
|
interfaces""" |
|
|
|
PRETTY_NAME = "Machine" |
|
|
|
|
|
|
|
|
|
|
|
user = models.ForeignKey('users.User', on_delete=models.PROTECT) |
|
|
|
name = models.CharField(max_length=255, help_text="Optionnel", blank=True, null=True) |
|
|
|
name = models.CharField( |
|
|
|
max_length=255, |
|
|
|
help_text="Optionnel", |
|
|
|
blank=True, |
|
|
|
null=True |
|
|
|
) |
|
|
|
active = models.BooleanField(default=True) |
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
return str(self.user) + ' - ' + str(self.id) + ' - ' + str(self.name) |
|
|
|
return str(self.user) + ' - ' + str(self.id) + ' - ' + str(self.name) |
|
|
|
|
|
|
|
|
|
|
|
class MachineType(models.Model): |
|
|
|
""" Type de machine, relié à un type d'ip, affecté aux interfaces""" |
|
|
|
PRETTY_NAME = "Type de machine" |
|
|
|
|
|
|
|
type = models.CharField(max_length=255) |
|
|
|
ip_type = models.ForeignKey('IpType', on_delete=models.PROTECT, blank=True, null=True) |
|
|
|
ip_type = models.ForeignKey( |
|
|
|
'IpType', |
|
|
|
on_delete=models.PROTECT, |
|
|
|
blank=True, |
|
|
|
null=True |
|
|
|
) |
|
|
|
|
|
|
|
def all_interfaces(self): |
|
|
|
""" Renvoie toutes les interfaces (cartes réseaux) de type machinetype""" |
|
|
|
""" Renvoie toutes les interfaces (cartes réseaux) de type |
|
|
|
machinetype""" |
|
|
|
return Interface.objects.filter(type=self) |
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
return self.type |
|
|
|
return self.type |
|
|
|
|
|
|
|
|
|
|
|
class IpType(models.Model): |
|
|
|
""" Type d'ip, définissant un range d'ip, affecté aux machine types""" |
|
|
|
@ -71,14 +86,27 @@ class IpType(models.Model): |
|
|
|
need_infra = models.BooleanField(default=False) |
|
|
|
domaine_ip_start = models.GenericIPAddressField(protocol='IPv4') |
|
|
|
domaine_ip_stop = models.GenericIPAddressField(protocol='IPv4') |
|
|
|
prefix_v6 = models.GenericIPAddressField(protocol='IPv6', null=True, blank=True) |
|
|
|
vlan = models.ForeignKey('Vlan', on_delete=models.PROTECT, blank=True, null=True) |
|
|
|
ouverture_ports = models.ForeignKey('OuverturePortList', blank=True, null=True) |
|
|
|
prefix_v6 = models.GenericIPAddressField( |
|
|
|
protocol='IPv6', |
|
|
|
null=True, |
|
|
|
blank=True |
|
|
|
) |
|
|
|
vlan = models.ForeignKey( |
|
|
|
'Vlan', |
|
|
|
on_delete=models.PROTECT, |
|
|
|
blank=True, |
|
|
|
null=True |
|
|
|
) |
|
|
|
ouverture_ports = models.ForeignKey( |
|
|
|
'OuverturePortList', |
|
|
|
blank=True, |
|
|
|
null=True |
|
|
|
) |
|
|
|
|
|
|
|
@cached_property |
|
|
|
def ip_range(self): |
|
|
|
""" Renvoie un objet IPRange à partir de l'objet IpType""" |
|
|
|
return IPRange(self.domaine_ip_start, end=self.domaine_ip_stop) |
|
|
|
""" Renvoie un objet IPRange à partir de l'objet IpType""" |
|
|
|
return IPRange(self.domaine_ip_start, end=self.domaine_ip_stop) |
|
|
|
|
|
|
|
@cached_property |
|
|
|
def ip_set(self): |
|
|
|
@ -96,18 +124,22 @@ class IpType(models.Model): |
|
|
|
|
|
|
|
def free_ip(self): |
|
|
|
""" Renvoie toutes les ip libres associées au type donné (self)""" |
|
|
|
return IpList.objects.filter(interface__isnull=True).filter(ip_type=self) |
|
|
|
return IpList.objects.filter( |
|
|
|
interface__isnull=True |
|
|
|
).filter(ip_type=self) |
|
|
|
|
|
|
|
def gen_ip_range(self): |
|
|
|
""" Cree les IpList associées au type self. Parcours pédestrement et crée |
|
|
|
les ip une par une. Si elles existent déjà, met à jour le type associé |
|
|
|
à l'ip""" |
|
|
|
""" Cree les IpList associées au type self. Parcours pédestrement et |
|
|
|
crée les ip une par une. Si elles existent déjà, met à jour le type |
|
|
|
associé à l'ip""" |
|
|
|
# Creation du range d'ip dans les objets iplist |
|
|
|
networks = [] |
|
|
|
for net in self.ip_range.cidrs(): |
|
|
|
networks += net.iter_hosts() |
|
|
|
ip_obj = [IpList(ip_type=self, ipv4=str(ip)) for ip in networks] |
|
|
|
listes_ip = IpList.objects.filter(ipv4__in=[str(ip) for ip in networks]) |
|
|
|
listes_ip = IpList.objects.filter( |
|
|
|
ipv4__in=[str(ip) for ip in networks] |
|
|
|
) |
|
|
|
# Si il n'y a pas d'ip, on les crée |
|
|
|
if not listes_ip: |
|
|
|
IpList.objects.bulk_create(ip_obj) |
|
|
|
@ -117,9 +149,11 @@ class IpType(models.Model): |
|
|
|
return |
|
|
|
|
|
|
|
def del_ip_range(self): |
|
|
|
""" Methode dépréciée, IpList est en mode cascade et supprimé automatiquement""" |
|
|
|
""" Methode dépréciée, IpList est en mode cascade et supprimé |
|
|
|
automatiquement""" |
|
|
|
if Interface.objects.filter(ipv4__in=self.ip_objects()): |
|
|
|
raise ValidationError("Une ou plusieurs ip du range sont affectées, impossible de supprimer le range") |
|
|
|
raise ValidationError("Une ou plusieurs ip du range sont\ |
|
|
|
affectées, impossible de supprimer le range") |
|
|
|
for ip in self.ip_objects(): |
|
|
|
ip.delete() |
|
|
|
|
|
|
|
@ -133,11 +167,13 @@ class IpType(models.Model): |
|
|
|
raise ValidationError("Domaine end doit être après start...") |
|
|
|
# On ne crée pas plus grand qu'un /16 |
|
|
|
if self.ip_range.size > 65536: |
|
|
|
raise ValidationError("Le range est trop gros, vous ne devez pas créer plus grand qu'un /16") |
|
|
|
raise ValidationError("Le range est trop gros, vous ne devez\ |
|
|
|
pas créer plus grand qu'un /16") |
|
|
|
# On check que les / ne se recoupent pas |
|
|
|
for element in IpType.objects.all().exclude(pk=self.pk): |
|
|
|
if not self.ip_set.isdisjoint(element.ip_set): |
|
|
|
raise ValidationError("Le range indiqué n'est pas disjoint des ranges existants") |
|
|
|
raise ValidationError("Le range indiqué n'est pas disjoint\ |
|
|
|
des ranges existants") |
|
|
|
# On formate le prefix v6 |
|
|
|
if self.prefix_v6: |
|
|
|
self.prefix_v6 = str(IPNetwork(self.prefix_v6 + '/64').network) |
|
|
|
@ -150,6 +186,7 @@ class IpType(models.Model): |
|
|
|
def __str__(self): |
|
|
|
return self.type |
|
|
|
|
|
|
|
|
|
|
|
class Vlan(models.Model): |
|
|
|
""" Un vlan : vlan_id et nom |
|
|
|
On limite le vlan id entre 0 et 4096, comme défini par la norme""" |
|
|
|
@ -162,8 +199,9 @@ class Vlan(models.Model): |
|
|
|
def __str__(self): |
|
|
|
return self.name |
|
|
|
|
|
|
|
|
|
|
|
class Nas(models.Model): |
|
|
|
""" Les nas. Associé à un machine_type. |
|
|
|
""" Les nas. Associé à un machine_type. |
|
|
|
Permet aussi de régler le port_access_mode (802.1X ou mac-address) pour |
|
|
|
le radius. Champ autocapture de la mac à true ou false""" |
|
|
|
PRETTY_NAME = "Correspondance entre les nas et les machines connectées" |
|
|
|
@ -175,33 +213,53 @@ class Nas(models.Model): |
|
|
|
) |
|
|
|
|
|
|
|
name = models.CharField(max_length=255, unique=True) |
|
|
|
nas_type = models.ForeignKey('MachineType', on_delete=models.PROTECT, related_name='nas_type') |
|
|
|
machine_type = models.ForeignKey('MachineType', on_delete=models.PROTECT, related_name='machinetype_on_nas') |
|
|
|
port_access_mode = models.CharField(choices=AUTH, default=default_mode, max_length=32) |
|
|
|
nas_type = models.ForeignKey( |
|
|
|
'MachineType', |
|
|
|
on_delete=models.PROTECT, |
|
|
|
related_name='nas_type' |
|
|
|
) |
|
|
|
machine_type = models.ForeignKey( |
|
|
|
'MachineType', |
|
|
|
on_delete=models.PROTECT, |
|
|
|
related_name='machinetype_on_nas' |
|
|
|
) |
|
|
|
port_access_mode = models.CharField( |
|
|
|
choices=AUTH, |
|
|
|
default=default_mode, |
|
|
|
max_length=32 |
|
|
|
) |
|
|
|
autocapture_mac = models.BooleanField(default=False) |
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
return self.name |
|
|
|
|
|
|
|
|
|
|
|
class Extension(models.Model): |
|
|
|
""" Extension dns type example.org. Précise si tout le monde peut l'utiliser, |
|
|
|
associé à un origin (ip d'origine)""" |
|
|
|
""" Extension dns type example.org. Précise si tout le monde peut |
|
|
|
l'utiliser, associé à un origin (ip d'origine)""" |
|
|
|
PRETTY_NAME = "Extensions dns" |
|
|
|
|
|
|
|
name = models.CharField(max_length=255, unique=True) |
|
|
|
need_infra = models.BooleanField(default=False) |
|
|
|
origin = models.OneToOneField('IpList', on_delete=models.PROTECT, blank=True, null=True) |
|
|
|
origin = models.OneToOneField( |
|
|
|
'IpList', |
|
|
|
on_delete=models.PROTECT, |
|
|
|
blank=True, |
|
|
|
null=True |
|
|
|
) |
|
|
|
|
|
|
|
@cached_property |
|
|
|
def dns_entry(self): |
|
|
|
""" Une entrée DNS A""" |
|
|
|
return "@ IN A " + str(self.origin) |
|
|
|
return "@ IN A " + str(self.origin) |
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
return self.name |
|
|
|
|
|
|
|
|
|
|
|
class Mx(models.Model): |
|
|
|
""" Entrées des MX. Enregistre la zone (extension) associée et la priorité |
|
|
|
""" Entrées des MX. Enregistre la zone (extension) associée et la |
|
|
|
priorité |
|
|
|
Todo : pouvoir associer un MX à une interface """ |
|
|
|
PRETTY_NAME = "Enregistrements MX" |
|
|
|
|
|
|
|
@ -211,12 +269,16 @@ class Mx(models.Model): |
|
|
|
|
|
|
|
@cached_property |
|
|
|
def dns_entry(self): |
|
|
|
return "@ IN MX " + str(self.priority) + " " + str(self.name) |
|
|
|
"""Renvoie l'entrée DNS complète pour un MX à mettre dans les |
|
|
|
fichiers de zones""" |
|
|
|
return "@ IN MX " + str(self.priority) + " " + str(self.name) |
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
return str(self.zone) + ' ' + str(self.priority) + ' ' + str(self.name) |
|
|
|
|
|
|
|
|
|
|
|
class Ns(models.Model): |
|
|
|
"""Liste des enregistrements name servers par zone considéérée""" |
|
|
|
PRETTY_NAME = "Enregistrements NS" |
|
|
|
|
|
|
|
zone = models.ForeignKey('Extension', on_delete=models.PROTECT) |
|
|
|
@ -224,11 +286,13 @@ class Ns(models.Model): |
|
|
|
|
|
|
|
@cached_property |
|
|
|
def dns_entry(self): |
|
|
|
return "@ IN NS " + str(self.ns) |
|
|
|
"""Renvoie un enregistrement NS complet pour les filezones""" |
|
|
|
return "@ IN NS " + str(self.ns) |
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
return str(self.zone) + ' ' + str(self.ns) |
|
|
|
|
|
|
|
|
|
|
|
class Text(models.Model): |
|
|
|
""" Un enregistrement TXT associé à une extension""" |
|
|
|
PRETTY_NAME = "Enregistrement text" |
|
|
|
@ -236,24 +300,33 @@ class Text(models.Model): |
|
|
|
zone = models.ForeignKey('Extension', on_delete=models.PROTECT) |
|
|
|
field1 = models.CharField(max_length=255) |
|
|
|
field2 = models.CharField(max_length=255) |
|
|
|
|
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
return str(self.zone) + " : " + str(self.field1) + " " + str(self.field2) |
|
|
|
return str(self.zone) + " : " + str(self.field1) + " " +\ |
|
|
|
str(self.field2) |
|
|
|
|
|
|
|
@cached_property |
|
|
|
def dns_entry(self): |
|
|
|
"""Renvoie l'enregistrement TXT complet pour le fichier de zone""" |
|
|
|
return str(self.field1) + " IN TXT " + str(self.field2) |
|
|
|
|
|
|
|
|
|
|
|
class Interface(models.Model): |
|
|
|
""" Une interface. Objet clef de l'application machine : |
|
|
|
- une address mac unique. Possibilité de la rendre unique avec le typemachine |
|
|
|
""" Une interface. Objet clef de l'application machine : |
|
|
|
- une address mac unique. Possibilité de la rendre unique avec le |
|
|
|
typemachine |
|
|
|
- une onetoone vers IpList pour attribution ipv4 |
|
|
|
- le type parent associé au range ip et à l'extension |
|
|
|
- un objet domain associé contenant son nom |
|
|
|
- la liste des ports oiuvert""" |
|
|
|
PRETTY_NAME = "Interface" |
|
|
|
|
|
|
|
ipv4 = models.OneToOneField('IpList', on_delete=models.PROTECT, blank=True, null=True) |
|
|
|
ipv4 = models.OneToOneField( |
|
|
|
'IpList', |
|
|
|
on_delete=models.PROTECT, |
|
|
|
blank=True, |
|
|
|
null=True |
|
|
|
) |
|
|
|
mac_address = MACAddressField(integer=False, unique=True) |
|
|
|
machine = models.ForeignKey('Machine', on_delete=models.CASCADE) |
|
|
|
type = models.ForeignKey('MachineType', on_delete=models.PROTECT) |
|
|
|
@ -267,12 +340,14 @@ class Interface(models.Model): |
|
|
|
user = self.machine.user |
|
|
|
return machine.active and user.has_access() |
|
|
|
|
|
|
|
|
|
|
|
@cached_property |
|
|
|
def ipv6_object(self): |
|
|
|
""" Renvoie un objet type ipv6 à partir du prefix associé à l'iptype parent""" |
|
|
|
""" Renvoie un objet type ipv6 à partir du prefix associé à |
|
|
|
l'iptype parent""" |
|
|
|
if self.type.ip_type.prefix_v6: |
|
|
|
return EUI(self.mac_address).ipv6(IPNetwork(self.type.ip_type.prefix_v6).network) |
|
|
|
return EUI(self.mac_address).ipv6( |
|
|
|
IPNetwork(self.type.ip_type.prefix_v6).network |
|
|
|
) |
|
|
|
else: |
|
|
|
return None |
|
|
|
|
|
|
|
@ -286,10 +361,11 @@ class Interface(models.Model): |
|
|
|
return str(EUI(self.mac_address, dialect=mac_bare)).lower() |
|
|
|
|
|
|
|
def filter_macaddress(self): |
|
|
|
""" Tente un formatage mac_bare, si échoue, lève une erreur de validation""" |
|
|
|
""" Tente un formatage mac_bare, si échoue, lève une erreur de |
|
|
|
validation""" |
|
|
|
try: |
|
|
|
self.mac_address = str(EUI(self.mac_address)) |
|
|
|
except : |
|
|
|
except: |
|
|
|
raise ValidationError("La mac donnée est invalide") |
|
|
|
|
|
|
|
def clean(self, *args, **kwargs): |
|
|
|
@ -307,7 +383,8 @@ class Interface(models.Model): |
|
|
|
if free_ips: |
|
|
|
self.ipv4 = free_ips[0] |
|
|
|
else: |
|
|
|
raise ValidationError("Il n'y a plus d'ip disponibles dans le slash") |
|
|
|
raise ValidationError("Il n'y a plus d'ip disponibles\ |
|
|
|
dans le slash") |
|
|
|
return |
|
|
|
|
|
|
|
def unassign_ipv4(self): |
|
|
|
@ -323,7 +400,8 @@ class Interface(models.Model): |
|
|
|
self.filter_macaddress() |
|
|
|
# On verifie la cohérence en forçant l'extension par la méthode |
|
|
|
if self.type.ip_type != self.ipv4.ip_type: |
|
|
|
raise ValidationError("L'ipv4 et le type de la machine ne correspondent pas") |
|
|
|
raise ValidationError("L'ipv4 et le type de la machine ne\ |
|
|
|
correspondent pas") |
|
|
|
super(Interface, self).save(*args, **kwargs) |
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
@ -342,18 +420,34 @@ class Interface(models.Model): |
|
|
|
|
|
|
|
def may_have_port_open(self): |
|
|
|
""" True si l'interface a une ip et une ip publique. |
|
|
|
Permet de ne pas exporter des ouvertures sur des ip privées (useless)""" |
|
|
|
Permet de ne pas exporter des ouvertures sur des ip privées |
|
|
|
(useless)""" |
|
|
|
return self.ipv4 and not self.has_private_ip() |
|
|
|
|
|
|
|
|
|
|
|
class Domain(models.Model): |
|
|
|
""" Objet domain. Enregistrement A et CNAME en même temps : permet de stocker les |
|
|
|
alias et les nom de machines, suivant si interface_parent ou cname sont remplis""" |
|
|
|
""" Objet domain. Enregistrement A et CNAME en même temps : permet de |
|
|
|
stocker les alias et les nom de machines, suivant si interface_parent |
|
|
|
ou cname sont remplis""" |
|
|
|
PRETTY_NAME = "Domaine dns" |
|
|
|
|
|
|
|
interface_parent = models.OneToOneField('Interface', on_delete=models.CASCADE, blank=True, null=True) |
|
|
|
name = models.CharField(help_text="Obligatoire et unique, ne doit pas comporter de points", max_length=255) |
|
|
|
interface_parent = models.OneToOneField( |
|
|
|
'Interface', |
|
|
|
on_delete=models.CASCADE, |
|
|
|
blank=True, |
|
|
|
null=True |
|
|
|
) |
|
|
|
name = models.CharField( |
|
|
|
help_text="Obligatoire et unique, ne doit pas comporter de points", |
|
|
|
max_length=255 |
|
|
|
) |
|
|
|
extension = models.ForeignKey('Extension', on_delete=models.PROTECT) |
|
|
|
cname = models.ForeignKey('self', null=True, blank=True, related_name='related_domain') |
|
|
|
cname = models.ForeignKey( |
|
|
|
'self', |
|
|
|
null=True, |
|
|
|
blank=True, |
|
|
|
related_name='related_domain' |
|
|
|
) |
|
|
|
|
|
|
|
class Meta: |
|
|
|
unique_together = (("name", "extension"),) |
|
|
|
@ -363,30 +457,35 @@ class Domain(models.Model): |
|
|
|
Retourne l'extension propre si c'est un cname, renvoie None sinon""" |
|
|
|
if self.interface_parent: |
|
|
|
return self.interface_parent.type.ip_type.extension |
|
|
|
elif hasattr(self,'extension'): |
|
|
|
elif hasattr(self, 'extension'): |
|
|
|
return self.extension |
|
|
|
else: |
|
|
|
return None |
|
|
|
|
|
|
|
def clean(self): |
|
|
|
""" Validation : |
|
|
|
""" Validation : |
|
|
|
- l'objet est bien soit A soit CNAME |
|
|
|
- le cname est pas pointé sur lui-même |
|
|
|
- le nom contient bien les caractères autorisés par la norme dns et moins de 63 caractères au total |
|
|
|
- le nom contient bien les caractères autorisés par la norme |
|
|
|
dns et moins de 63 caractères au total |
|
|
|
- le couple nom/extension est bien unique""" |
|
|
|
if self.get_extension(): |
|
|
|
self.extension=self.get_extension() |
|
|
|
""" Validation du nom de domaine, extensions dans type de machine, prefixe pas plus long que 63 caractères """ |
|
|
|
self.extension = self.get_extension() |
|
|
|
if self.interface_parent and self.cname: |
|
|
|
raise ValidationError("On ne peut créer à la fois A et CNAME") |
|
|
|
if self.cname==self: |
|
|
|
if self.cname == self: |
|
|
|
raise ValidationError("On ne peut créer un cname sur lui même") |
|
|
|
HOSTNAME_LABEL_PATTERN = re.compile("(?!-)[A-Z\d-]+(?<!-)$", re.IGNORECASE) |
|
|
|
HOSTNAME_LABEL_PATTERN = re.compile( |
|
|
|
"(?!-)[A-Z\d-]+(?<!-)$", |
|
|
|
re.IGNORECASE |
|
|
|
) |
|
|
|
dns = self.name.lower() |
|
|
|
if len(dns) > 63: |
|
|
|
raise ValidationError("Le nom de domaine %s est trop long (maximum de 63 caractères)." % dns) |
|
|
|
raise ValidationError("Le nom de domaine %s est trop long\ |
|
|
|
(maximum de 63 caractères)." % dns) |
|
|
|
if not HOSTNAME_LABEL_PATTERN.match(dns): |
|
|
|
raise ValidationError("Ce nom de domaine %s contient des carractères interdits." % dns) |
|
|
|
raise ValidationError("Ce nom de domaine %s contient des\ |
|
|
|
carractères interdits." % dns) |
|
|
|
self.validate_unique() |
|
|
|
super(Domain, self).clean() |
|
|
|
|
|
|
|
@ -394,10 +493,11 @@ class Domain(models.Model): |
|
|
|
def dns_entry(self): |
|
|
|
""" Une entrée DNS""" |
|
|
|
if self.cname: |
|
|
|
return str(self.name) + " IN CNAME " + str(self.cname) + "." |
|
|
|
return str(self.name) + " IN CNAME " + str(self.cname) + "." |
|
|
|
|
|
|
|
def save(self, *args, **kwargs): |
|
|
|
""" Empèche le save sans extension valide. Force à avoir appellé clean avant""" |
|
|
|
""" Empèche le save sans extension valide. |
|
|
|
Force à avoir appellé clean avant""" |
|
|
|
if not self.get_extension(): |
|
|
|
raise ValidationError("Extension invalide") |
|
|
|
self.full_clean() |
|
|
|
@ -406,6 +506,7 @@ class Domain(models.Model): |
|
|
|
def __str__(self): |
|
|
|
return str(self.name) + str(self.extension) |
|
|
|
|
|
|
|
|
|
|
|
class IpList(models.Model): |
|
|
|
PRETTY_NAME = "Addresses ipv4" |
|
|
|
|
|
|
|
@ -414,13 +515,15 @@ class IpList(models.Model): |
|
|
|
|
|
|
|
@cached_property |
|
|
|
def need_infra(self): |
|
|
|
""" Permet de savoir si un user basique peut assigner cette ip ou non""" |
|
|
|
""" Permet de savoir si un user basique peut assigner cette ip ou |
|
|
|
non""" |
|
|
|
return self.ip_type.need_infra |
|
|
|
|
|
|
|
def clean(self): |
|
|
|
""" Erreur si l'ip_type est incorrect""" |
|
|
|
if not str(self.ipv4) in self.ip_type.ip_set_as_str: |
|
|
|
raise ValidationError("L'ipv4 et le range de l'iptype ne correspondent pas!") |
|
|
|
raise ValidationError("L'ipv4 et le range de l'iptype ne\ |
|
|
|
correspondent pas!") |
|
|
|
return |
|
|
|
|
|
|
|
def save(self, *args, **kwargs): |
|
|
|
@ -430,24 +533,36 @@ class IpList(models.Model): |
|
|
|
def __str__(self): |
|
|
|
return self.ipv4 |
|
|
|
|
|
|
|
|
|
|
|
class Service(models.Model): |
|
|
|
""" Definition d'un service (dhcp, dns, etc)""" |
|
|
|
service_type = models.CharField(max_length=255, blank=True, unique=True) |
|
|
|
min_time_regen = models.DurationField(default=timedelta(minutes=1), help_text="Temps minimal avant nouvelle génération du service") |
|
|
|
regular_time_regen = models.DurationField(default=timedelta(hours=1), help_text="Temps maximal avant nouvelle génération du service") |
|
|
|
min_time_regen = models.DurationField( |
|
|
|
default=timedelta(minutes=1), |
|
|
|
help_text="Temps minimal avant nouvelle génération du service" |
|
|
|
) |
|
|
|
regular_time_regen = models.DurationField( |
|
|
|
default=timedelta(hours=1), |
|
|
|
help_text="Temps maximal avant nouvelle génération du service" |
|
|
|
) |
|
|
|
servers = models.ManyToManyField('Interface', through='Service_link') |
|
|
|
|
|
|
|
def ask_regen(self): |
|
|
|
""" Marque à True la demande de régénération pour un service x """ |
|
|
|
Service_link.objects.filter(service=self).exclude(asked_regen=True).update(asked_regen=True) |
|
|
|
Service_link.objects.filter(service=self).exclude(asked_regen=True)\ |
|
|
|
.update(asked_regen=True) |
|
|
|
return |
|
|
|
|
|
|
|
def process_link(self, servers): |
|
|
|
""" Django ne peut créer lui meme les relations manytomany avec table intermediaire explicite""" |
|
|
|
for serv in servers.exclude(pk__in=Interface.objects.filter(service=self)): |
|
|
|
""" Django ne peut créer lui meme les relations manytomany avec table |
|
|
|
intermediaire explicite""" |
|
|
|
for serv in servers.exclude( |
|
|
|
pk__in=Interface.objects.filter(service=self) |
|
|
|
): |
|
|
|
link = Service_link(service=self, server=serv) |
|
|
|
link.save() |
|
|
|
Service_link.objects.filter(service=self).exclude(server__in=servers).delete() |
|
|
|
Service_link.objects.filter(service=self).exclude(server__in=servers)\ |
|
|
|
.delete() |
|
|
|
return |
|
|
|
|
|
|
|
def save(self, *args, **kwargs): |
|
|
|
@ -456,13 +571,16 @@ class Service(models.Model): |
|
|
|
def __str__(self): |
|
|
|
return str(self.service_type) |
|
|
|
|
|
|
|
|
|
|
|
def regen(service): |
|
|
|
""" Fonction externe pour régérération d'un service, prend un objet service en arg""" |
|
|
|
""" Fonction externe pour régérération d'un service, prend un objet service |
|
|
|
en arg""" |
|
|
|
obj = Service.objects.filter(service_type=service) |
|
|
|
if obj: |
|
|
|
obj[0].ask_regen() |
|
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
class Service_link(models.Model): |
|
|
|
""" Definition du lien entre serveurs et services""" |
|
|
|
service = models.ForeignKey('Service', on_delete=models.CASCADE) |
|
|
|
@ -477,11 +595,16 @@ class Service_link(models.Model): |
|
|
|
self.save() |
|
|
|
|
|
|
|
def need_regen(self): |
|
|
|
""" Décide si le temps minimal écoulé est suffisant pour provoquer une régénération de service""" |
|
|
|
if (self.asked_regen and (self.last_regen + self.service.min_time_regen) < timezone.now()) or (self.last_regen + self.service.regular_time_regen) < timezone.now(): |
|
|
|
return True |
|
|
|
else: |
|
|
|
return False |
|
|
|
""" Décide si le temps minimal écoulé est suffisant pour provoquer une |
|
|
|
régénération de service""" |
|
|
|
return bool( |
|
|
|
(self.asked_regen and ( |
|
|
|
self.last_regen + self.service.min_time_regen |
|
|
|
) < timezone.now() |
|
|
|
) or ( |
|
|
|
self.last_regen + self.service.regular_time_regen |
|
|
|
) < timezone.now() |
|
|
|
) |
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
return str(self.server) + " " + str(self.service) |
|
|
|
@ -489,29 +612,48 @@ class Service_link(models.Model): |
|
|
|
|
|
|
|
class OuverturePortList(models.Model): |
|
|
|
"""Liste des ports ouverts sur une interface.""" |
|
|
|
name = models.CharField(help_text="Nom de la configuration des ports.", max_length=255) |
|
|
|
name = models.CharField( |
|
|
|
help_text="Nom de la configuration des ports.", |
|
|
|
max_length=255 |
|
|
|
) |
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
return self.name |
|
|
|
|
|
|
|
def tcp_ports_in(self): |
|
|
|
return self.ouvertureport_set.filter(protocole=OuverturePort.TCP, io=OuverturePort.IN) |
|
|
|
|
|
|
|
"""Renvoie la liste des ports ouverts en TCP IN pour ce profil""" |
|
|
|
return self.ouvertureport_set.filter( |
|
|
|
protocole=OuverturePort.TCP, |
|
|
|
io=OuverturePort.IN |
|
|
|
) |
|
|
|
|
|
|
|
def udp_ports_in(self): |
|
|
|
return self.ouvertureport_set.filter(protocole=OuverturePort.UDP, io=OuverturePort.IN) |
|
|
|
"""Renvoie la liste des ports ouverts en UDP IN pour ce profil""" |
|
|
|
return self.ouvertureport_set.filter( |
|
|
|
protocole=OuverturePort.UDP, |
|
|
|
io=OuverturePort.IN |
|
|
|
) |
|
|
|
|
|
|
|
def tcp_ports_out(self): |
|
|
|
return self.ouvertureport_set.filter(protocole=OuverturePort.TCP, io=OuverturePort.OUT) |
|
|
|
|
|
|
|
"""Renvoie la liste des ports ouverts en TCP OUT pour ce profil""" |
|
|
|
return self.ouvertureport_set.filter( |
|
|
|
protocole=OuverturePort.TCP, |
|
|
|
io=OuverturePort.OUT |
|
|
|
) |
|
|
|
|
|
|
|
def udp_ports_out(self): |
|
|
|
return self.ouvertureport_set.filter(protocole=OuverturePort.UDP, io=OuverturePort.OUT) |
|
|
|
"""Renvoie la liste des ports ouverts en UDP OUT pour ce profil""" |
|
|
|
return self.ouvertureport_set.filter( |
|
|
|
protocole=OuverturePort.UDP, |
|
|
|
io=OuverturePort.OUT |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
class OuverturePort(models.Model): |
|
|
|
""" |
|
|
|
Représente un simple port ou une plage de ports. |
|
|
|
|
|
|
|
Les ports de la plage sont compris entre begin et en inclus. |
|
|
|
|
|
|
|
Les ports de la plage sont compris entre begin et en inclus. |
|
|
|
Si begin == end alors on ne représente qu'un seul port. |
|
|
|
|
|
|
|
On limite les ports entre 0 et 65535, tels que défini par la RFC |
|
|
|
@ -522,112 +664,150 @@ class OuverturePort(models.Model): |
|
|
|
OUT = 'O' |
|
|
|
begin = models.PositiveIntegerField(validators=[MaxValueValidator(65535)]) |
|
|
|
end = models.PositiveIntegerField(validators=[MaxValueValidator(65535)]) |
|
|
|
port_list = models.ForeignKey('OuverturePortList', on_delete=models.CASCADE) |
|
|
|
port_list = models.ForeignKey( |
|
|
|
'OuverturePortList', |
|
|
|
on_delete=models.CASCADE |
|
|
|
) |
|
|
|
protocole = models.CharField( |
|
|
|
max_length=1, |
|
|
|
choices=( |
|
|
|
(TCP, 'TCP'), |
|
|
|
(UDP, 'UDP'), |
|
|
|
), |
|
|
|
default=TCP, |
|
|
|
max_length=1, |
|
|
|
choices=( |
|
|
|
(TCP, 'TCP'), |
|
|
|
(UDP, 'UDP'), |
|
|
|
), |
|
|
|
default=TCP, |
|
|
|
) |
|
|
|
io = models.CharField( |
|
|
|
max_length=1, |
|
|
|
choices=( |
|
|
|
(IN, 'IN'), |
|
|
|
(OUT, 'OUT'), |
|
|
|
), |
|
|
|
default=OUT, |
|
|
|
max_length=1, |
|
|
|
choices=( |
|
|
|
(IN, 'IN'), |
|
|
|
(OUT, 'OUT'), |
|
|
|
), |
|
|
|
default=OUT, |
|
|
|
) |
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
if self.begin == self.end : |
|
|
|
if self.begin == self.end: |
|
|
|
return str(self.begin) |
|
|
|
return '-'.join([str(self.begin), str(self.end)]) |
|
|
|
|
|
|
|
def show_port(self): |
|
|
|
"""Formatage plus joli, alias pour str""" |
|
|
|
return str(self) |
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_save, sender=Machine) |
|
|
|
def machine_post_save(sender, **kwargs): |
|
|
|
"""Synchronisation ldap et régen parefeu/dhcp lors de la modification |
|
|
|
d'une machine""" |
|
|
|
user = kwargs['instance'].user |
|
|
|
user.ldap_sync(base=False, access_refresh=False, mac_refresh=True) |
|
|
|
regen('dhcp') |
|
|
|
regen('mac_ip_list') |
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_delete, sender=Machine) |
|
|
|
def machine_post_delete(sender, **kwargs): |
|
|
|
"""Synchronisation ldap et régen parefeu/dhcp lors de la suppression |
|
|
|
d'une machine""" |
|
|
|
machine = kwargs['instance'] |
|
|
|
user = machine.user |
|
|
|
user.ldap_sync(base=False, access_refresh=False, mac_refresh=True) |
|
|
|
regen('dhcp') |
|
|
|
regen('mac_ip_list') |
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_save, sender=Interface) |
|
|
|
def interface_post_save(sender, **kwargs): |
|
|
|
"""Synchronisation ldap et régen parefeu/dhcp lors de la modification |
|
|
|
d'une interface""" |
|
|
|
interface = kwargs['instance'] |
|
|
|
user = interface.machine.user |
|
|
|
user.ldap_sync(base=False, access_refresh=False, mac_refresh=True) |
|
|
|
if not interface.may_have_port_open() and interface.port_lists.all(): |
|
|
|
interface.port_lists.clear() |
|
|
|
# Regen services |
|
|
|
regen('dhcp') |
|
|
|
regen('mac_ip_list') |
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_delete, sender=Interface) |
|
|
|
def interface_post_delete(sender, **kwargs): |
|
|
|
"""Synchronisation ldap et régen parefeu/dhcp lors de la suppression |
|
|
|
d'une interface""" |
|
|
|
interface = kwargs['instance'] |
|
|
|
user = interface.machine.user |
|
|
|
user.ldap_sync(base=False, access_refresh=False, mac_refresh=True) |
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_save, sender=IpType) |
|
|
|
def iptype_post_save(sender, **kwargs): |
|
|
|
"""Generation des objets ip après modification d'un range ip""" |
|
|
|
iptype = kwargs['instance'] |
|
|
|
iptype.gen_ip_range() |
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_save, sender=MachineType) |
|
|
|
def machine_post_save(sender, **kwargs): |
|
|
|
"""Mise à jour des interfaces lorsque changement d'attribution |
|
|
|
d'une machinetype (changement iptype parent)""" |
|
|
|
machinetype = kwargs['instance'] |
|
|
|
for interface in machinetype.all_interfaces(): |
|
|
|
interface.update_type() |
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_save, sender=Domain) |
|
|
|
def domain_post_save(sender, **kwargs): |
|
|
|
"""Regeneration dns après modification d'un domain object""" |
|
|
|
regen('dns') |
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_delete, sender=Domain) |
|
|
|
def domain_post_delete(sender, **kwargs): |
|
|
|
"""Regeneration dns après suppression d'un domain object""" |
|
|
|
regen('dns') |
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_save, sender=Extension) |
|
|
|
def extension_post_save(sender, **kwargs): |
|
|
|
"""Regeneration dns après modification d'une extension""" |
|
|
|
regen('dns') |
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_delete, sender=Extension) |
|
|
|
def extension_post_selete(sender, **kwargs): |
|
|
|
"""Regeneration dns après suppression d'une extension""" |
|
|
|
regen('dns') |
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_save, sender=Mx) |
|
|
|
def mx_post_save(sender, **kwargs): |
|
|
|
"""Regeneration dns après modification d'un MX""" |
|
|
|
regen('dns') |
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_delete, sender=Mx) |
|
|
|
def mx_post_delete(sender, **kwargs): |
|
|
|
"""Regeneration dns après suppresson d'un MX""" |
|
|
|
regen('dns') |
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_save, sender=Ns) |
|
|
|
def ns_post_save(sender, **kwargs): |
|
|
|
"""Regeneration dns après modification d'un NS""" |
|
|
|
regen('dns') |
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_delete, sender=Ns) |
|
|
|
def ns_post_delete(sender, **kwargs): |
|
|
|
"""Regeneration dns après modification d'un NS""" |
|
|
|
regen('dns') |
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_save, sender=Text) |
|
|
|
def text_post_save(sender, **kwargs): |
|
|
|
"""Regeneration dns après modification d'un TXT""" |
|
|
|
regen('dns') |
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_delete, sender=Text) |
|
|
|
def text_post_delete(sender, **kwargs): |
|
|
|
"""Regeneration dns après modification d'un TX""" |
|
|
|
regen('dns') |
|
|
|
|