diff --git a/api/serializers.py b/api/serializers.py index 6967dba8..58de859b 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -153,7 +153,7 @@ class VlanSerializer(NamespacedHMSerializer): """ class Meta: model = machines.Vlan - fields = ('vlan_id', 'name', 'comment', 'api_url') + fields = ('vlan_id', 'name', 'comment', 'arp_protect', 'dhcp_snooping', 'dhcpv6_snooping', 'api_url') class NasSerializer(NamespacedHMSerializer): @@ -303,6 +303,16 @@ class OuverturePortSerializer(NamespacedHMSerializer): fields = ('begin', 'end', 'port_list', 'protocole', 'io', 'api_url') +class RoleSerializer(NamespacedHMSerializer): + """Serialize `machines.models.OuverturePort` objects. + """ + servers = InterfaceSerializer(read_only=True, many=True) + + class Meta: + model = machines.Role + fields = ('role_type', 'servers', 'api_url') + + # PREFERENCES @@ -634,10 +644,38 @@ class ServiceRegenSerializer(NamespacedHMSerializer): # Switches et ports +class InterfaceVlanSerializer(NamespacedHMSerializer): + domain = serializers.CharField(read_only=True) + ipv4 = serializers.CharField(read_only=True) + ipv6 = Ipv6ListSerializer(read_only=True, many=True) + vlan_id = serializers.IntegerField(source='type.ip_type.vlan.vlan_id', read_only=True) + + class Meta: + model = machines.Interface + fields = ('ipv4', 'ipv6', 'domain', 'vlan_id') + +class InterfaceRoleSerializer(NamespacedHMSerializer): + interface = InterfaceVlanSerializer(source='machine.interface_set', read_only=True, many=True) + + class Meta: + model = machines.Interface + fields = ('interface',) + + +class RoleSerializer(NamespacedHMSerializer): + """Serialize `machines.models.OuverturePort` objects. + """ + servers = InterfaceRoleSerializer(read_only=True, many=True) + + class Meta: + model = machines.Role + fields = ('role_type', 'servers') + + class VlanPortSerializer(NamespacedHMSerializer): class Meta: model = machines.Vlan - fields = ('vlan_id', 'name') + fields = ('vlan_id', 'name') class ProfilSerializer(NamespacedHMSerializer): @@ -669,7 +707,7 @@ class PortsSerializer(NamespacedHMSerializer): class Meta: model = topologie.Port - fields = ('state', 'port', 'get_port_profil') + fields = ('state', 'port', 'pretty_name', 'get_port_profil') @@ -682,7 +720,7 @@ class SwitchPortSerializer(serializers.ModelSerializer): class Meta: model = topologie.Switch - fields = ('short_name', 'model', 'switchbay', 'ports', 'subnet', 'subnet6') + fields = ('short_name', 'model', 'switchbay', 'ports', 'ipv4', 'ipv6', 'subnet', 'subnet6') # DHCP diff --git a/api/urls.py b/api/urls.py index a707ab57..83b1108c 100644 --- a/api/urls.py +++ b/api/urls.py @@ -62,6 +62,7 @@ router.register_viewset(r'machines/service', views.ServiceViewSet) router.register_viewset(r'machines/servicelink', views.ServiceLinkViewSet, base_name='servicelink') router.register_viewset(r'machines/ouvertureportlist', views.OuverturePortListViewSet) router.register_viewset(r'machines/ouvertureport', views.OuverturePortViewSet) +router.register_viewset(r'machines/role', views.RoleViewSet) # PREFERENCES router.register_view(r'preferences/optionaluser', views.OptionalUserView), router.register_view(r'preferences/optionalmachine', views.OptionalMachineView), @@ -100,6 +101,7 @@ router.register_viewset(r'services/regen', views.ServiceRegenViewSet, base_name= router.register_view(r'dhcp/hostmacip', views.HostMacIpView), # Switches config router.register_view(r'switchs/ports-config', views.SwitchPortView), +router.register_view(r'switchs/role', views.RoleView), # Reminder router.register_view(r'reminder/get-users', views.ReminderView), # DNS diff --git a/api/views.py b/api/views.py index ea384ad9..03ebe349 100644 --- a/api/views.py +++ b/api/views.py @@ -234,6 +234,13 @@ class OuverturePortViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = serializers.OuverturePortSerializer +class RoleViewSet(viewsets.ReadOnlyModelViewSet): + """Exposes list and details of `machines.models.Machine` objects. + """ + queryset = machines.Role.objects.all() + serializer_class = serializers.RoleSerializer + + # PREFERENCES # Those views differ a bit because there is only one object # to display, so we don't bother with the listing part @@ -504,6 +511,15 @@ class SwitchPortView(generics.ListAPIView): queryset = topologie.Switch.objects.all().prefetch_related('ports__custom_profile') serializer_class = serializers.SwitchPortSerializer + +class RoleView(generics.ListAPIView): + """Exposes the associations between hostname, mac address and IPv4 in + order to build the DHCP lease files. + """ + queryset = machines.Role.objects.all().prefetch_related('servers') + serializer_class = serializers.RoleSerializer + + # Rappel fin adhésion class ReminderView(generics.ListAPIView): diff --git a/machines/migrations/0091_auto_20180707_2040.py b/machines/migrations/0091_auto_20180707_2040.py new file mode 100644 index 00000000..a2aea3a6 --- /dev/null +++ b/machines/migrations/0091_auto_20180707_2040.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-07-07 18:40 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0090_auto_20180625_1706'), + ] + + operations = [ + migrations.AddField( + model_name='vlan', + name='arp_protect', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='vlan', + name='dhcp_snooping', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='vlan', + name='dhcpv6_snooping', + field=models.BooleanField(default=False), + ), + ] diff --git a/machines/models.py b/machines/models.py index 5113c34c..7e5062c0 100644 --- a/machines/models.py +++ b/machines/models.py @@ -387,6 +387,8 @@ class IpType(RevMixin, AclMixin, models.Model): 'network': str(ip_set.network), 'netmask': str(ip_set.netmask), 'broadcast': str(ip_set.broadcast), + 'vlan': str(self.vlan), + 'vlan_id': self.vlan.vlan_id } for ip_set in self.ip_set.iter_cidrs() ] @@ -395,7 +397,9 @@ class IpType(RevMixin, AclMixin, models.Model): if self.prefix_v6: return { 'network' : str(self.prefix_v6), - 'netmask' : 'ffff:ffff:ffff:ffff::' + 'netmask' : 'ffff:ffff:ffff:ffff::', + 'vlan': str(self.vlan), + 'vlan_id': str(self.vlan.vlan_id) } else: return None @@ -494,7 +498,11 @@ class Vlan(RevMixin, AclMixin, models.Model): vlan_id = models.PositiveIntegerField(validators=[MaxValueValidator(4095)]) name = models.CharField(max_length=256) comment = models.CharField(max_length=256, blank=True) - + #Réglages supplémentaires + arp_protect = models.BooleanField(default=False) + dhcp_snooping = models.BooleanField(default=False) + dhcpv6_snooping = models.BooleanField(default=False) + class Meta: permissions = ( ("view_vlan", "Peut voir un objet vlan"), diff --git a/topologie/models.py b/topologie/models.py index 700b6c16..27952a4e 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -271,6 +271,14 @@ class Switch(AclMixin, Machine): """ Returns the 'main' interface of the switch """ return self.interface_set.first() + @cached_property + def ipv4(self): + return str(self.main_interface().ipv4) + + @cached_property + def ipv6(self): + return str(self.main_interface().ipv6().first()) + @cached_property def subnet(self): return self.main_interface().type.ip_type.ip_set_full_info @@ -414,6 +422,18 @@ class Port(AclMixin, RevMixin, models.Model): ("view_port", "Peut voir un objet port"), ) + @cached_property + def pretty_name(self): + """More elaborated name for label on switch conf""" + if self.related: + return "Uplink : " + self.related.switch.short_name + elif self.machine_interface: + return "Machine : " + str(self.machine_interface.domain) + elif self.room: + return "Chambre : " + str(self.room) + else: + return "Inconnue" + @cached_property def get_port_profil(self): """Return the config profil for this port diff --git a/topologie/templates/topologie/aff_vlanoptions.html b/topologie/templates/topologie/aff_vlanoptions.html new file mode 100644 index 00000000..d9e6f117 --- /dev/null +++ b/topologie/templates/topologie/aff_vlanoptions.html @@ -0,0 +1,55 @@ +{% 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 %} + +{% load acl %} + +
+ + + + + + + + + + + + {% for vlan in vlan_list %} + + + + + + + + + {% endfor %} +
IdNomArp ProtectDhcp SnoopingDhcpv6 Snooping
{{ vlan.vlan_id }}{{ vlan.name }}{{ vlan.arp_protect }}{{ vlan.dhcp_snooping }}{{ vlan.dhcpv6_snooping }} + {% can_edit vlan %} + {% include 'buttons/edit.html' with href='topologie:edit-vlanoptions' id=vlan.id %} + {% acl_end %} + {% include 'buttons/history.html' with href='machines:history' name='vlan' id=vlan.id %} +
+
diff --git a/topologie/templates/topologie/index_portprofile.html b/topologie/templates/topologie/index_portprofile.html index f95415c8..53028577 100644 --- a/topologie/templates/topologie/index_portprofile.html +++ b/topologie/templates/topologie/index_portprofile.html @@ -30,12 +30,18 @@ with this program; if not, write to the Free Software Foundation, Inc., {% block content %} +

{% trans "Port profiles" %}

{% can_create PortProfile %} {% trans " Add a port profile" %}
{% acl_end %} {% include "topologie/aff_port_profile.html" with port_profile_list=port_profile_list %} + + +

{% trans "Sécurité par vlan" %}

+{% include "topologie/aff_vlanoptions.html" with vlan_list=vlan_list %} +


diff --git a/topologie/urls.py b/topologie/urls.py index a827acf2..7b27d42a 100644 --- a/topologie/urls.py +++ b/topologie/urls.py @@ -125,4 +125,7 @@ urlpatterns = [ url(r'^del_port_profile/(?P[0-9]+)$', views.del_port_profile, name='del-port-profile'), -] + url(r'^edit_vlanoptions/(?P[0-9]+)$', + views.edit_vlanoptions, + name='edit-vlanoptions'), + ] diff --git a/topologie/views.py b/topologie/views.py index e5453982..b11c983f 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -64,10 +64,15 @@ from re2o.settings import MEDIA_ROOT from machines.forms import ( DomainForm, EditInterfaceForm, - AddInterfaceForm + AddInterfaceForm, + EditOptionVlanForm ) from machines.views import generate_ipv4_mbf_param -from machines.models import Interface, Service_link +from machines.models import ( + Interface, + Service_link, + Vlan +) from preferences.models import AssoOption, GeneralOption from .models import ( @@ -152,10 +157,11 @@ def index_port_profile(request): pagination_number = GeneralOption.get_cached_value('pagination_number') port_profile_list = PortProfile.objects.all().select_related('vlan_untagged') port_profile_list = re2o_paginator(request, port_profile_list, pagination_number) + vlan_list = Vlan.objects.all().order_by('vlan_id') return render( request, 'topologie/index_portprofile.html', - {'port_profile_list': port_profile_list} + {'port_profile_list': port_profile_list, 'vlan_list': vlan_list} ) @@ -306,6 +312,23 @@ def index_model_switch(request): ) +@login_required +@can_edit(Vlan) +def edit_vlanoptions(request, vlan_instance, **_kwargs): + """ View used to edit options for switch of VLAN object """ + vlan = EditOptionVlanForm(request.POST or None, instance=vlan_instance) + if vlan.is_valid(): + if vlan.changed_data: + vlan.save() + messages.success(request, "Vlan modifié") + return redirect(reverse('topologie:index-port-profile')) + return form( + {'vlanform': vlan, 'action_name': 'Editer'}, + 'machines/machine.html', + request + ) + + @login_required @can_create(Port) def new_port(request, switchid):