From dfdecf54ca03bb0cff841b8bfd7e3c1fd2700d51 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Thu, 16 Nov 2017 02:33:57 +0100 Subject: [PATCH] Fix #52 --- machines/admin.py | 20 ++++- machines/forms.py | 21 ++++++ machines/migrations/0066_srv.py | 31 ++++++++ .../migrations/0067_auto_20171116_0152.py | 37 ++++++++++ machines/models.py | 72 +++++++++++++++++- machines/serializers.py | 27 +++++++ machines/templates/machines/aff_srv.html | 60 +++++++++++++++ .../templates/machines/index_extension.html | 6 ++ machines/templates/machines/machine.html | 7 ++ machines/urls.py | 5 ++ machines/views.py | 73 ++++++++++++++++++- 11 files changed, 351 insertions(+), 8 deletions(-) create mode 100644 machines/migrations/0066_srv.py create mode 100644 machines/migrations/0067_auto_20171116_0152.py create mode 100644 machines/templates/machines/aff_srv.html diff --git a/machines/admin.py b/machines/admin.py index 763cff43..e6165672 100644 --- a/machines/admin.py +++ b/machines/admin.py @@ -27,8 +27,19 @@ from django.contrib import admin from reversion.admin import VersionAdmin from .models import IpType, Machine, MachineType, Domain, IpList, Interface -from .models import Extension, SOA, Mx, Ns, Vlan, Txt, Nas, Service -from .models import OuverturePort, OuverturePortList +from .models import ( + Extension, + SOA, + Mx, + Ns, + Vlan, + Txt, + Srv, + Nas, + Service, + OuverturePort, + OuverturePortList +) class MachineAdmin(VersionAdmin): @@ -67,6 +78,10 @@ class TxtAdmin(VersionAdmin): pass +class SrvAdmin(VersionAdmin): + pass + + class NasAdmin(VersionAdmin): pass @@ -103,6 +118,7 @@ admin.site.register(SOA, SOAAdmin) admin.site.register(Mx, MxAdmin) admin.site.register(Ns, NsAdmin) admin.site.register(Txt, TxtAdmin) +admin.site.register(Srv, SrvAdmin) admin.site.register(IpList, IpListAdmin) admin.site.register(Interface, InterfaceAdmin) admin.site.register(Domain, DomainAdmin) diff --git a/machines/forms.py b/machines/forms.py index b043d9f5..d6aa5e3e 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -51,6 +51,7 @@ from .models import ( Ns, Service, Vlan, + Srv, Nas, IpType, OuverturePortList, @@ -380,6 +381,26 @@ class DelTxtForm(Form): widget=forms.CheckboxSelectMultiple ) + +class SrvForm(ModelForm): + """Ajout d'un srv pour une zone""" + class Meta: + model = Srv + fields = '__all__' + + def __init__(self, *args, **kwargs): + prefix = kwargs.pop('prefix', self.Meta.model.__name__) + super(SrvForm, self).__init__(*args, prefix=prefix, **kwargs) + + +class DelSrvForm(Form): + """Suppression d'un ou plusieurs Srv""" + srv = forms.ModelMultipleChoiceField( + queryset=Srv.objects.all(), + label="Enregistrements Srv actuels", + widget=forms.CheckboxSelectMultiple + ) + class NasForm(ModelForm): """Ajout d'un type de nas (machine d'authentification, diff --git a/machines/migrations/0066_srv.py b/machines/migrations/0066_srv.py new file mode 100644 index 00000000..94b7d0ce --- /dev/null +++ b/machines/migrations/0066_srv.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-11-16 00:10 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0065_auto_20171115_1514'), + ] + + operations = [ + migrations.CreateModel( + name='Srv', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('service', models.CharField(max_length=31)), + ('protocole', models.CharField(choices=[('TCP', 'TCP'), ('UDP', 'UDP')], default='TCP', max_length=3)), + ('ttl', models.PositiveIntegerField(default=172800, help_text='Time To Live')), + ('priority', models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(65535)])), + ('weight', models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(65535)])), + ('port', models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(65535)])), + ('extension', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='machines.Extension')), + ('target', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='machines.Domain')), + ], + ), + ] diff --git a/machines/migrations/0067_auto_20171116_0152.py b/machines/migrations/0067_auto_20171116_0152.py new file mode 100644 index 00000000..dec7f865 --- /dev/null +++ b/machines/migrations/0067_auto_20171116_0152.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-11-16 00:52 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0066_srv'), + ] + + operations = [ + migrations.AlterField( + model_name='srv', + name='port', + field=models.PositiveIntegerField(help_text='Port (tcp/udp)', validators=[django.core.validators.MaxValueValidator(65535)]), + ), + migrations.AlterField( + model_name='srv', + name='priority', + field=models.PositiveIntegerField(help_text="La priorité du serveur cible (valeur entière non négative, plus elle est faible, plus ce serveur sera utilisé s'il est disponible)", validators=[django.core.validators.MaxValueValidator(65535)]), + ), + migrations.AlterField( + model_name='srv', + name='target', + field=models.ForeignKey(help_text='Serveur cible', on_delete=django.db.models.deletion.PROTECT, to='machines.Domain'), + ), + migrations.AlterField( + model_name='srv', + name='weight', + field=models.PositiveIntegerField(help_text='Poids relatif pour les enregistrements de même priorité (valeur entière de 0 à 65535)', validators=[django.core.validators.MaxValueValidator(65535)]), + ), + ] diff --git a/machines/models.py b/machines/models.py index e3104f22..76274e7f 100644 --- a/machines/models.py +++ b/machines/models.py @@ -311,18 +311,24 @@ class Extension(models.Model): l'utiliser, associé à un origin (ip d'origine)""" PRETTY_NAME = "Extensions dns" - name = models.CharField(max_length=255, unique=True) + name = models.CharField( + max_length=255, + unique=True, + help_text="Nom de la zone, doit commencer par un point (.example.org)" + ) need_infra = models.BooleanField(default=False) origin = models.OneToOneField( 'IpList', on_delete=models.PROTECT, blank=True, - null=True + null=True, + help_text="Enregistrement A associé à la zone" ) origin_v6 = models.GenericIPAddressField( protocol='IPv6', null=True, - blank=True + blank=True, + help_text="Enregistremen AAAA associé à la zone" ) soa = models.ForeignKey( 'SOA', @@ -345,6 +351,11 @@ class Extension(models.Model): def __str__(self): return self.name + def clean(self): + if self.name and self.name[0] != '.': + raise ValidationError("Une extension doit commencer par un point") + super(Extension, self).clean(*args, **kwargs) + class Mx(models.Model): """ Entrées des MX. Enregistre la zone (extension) associée et la @@ -400,6 +411,61 @@ class Txt(models.Model): return str(self.field1).ljust(15) + " IN TXT " + str(self.field2) +class Srv(models.Model): + PRETTY_NAME = "Enregistrement Srv" + + TCP = 'TCP' + UDP = 'UDP' + + service = models.CharField(max_length=31) + protocole = models.CharField( + max_length=3, + choices=( + (TCP, 'TCP'), + (UDP, 'UDP'), + ), + default=TCP, + ) + extension = models.ForeignKey('Extension', on_delete=models.PROTECT) + ttl = models.PositiveIntegerField( + default=172800, # 2 days + help_text='Time To Live' + ) + priority = models.PositiveIntegerField( + validators=[MaxValueValidator(65535)], + help_text="La priorité du serveur cible (valeur entière non négative,\ + plus elle est faible, plus ce serveur sera utilisé s'il est disponible)" + + ) + weight = models.PositiveIntegerField( + validators=[MaxValueValidator(65535)], + help_text="Poids relatif pour les enregistrements de même priorité\ + (valeur entière de 0 à 65535)" + ) + port = models.PositiveIntegerField( + validators=[MaxValueValidator(65535)], + help_text="Port (tcp/udp)" + ) + target = models.ForeignKey( + 'Domain', + on_delete=models.PROTECT, + help_text="Serveur cible" + ) + + def __str__(self): + return str(self.service) + ' ' + str(self.protocole) + ' ' +\ + str(self.extension) + ' ' + str(self.priority) +\ + ' ' + str(self.weight) + str(self.port) + str(self.target) + + @cached_property + def dns_entry(self): + """Renvoie l'enregistrement SRV complet pour le fichier de zone""" + return str(self.service) + '._' + str(self.protocole).lower() +\ + str(self.extension) + '. ' + str(self.ttl) + ' IN SRV ' +\ + str(self.priority) + ' ' + str(self.weight) + ' ' +\ + str(self.port) + ' ' + str(self.target) + '.' + + class Interface(models.Model): """ Une interface. Objet clef de l'application machine : - une address mac unique. Possibilité de la rendre unique avec le diff --git a/machines/serializers.py b/machines/serializers.py index 6d128da4..d1876e06 100644 --- a/machines/serializers.py +++ b/machines/serializers.py @@ -33,6 +33,7 @@ from machines.models import ( Domain, Txt, Mx, + Srv, Service_link, Ns, OuverturePortList, @@ -212,6 +213,32 @@ class TxtSerializer(serializers.ModelSerializer): return str(obj.dns_entry) +class SrvSerializer(serializers.ModelSerializer): + """Serialisation d'un srv : zone cible et l'entrée txt""" + extension = serializers.SerializerMethodField('get_extension_name') + srv_entry = serializers.SerializerMethodField('get_srv_name') + + class Meta: + model = Srv + fields = ( + 'service', + 'protocole', + 'extension', + 'ttl', + 'priority', + 'weight', + 'port', + 'target', + 'srv_entry' + ) + + def get_extension_name(self, obj): + return str(obj.extension.name) + + def get_srv_name(self, obj): + return str(obj.dns_entry) + + class NsSerializer(serializers.ModelSerializer): """Serialisation d'un NS : la zone, l'entrée ns complète et le serveur ns sont évalués à part""" diff --git a/machines/templates/machines/aff_srv.html b/machines/templates/machines/aff_srv.html new file mode 100644 index 00000000..a06d5626 --- /dev/null +++ b/machines/templates/machines/aff_srv.html @@ -0,0 +1,60 @@ +{% 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 %} + + + + + + + + + + + + + + + + + {% for srv in srv_list %} + + + + + + + + + + + + {% endfor %} +
ServiceProtocoleExtensionTTLPrioritéPoidsPortCible
{{ srv.service }}{{ srv.protocole }}{{ srv.extension }}{{ srv.ttl }}{{ srv.prority }}{{ srv.weight }}{{ srv.port }}{{ srv.target }} + {% if is_infra %} + {% include 'buttons/edit.html' with href='machines:edit-srv' id=srv.id %} + {% endif %} + {% include 'buttons/history.html' with href='machines:history' name='srv' id=srv.id %} +
+ + diff --git a/machines/templates/machines/index_extension.html b/machines/templates/machines/index_extension.html index ceae84c6..3169b4c9 100644 --- a/machines/templates/machines/index_extension.html +++ b/machines/templates/machines/index_extension.html @@ -59,6 +59,12 @@ with this program; if not, write to the Free Software Foundation, Inc., Supprimer un enregistrement TXT {% endif %} {% include "machines/aff_txt.html" with txt_list=txt_list %} +

Liste des enregistrements SRV

+ {% if is_infra %} + Ajouter un enregistrement SRV + Supprimer un enregistrement SRV + {% endif %} + {% include "machines/aff_srv.html" with srv_list=srv_list %}


diff --git a/machines/templates/machines/machine.html b/machines/templates/machines/machine.html index 636c8e2f..796aac7b 100644 --- a/machines/templates/machines/machine.html +++ b/machines/templates/machines/machine.html @@ -57,6 +57,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if txtform %} {% bootstrap_form_errors txtform %} {% endif %} +{% if srvform %} +{% bootstrap_form_errors srvform %} +{% endif %} {% if aliasform %} {% bootstrap_form_errors aliasform %} {% endif %} @@ -116,6 +119,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,

Enregistrement TXT

{% bootstrap_form txtform %} {% endif %} + {% if srvform %} +

Enregistrement SRV

+ {% massive_bootstrap_form srvform 'target' %} + {% endif %} {% if aliasform %}

Alias

{% bootstrap_form aliasform %} diff --git a/machines/urls.py b/machines/urls.py index e9306605..c024cf56 100644 --- a/machines/urls.py +++ b/machines/urls.py @@ -56,6 +56,9 @@ urlpatterns = [ url(r'^add_ns/$', views.add_ns, name='add-ns'), url(r'^edit_ns/(?P[0-9]+)$', views.edit_ns, name='edit-ns'), url(r'^del_ns/$', views.del_ns, name='del-ns'), + url(r'^add_srv/$', views.add_srv, name='add-srv'), + url(r'^edit_srv/(?P[0-9]+)$', views.edit_srv, name='edit-srv'), + url(r'^del_srv/$', views.del_srv, name='del-srv'), url(r'^index_extension/$', views.index_extension, name='index-extension'), url(r'^add_alias/(?P[0-9]+)$', views.add_alias, name='add-alias'), url(r'^edit_alias/(?P[0-9]+)$', views.edit_alias, name='edit-alias'), @@ -81,6 +84,7 @@ urlpatterns = [ url(r'^history/(?Pmx)/(?P[0-9]+)$', views.history, name='history'), url(r'^history/(?Pns)/(?P[0-9]+)$', views.history, name='history'), url(r'^history/(?Ptxt)/(?P[0-9]+)$', views.history, name='history'), + url(r'^history/(?Psrv)/(?P[0-9]+)$', views.history, name='history'), url(r'^history/(?Piptype)/(?P[0-9]+)$', views.history, name='history'), url(r'^history/(?Palias)/(?P[0-9]+)$', views.history, name='history'), url(r'^history/(?Pvlan)/(?P[0-9]+)$', views.history, name='history'), @@ -95,6 +99,7 @@ urlpatterns = [ url(r'^rest/mx/$', views.mx, name='mx'), url(r'^rest/ns/$', views.ns, name='ns'), url(r'^rest/txt/$', views.txt, name='txt'), + url(r'^rest/srv/$', views.srv, name='srv'), url(r'^rest/zones/$', views.zones, name='zones'), url(r'^rest/service_servers/$', views.service_servers, name='service-servers'), url(r'^rest/ouverture_ports/$', views.ouverture_ports, name='ouverture-ports'), diff --git a/machines/views.py b/machines/views.py index 3508cba2..a59e493c 100644 --- a/machines/views.py +++ b/machines/views.py @@ -49,6 +49,7 @@ from machines.serializers import ( FullInterfaceSerializer, TypeSerializer, DomainSerializer, TxtSerializer, + SrvSerializer, MxSerializer, ExtensionSerializer, ServiceServersSerializer, @@ -91,7 +92,9 @@ from .forms import ( ServiceForm, DelServiceForm, NasForm, - DelNasForm + DelNasForm, + SrvForm, + DelSrvForm, ) from .forms import EditOuverturePortListForm, EditOuverturePortConfigForm from .models import ( @@ -110,8 +113,9 @@ from .models import ( Vlan, Nas, Txt, + Srv, OuverturePortList, - OuverturePort + OuverturePort, ) from users.models import User from preferences.models import GeneralOption, OptionalMachine @@ -752,6 +756,54 @@ def del_txt(request): return redirect(reverse('machines:index-extension')) return form({'txtform': txt}, 'machines/machine.html', request) +@login_required +@permission_required('infra') +def add_srv(request): + srv = SrvForm(request.POST or None) + if srv.is_valid(): + with transaction.atomic(), reversion.create_revision(): + srv.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + messages.success(request, "Cet enregistrement srv a été ajouté") + return redirect(reverse('machines:index-extension')) + return form({'srvform': srv}, 'machines/machine.html', request) + +@login_required +@permission_required('infra') +def edit_srv(request, srvid): + try: + srv_instance = Srv.objects.get(pk=srvid) + except Srv.DoesNotExist: + messages.error(request, u"Entrée inexistante" ) + return redirect(reverse('machines:index-extension')) + srv = SrvForm(request.POST or None, instance=srv_instance) + if srv.is_valid(): + with transaction.atomic(), reversion.create_revision(): + srv.save() + reversion.set_user(request.user) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in srv.changed_data)) + messages.success(request, "Srv modifié") + return redirect(reverse('machines:index-extension')) + return form({'srvform': srv}, 'machines/machine.html', request) + +@login_required +@permission_required('infra') +def del_srv(request): + srv = DelSrvForm(request.POST or None) + if srv.is_valid(): + srv_dels = srv.cleaned_data['srv'] + for srv_del in srv_dels: + try: + with transaction.atomic(), reversion.create_revision(): + srv_del.delete() + reversion.set_user(request.user) + messages.success(request, "L'srv a été supprimée") + except ProtectedError: + messages.error(request, "Erreur le Srv suivant %s ne peut être supprimé" % srv_del) + return redirect(reverse('machines:index-extension')) + return form({'srvform': srv}, 'machines/machine.html', request) + @login_required def add_alias(request, interfaceid): try: @@ -1046,7 +1098,8 @@ def index_extension(request): 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') txt_list = Txt.objects.all().select_related('zone') - return render(request, 'machines/index_extension.html', {'extension_list':extension_list, 'soa_list': soa_list, 'mx_list': mx_list, 'ns_list': ns_list, 'txt_list' : txt_list}) + srv_list = Srv.objects.all().select_related('extension').select_related('target__extension') + return render(request, 'machines/index_extension.html', {'extension_list':extension_list, 'soa_list': soa_list, 'mx_list': mx_list, 'ns_list': ns_list, 'txt_list' : txt_list, 'srv_list': srv_list}) @login_required def index_alias(request, interfaceid): @@ -1145,6 +1198,12 @@ def history(request, object, id): except Txt.DoesNotExist: messages.error(request, "Txt inexistant") return redirect(reverse('machines:index')) + elif object == 'srv' and request.user.has_perms(('cableur',)): + try: + object_instance = Srv.objects.get(pk=id) + except Srv.DoesNotExist: + messages.error(request, "Srv inexistant") + return redirect(reverse('machines:index')) elif object == 'ns' and request.user.has_perms(('cableur',)): try: object_instance = Ns.objects.get(pk=id) @@ -1343,6 +1402,14 @@ def txt(request): seria = TxtSerializer(txt, many=True) return JSONResponse(seria.data) +@csrf_exempt +@login_required +@permission_required('serveur') +def srv(request): + srv = Srv.objects.all().select_related('extension').select_related('target__extension') + seria = SrvSerializer(srv, many=True) + return JSONResponse(seria.data) + @csrf_exempt @login_required @permission_required('serveur')