8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-12-25 16:33:47 +00:00

Merge branch 'master' into sort_columns

This commit is contained in:
Maël Kervella 2017-10-22 16:18:03 +00:00
commit 2b38cdb7b6
13 changed files with 316 additions and 18 deletions

View file

@ -27,8 +27,8 @@ from django.contrib import admin
from reversion.admin import VersionAdmin
from .models import IpType, Machine, MachineType, Domain, IpList, Interface
from .models import Extension, Mx, Ns, Vlan, Text, Nas, Service, OuverturePort
from .models import OuverturePortList
from .models import Extension, SOA, Mx, Ns, Vlan, Text, Nas, Service
from .models import OuverturePort, OuverturePortList
class MachineAdmin(VersionAdmin):
@ -51,6 +51,10 @@ class ExtensionAdmin(VersionAdmin):
pass
class SOAAdmin(VersionAdmin):
pass
class MxAdmin(VersionAdmin):
pass
@ -95,6 +99,7 @@ admin.site.register(Machine, MachineAdmin)
admin.site.register(MachineType, MachineTypeAdmin)
admin.site.register(IpType, IpTypeAdmin)
admin.site.register(Extension, ExtensionAdmin)
admin.site.register(SOA, SOAAdmin)
admin.site.register(Mx, MxAdmin)
admin.site.register(Ns, NsAdmin)
admin.site.register(Text, TextAdmin)

View file

@ -45,6 +45,7 @@ from .models import (
IpList,
MachineType,
Extension,
SOA,
Mx,
Text,
Ns,
@ -175,12 +176,18 @@ class AliasForm(ModelForm):
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
infra = kwargs.pop('infra')
super(AliasForm, self).__init__(*args, prefix=prefix, **kwargs)
if not infra:
self.fields['extension'].queryset = Extension.objects.filter(
need_infra=False
)
class DomainForm(AliasForm):
class DomainForm(ModelForm):
"""Ajout et edition d'un enregistrement de nom, relié à interface"""
class Meta(AliasForm.Meta):
class Meta:
model = Domain
fields = ['name']
def __init__(self, *args, **kwargs):
@ -274,6 +281,7 @@ class ExtensionForm(ModelForm):
self.fields['name'].label = 'Extension à ajouter'
self.fields['origin'].label = 'Enregistrement A origin'
self.fields['origin_v6'].label = 'Enregistrement AAAA origin'
self.fields['soa'].label = 'En-tête SOA à utiliser'
class DelExtensionForm(Form):
@ -285,6 +293,26 @@ class DelExtensionForm(Form):
)
class SOAForm(ModelForm):
"""Ajout et edition d'un SOA"""
class Meta:
model = SOA
fields = '__all__'
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(SOAForm, self).__init__(*args, prefix=prefix, **kwargs)
class DelSOAForm(Form):
"""Suppression d'un ou plusieurs SOA"""
soa = forms.ModelMultipleChoiceField(
queryset=SOA.objects.all(),
label="SOA actuels",
widget=forms.CheckboxSelectMultiple
)
class MxForm(ModelForm):
"""Ajout et edition d'un MX"""
class Meta:

View file

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-10-19 22:40
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import machines.models
class Migration(migrations.Migration):
dependencies = [
('machines', '0062_extension_origin_v6'),
]
operations = [
migrations.CreateModel(
name='SOA',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('mail', models.EmailField(help_text='Email du contact pour la zone', max_length=254)),
('refresh', models.PositiveIntegerField(default=86400, help_text='Secondes avant que les DNS secondaires doivent demander le serial du DNS primaire pour détecter une modification')),
('retry', models.PositiveIntegerField(default=7200, help_text='Secondes avant que les DNS secondaires fassent une nouvelle demande de serial en cas de timeout du DNS primaire')),
('expire', models.PositiveIntegerField(default=3600000, help_text='Secondes après lesquelles les DNS secondaires arrêtent de de répondre aux requêtes en cas de timeout du DNS primaire')),
('ttl', models.PositiveIntegerField(default=172800, help_text='Time To Live')),
],
),
migrations.AddField(
model_name='extension',
name='soa',
field=models.ForeignKey(default=machines.models.SOA.new_default_soa, on_delete=django.db.models.deletion.CASCADE, to='machines.SOA'),
),
]

View file

@ -234,6 +234,78 @@ class Nas(models.Model):
return self.name
class SOA(models.Model):
"""
Un enregistrement SOA associé à une extension
Les valeurs par défault viennent des recommandations RIPE :
https://www.ripe.net/publications/docs/ripe-203
"""
PRETTY_NAME = "Enregistrement SOA"
name = models.CharField(max_length=255)
mail = models.EmailField(
help_text='Email du contact pour la zone'
)
refresh = models.PositiveIntegerField(
default=86400, # 24 hours
help_text='Secondes avant que les DNS secondaires doivent demander le\
serial du DNS primaire pour détecter une modification'
)
retry = models.PositiveIntegerField(
default=7200, # 2 hours
help_text='Secondes avant que les DNS secondaires fassent une nouvelle\
demande de serial en cas de timeout du DNS primaire'
)
expire = models.PositiveIntegerField(
default=3600000, # 1000 hours
help_text='Secondes après lesquelles les DNS secondaires arrêtent de\
de répondre aux requêtes en cas de timeout du DNS primaire'
)
ttl = models.PositiveIntegerField(
default=172800, # 2 days
help_text='Time To Live'
)
def __str__(self):
return str(self.name)
@cached_property
def dns_soa_param(self):
"""
Renvoie la partie de l'enregistrement SOA correspondant aux champs :
<refresh> ; refresh
<retry> ; retry
<expire> ; expire
<ttl> ; TTL
"""
return (
' {refresh}; refresh\n'
' {retry}; retry\n'
' {expire}; expire\n'
' {ttl}; TTL'
).format(
refresh=str(self.refresh).ljust(12),
retry=str(self.retry).ljust(12),
expire=str(self.expire).ljust(12),
ttl=str(self.ttl).ljust(12)
)
@cached_property
def dns_soa_mail(self):
""" Renvoie le mail dans l'enregistrement SOA """
mail_fields = str(self.mail).split('@')
return mail_fields[0].replace('.', '\\.') + '.' + mail_fields[1] + '.'
@classmethod
def new_default_soa(cls):
""" Fonction pour créer un SOA par défaut, utile pour les nouvelles
extensions .
/!\ Ne jamais supprimer ou renommer cette fonction car elle est
utilisée dans les migrations de la BDD. """
return cls.objects.get_or_create(name="SOA to edit", mail="postmaser@example.com")[0].pk
class Extension(models.Model):
""" Extension dns type example.org. Précise si tout le monde peut
l'utiliser, associé à un origin (ip d'origine)"""
@ -252,17 +324,22 @@ class Extension(models.Model):
null=True,
blank=True
)
soa = models.ForeignKey(
'SOA',
on_delete=models.CASCADE,
default=SOA.new_default_soa
)
@cached_property
def dns_entry(self):
""" Une entrée DNS A et AAAA sur origin (zone self)"""
entry = ""
if self.origin:
entry += "@ IN A " + str(self.origin)
entry += "@ IN A " + str(self.origin)
if self.origin_v6:
if entry:
entry += "\n"
entry += "@ IN AAAA " + str(self.origin_v6)
entry += "@ IN AAAA " + str(self.origin_v6)
return entry
def __str__(self):
@ -283,7 +360,7 @@ class Mx(models.Model):
def dns_entry(self):
"""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)
return "@ IN MX " + str(self.priority).ljust(3) + " " + str(self.name)
def __str__(self):
return str(self.zone) + ' ' + str(self.priority) + ' ' + str(self.name)
@ -299,7 +376,7 @@ class Ns(models.Model):
@cached_property
def dns_entry(self):
"""Renvoie un enregistrement NS complet pour les filezones"""
return "@ IN NS " + str(self.ns)
return "@ IN NS " + str(self.ns)
def __str__(self):
return str(self.zone) + ' ' + str(self.ns)
@ -307,7 +384,7 @@ class Ns(models.Model):
class Text(models.Model):
""" Un enregistrement TXT associé à une extension"""
PRETTY_NAME = "Enregistrement text"
PRETTY_NAME = "Enregistrement TXT"
zone = models.ForeignKey('Extension', on_delete=models.PROTECT)
field1 = models.CharField(max_length=255)
@ -320,7 +397,7 @@ class Text(models.Model):
@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)
return str(self.field1).ljust(15) + " IN TXT " + str(self.field2)
class Interface(models.Model):
@ -506,7 +583,7 @@ 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).ljust(15) + " IN CNAME " + str(self.cname) + "."
def save(self, *args, **kwargs):
""" Empèche le save sans extension valide.
@ -790,6 +867,18 @@ def extension_post_selete(sender, **kwargs):
regen('dns')
@receiver(post_save, sender=SOA)
def soa_post_save(sender, **kwargs):
"""Regeneration dns après modification d'un SOA"""
regen('dns')
@receiver(post_delete, sender=SOA)
def soa_post_delete(sender, **kwargs):
"""Regeneration dns après suppresson d'un SOA"""
regen('dns')
@receiver(post_save, sender=Mx)
def mx_post_save(sender, **kwargs):
"""Regeneration dns après modification d'un MX"""

View file

@ -158,10 +158,11 @@ class ExtensionSerializer(serializers.ModelSerializer):
des foreign_key donc evalués en get_..."""
origin = serializers.SerializerMethodField('get_origin_ip')
zone_entry = serializers.SerializerMethodField('get_zone_name')
soa = serializers.SerializerMethodField('get_soa_data')
class Meta:
model = Extension
fields = ('name', 'origin', 'origin_v6', 'zone_entry')
fields = ('name', 'origin', 'origin_v6', 'zone_entry', 'soa')
def get_origin_ip(self, obj):
return obj.origin.ipv4
@ -169,6 +170,9 @@ class ExtensionSerializer(serializers.ModelSerializer):
def get_zone_name(self, obj):
return str(obj.dns_entry)
def get_soa_data(self, obj):
return { 'mail': obj.soa.dns_soa_mail, 'param': obj.soa.dns_soa_param }
class MxSerializer(serializers.ModelSerializer):
"""Serialisation d'un MX, evaluation du nom, de la zone

View file

@ -26,19 +26,25 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<thead>
<tr>
<th>Extension</th>
<th>Autorisation infra pour utiliser l'extension</th>
<th>Droit infra pour utiliser ?</th>
<th>Enregistrement SOA</th>
<th>Enregistrement A origin</th>
{% if ipv6_enabled %}
<th>Enregistrement AAAA origin</th>
{% endif %}
<th></th>
</tr>
</thead>
{% for extension in extension_list %}
<tr>
<td>{{ extension.name }}</td>
<td>{{ extension.need_infra }}</td>
<td>{{ extension.need_infra }}</td>
<td>{{ extension.soa}}</td>
<td>{{ extension.origin }}</td>
<td>{{ extension.origin_v6 }}</td>
<td class="text-right">
{% if ipv6_enabled %}
<td>{{ extension.origin_v6 }}</td>
{% endif %}
<td class="text-right">
{% if is_infra %}
{% include 'buttons/edit.html' with href='machines:edit-extension' id=extension.id %}
{% endif %}

View file

@ -0,0 +1,56 @@
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
<table class="table table-striped">
<thead>
<tr>
<th>Nom</th>
<th>Mail</th>
<th>Refresh</th>
<th>Retry</th>
<th>Expire</th>
<th>TTL</th>
<th></th>
<th></th>
</tr>
</thead>
{% for soa in soa_list %}
<tr>
<td>{{ soa.name }}</td>
<td>{{ soa.mail }}</td>
<td>{{ soa.refresh }}</td>
<td>{{ soa.retry }}</td>
<td>{{ soa.expire }}</td>
<td>{{ soa.ttl }}</td>
<td class="text-right">
{% if is_infra %}
{% include 'buttons/edit.html' with href='machines:edit-soa' id=soa.id %}
{% endif %}
{% include 'buttons/history.html' with href='machines:history' name='soa' id=soa.id %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -35,6 +35,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endif %}
{% include "machines/aff_extension.html" with extension_list=extension_list %}
<h2>Liste des enregistrements SOA</h2>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-soa' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un enregistrement SOA</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-soa' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un enregistrement SOA</a>
{% endif %}
{% include "machines/aff_soa.html" with soa_list=soa_list %}
<h2>Liste des enregistrements MX</h2>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-mx' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un enregistrement MX</a>

View file

@ -31,8 +31,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<h2>Liste des nas</h2>
<h5>La correpondance nas-machinetype relie le type de nas à un type de machine.
Elle est utile pour l'autoenregistrement des macs par radius, et permet de choisir le type de machine à affecter aux machines en fonction du type de nas</h5>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-nas' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un type de nas</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-nas' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs types nas</a>
{% endif %}
{% include "machines/aff_nas.html" with nas_list=nas_list %}
<br />
<br />

View file

@ -29,8 +29,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% block content %}
<h2>Liste des vlans</h2>
{% if is_infra %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-vlan' %}"><i class="glyphicon glyphicon-plus"></i> Ajouter un vlan</a>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-vlan' %}"><i class="glyphicon glyphicon-trash"></i> Supprimer un ou plusieurs vlan</a>
{% endif %}
{% include "machines/aff_vlan.html" with vlan_list=vlan_list %}
<br />
<br />

View file

@ -100,6 +100,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<h3>Extension</h3>
{% massive_bootstrap_form extensionform 'origin' %}
{% endif %}
{% if soaform %}
<h3>Enregistrement SOA</h3>
{% bootstrap_form soaform %}
{% endif %}
{% if mxform %}
<h3>Enregistrement MX</h3>
{% massive_bootstrap_form mxform 'name' %}

View file

@ -44,6 +44,9 @@ urlpatterns = [
url(r'^add_extension/$', views.add_extension, name='add-extension'),
url(r'^edit_extension/(?P<extensionid>[0-9]+)$', views.edit_extension, name='edit-extension'),
url(r'^del_extension/$', views.del_extension, name='del-extension'),
url(r'^add_soa/$', views.add_soa, name='add-soa'),
url(r'^edit_soa/(?P<soaid>[0-9]+)$', views.edit_soa, name='edit-soa'),
url(r'^del_soa/$', views.del_soa, name='del-soa'),
url(r'^add_mx/$', views.add_mx, name='add-mx'),
url(r'^edit_mx/(?P<mxid>[0-9]+)$', views.edit_mx, name='edit-mx'),
url(r'^del_mx/$', views.del_mx, name='del-mx'),
@ -74,6 +77,7 @@ urlpatterns = [
url(r'^history/(?P<object>interface)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>machinetype)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>extension)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>soa)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>mx)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>ns)/(?P<id>[0-9]+)$', views.history, name='history'),
url(r'^history/(?P<object>txt)/(?P<id>[0-9]+)$', views.history, name='history'),

View file

@ -77,6 +77,8 @@ from .forms import (
DomainForm,
AliasForm,
DelAliasForm,
SOAForm,
DelSOAForm,
NsForm,
DelNsForm,
TxtForm,
@ -98,6 +100,7 @@ from .models import (
IpList,
MachineType,
Extension,
SOA,
Mx,
Ns,
Domain,
@ -520,6 +523,54 @@ def del_extension(request):
return redirect("/machines/index_extension")
return form({'extensionform': extension}, 'machines/machine.html', request)
@login_required
@permission_required('infra')
def add_soa(request):
soa = SOAForm(request.POST or None)
if soa.is_valid():
with transaction.atomic(), reversion.create_revision():
soa.save()
reversion.set_user(request.user)
reversion.set_comment("Création")
messages.success(request, "Cet enregistrement SOA a été ajouté")
return redirect("/machines/index_extension")
return form({'soaform': soa}, 'machines/machine.html', request)
@login_required
@permission_required('infra')
def edit_soa(request, soaid):
try:
soa_instance = SOA.objects.get(pk=soaid)
except SOA.DoesNotExist:
messages.error(request, u"Entrée inexistante" )
return redirect("/machines/index_extension/")
soa = SOAForm(request.POST or None, instance=soa_instance)
if soa.is_valid():
with transaction.atomic(), reversion.create_revision():
soa.save()
reversion.set_user(request.user)
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in soa.changed_data))
messages.success(request, "SOA modifié")
return redirect("/machines/index_extension/")
return form({'soaform': soa}, 'machines/machine.html', request)
@login_required
@permission_required('infra')
def del_soa(request):
soa = DelSOAForm(request.POST or None)
if soa.is_valid():
soa_dels = soa.cleaned_data['soa']
for soa_del in soa_dels:
try:
with transaction.atomic(), reversion.create_revision():
soa_del.delete()
reversion.set_user(request.user)
messages.success(request, "Le SOA a été supprimée")
except ProtectedError:
messages.error(request, "Erreur le SOA suivant %s ne peut être supprimé" % soa_del)
return redirect("/machines/index_extension")
return form({'soaform': soa}, 'machines/machine.html', request)
@login_required
@permission_required('infra')
def add_mx(request):
@ -932,11 +983,12 @@ def index_nas(request):
@login_required
@permission_required('cableur')
def index_extension(request):
extension_list = Extension.objects.select_related('origin').order_by('name')
extension_list = Extension.objects.select_related('origin').select_related('soa').order_by('name')
soa_list = SOA.objects.order_by('name')
mx_list = Mx.objects.order_by('zone').select_related('zone').select_related('name__extension')
ns_list = Ns.objects.order_by('zone').select_related('zone').select_related('ns__extension')
text_list = Text.objects.all().select_related('zone')
return render(request, 'machines/index_extension.html', {'extension_list':extension_list, 'mx_list': mx_list, 'ns_list': ns_list, 'text_list' : text_list})
return render(request, 'machines/index_extension.html', {'extension_list':extension_list, 'soa_list': soa_list, 'mx_list': mx_list, 'ns_list': ns_list, 'text_list' : text_list})
@login_required
def index_alias(request, interfaceid):
@ -1005,6 +1057,12 @@ def history(request, object, id):
except Extension.DoesNotExist:
messages.error(request, "Extension inexistante")
return redirect("/machines/")
elif object == 'soa' and request.user.has_perms(('cableur',)):
try:
object_instance = SOA.objects.get(pk=id)
except SOA.DoesNotExist:
messages.error(request, "SOA inexistant")
return redirect("/machines/")
elif object == 'mx' and request.user.has_perms(('cableur',)):
try:
object_instance = Mx.objects.get(pk=id)