diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index 8450777b..d743495f 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -355,7 +355,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, port=port_number ) .first()) - + # Si le port est inconnu, on place sur le vlan defaut # Aucune information particulière ne permet de déterminer quelle # politique à appliquer sur ce port @@ -363,12 +363,12 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, return (sw_name, "Chambre inconnue", u'Port inconnu', VLAN_OK) # On récupère le profil du port - port_profil = port.get_port_profil + port_profile = port.get_port_profile - # Si un vlan a été précisé dans la config du port, + # Si un vlan a été précisé dans la config du port, # on l'utilise pour VLAN_OK - if port_profil.vlan_untagged: - DECISION_VLAN = int(port_profil.vlan_untagged.vlan_id) + if port_profile.vlan_untagged: + DECISION_VLAN = int(port_profile.vlan_untagged.vlan_id) extra_log = u"Force sur vlan " + str(DECISION_VLAN) else: DECISION_VLAN = VLAN_OK @@ -378,7 +378,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, return (sw_name, port.room, u'Port desactivé', VLAN_NOK) # Si radius est désactivé, on laisse passer - if port_profil.radius_type == 'NO': + if port_profile.radius_type == 'NO': return (sw_name, "", u"Pas d'authentification sur ce port" + extra_log, @@ -386,7 +386,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, # Si le 802.1X est activé sur ce port, cela veut dire que la personne a été accept précédemment # Par conséquent, on laisse passer sur le bon vlan - if nas_type.port_access_mode == '802.1X' and port_profil.radius_type == '802.1X': + if nas_type.port_access_mode == '802.1X' and port_profile.radius_type == '802.1X': room = port.room or "Chambre/local inconnu" return (sw_name, room, u'Acceptation authentification 802.1X', DECISION_VLAN) @@ -395,7 +395,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, # rattachés à ce port sont bien à jour de cotisation. Sinon on rejette (anti squattage) # Il n'est pas possible de se connecter sur une prise strict sans adhérent à jour de cotis # dedans - if port_profil.radius_mode == 'STRICT': + if port_profile.radius_mode == 'STRICT': room = port.room if not room: return (sw_name, "Inconnue", u'Chambre inconnue', VLAN_NOK) @@ -411,7 +411,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number, # else: user OK, on passe à la verif MAC # Si on fait de l'auth par mac, on cherche l'interface via sa mac dans la bdd - if port_profil.radius_mode == 'COMMON' or port_profil.radius_mode == 'STRICT': + if port_profile.radius_mode == 'COMMON' or port_profile.radius_mode == 'STRICT': # Authentification par mac interface = (Interface.objects .filter(mac_address=mac_address) diff --git a/topologie/models.py b/topologie/models.py index 029aebc8..a0333d46 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -40,7 +40,7 @@ from __future__ import unicode_literals import itertools from django.db import models -from django.db.models.signals import pre_save, post_save, post_delete +from django.db.models.signals import post_save, post_delete from django.utils.functional import cached_property from django.dispatch import receiver from django.core.exceptions import ValidationError @@ -52,11 +52,6 @@ from reversion import revisions as reversion from machines.models import Machine, regen from re2o.mixins import AclMixin, RevMixin -from os.path import isfile -from os import remove - - - class Stack(AclMixin, RevMixin, models.Model): """Un objet stack. Regrouppe des switchs en foreign key @@ -123,7 +118,10 @@ class AccessPoint(AclMixin, Machine): ) def building(self): - """Return the building of the AP/Server (building of the switchs connected to...)""" + """ + Return the building of the AP/Server (building of the switchs + connected to...) + """ return Building.objects.filter( switchbay__switch=self.switch() ) @@ -135,14 +133,18 @@ class AccessPoint(AclMixin, Machine): @classmethod def all_ap_in(cls, building_instance): """Get a building as argument, returns all ap of a building""" - return cls.objects.filter(interface__port__switch__switchbay__building=building_instance) + return cls.objects.filter( + interface__port__switch__switchbay__building=building_instance + ) def __str__(self): return str(self.interface_set.first()) class Server(Machine): - """Dummy class, to retrieve servers of a building, or get switch of a server""" + """ + Dummy class, to retrieve servers of a building, or get switch of a server + """ class Meta: proxy = True @@ -160,7 +162,10 @@ class Server(Machine): ) def building(self): - """Return the building of the AP/Server (building of the switchs connected to...)""" + """ + Return the building of the AP/Server + (building of the switchs connected to...) + """ return Building.objects.filter( switchbay__switch=self.switch() ) @@ -172,7 +177,9 @@ class Server(Machine): @classmethod def all_server_in(cls, building_instance): """Get a building as argument, returns all server of a building""" - return cls.objects.filter(interface__port__switch__switchbay__building=building_instance).exclude(accesspoint__isnull=False) + return cls.objects.filter( + interface__port__switch__switchbay__building=building_instance + ).exclude(accesspoint__isnull=False) def __str__(self): return str(self.interface_set.first()) @@ -200,7 +207,7 @@ class Switch(AclMixin, Machine): blank=True, null=True, on_delete=models.SET_NULL - ) + ) stack_member_id = models.PositiveIntegerField( blank=True, null=True, @@ -238,7 +245,7 @@ class Switch(AclMixin, Machine): raise ValidationError( {'stack_member_id': "L'id de ce switch est en\ dehors des bornes permises pas la stack"} - ) + ) else: raise ValidationError({'stack_member_id': "L'id dans la stack\ ne peut être nul"}) @@ -382,25 +389,25 @@ class Port(AclMixin, RevMixin, models.Model): on_delete=models.PROTECT, blank=True, null=True - ) + ) machine_interface = models.ForeignKey( 'machines.Interface', on_delete=models.SET_NULL, blank=True, null=True - ) + ) related = models.OneToOneField( 'self', null=True, blank=True, related_name='related_port' - ) + ) custom_profile = models.ForeignKey( 'PortProfile', on_delete=models.PROTECT, blank=True, null=True - ) + ) state = models.BooleanField( default=True, help_text='Port state Active', @@ -415,32 +422,35 @@ class Port(AclMixin, RevMixin, models.Model): ) @cached_property - def get_port_profil(self): - """Return the config profil for this port + def get_port_profile(self): + """Return the config profile for this port :returns: the profile of self (port)""" - def profil_or_nothing(profil): - port_profil = PortProfile.objects.filter(profil_default=profil).first() - if port_profil: - return port_profil + def profile_or_nothing(profile): + port_profile = PortProfile.objects.filter( + profile_default=profile).first() + if port_profile: + return port_profile else: - nothing = PortProfile.objects.filter(profil_default='nothing').first() - if not nothing: - nothing = PortProfile.objects.create(profil_default='nothing', name='nothing', radius_type='NO') - return nothing + nothing_profile, _created = PortProfile.objects.get_or_create( + profile_default='nothing', + name='nothing', + radius_type='NO' + ) + return nothing_profile if self.custom_profile: return self.custom_profile elif self.related: - return profil_or_nothing('uplink') + return profile_or_nothing('uplink') elif self.machine_interface: if hasattr(self.machine_interface.machine, 'accesspoint'): - return profil_or_nothing('access_point') + return profile_or_nothing('access_point') else: - return profil_or_nothing('asso_machine') + return profile_or_nothing('asso_machine') elif self.room: - return profil_or_nothing('room') + return profile_or_nothing('room') else: - return profil_or_nothing('nothing') + return profile_or_nothing('nothing') @classmethod def get_instance(cls, portid, *_args, **kwargs): @@ -525,11 +535,11 @@ class PortProfile(AclMixin, RevMixin, models.Model): ('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius'), - ) + ) MODES = ( ('STRICT', 'STRICT'), ('COMMON', 'COMMON'), - ) + ) SPEED = ( ('10-half', '10-half'), ('100-half', '100-half'), @@ -539,14 +549,14 @@ class PortProfile(AclMixin, RevMixin, models.Model): ('auto', 'auto'), ('auto-10', 'auto-10'), ('auto-100', 'auto-100'), - ) - PROFIL_DEFAULT= ( + ) + PROFIL_DEFAULT = ( ('room', 'room'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine'), ('nothing', 'nothing'), - ) + ) name = models.CharField(max_length=255, verbose_name=_("Name")) profil_default = models.CharField( max_length=32, @@ -620,25 +630,36 @@ class PortProfile(AclMixin, RevMixin, models.Model): default=False, help_text='Protect against rogue ra', verbose_name=_("Ra guard") - ) + ) loop_protect = models.BooleanField( default=False, help_text='Protect again loop', verbose_name=_("Loop Protect") - ) + ) class Meta: permissions = ( - ("view_port_profile", _("Can view a port profile object")), + ("view_port_profile", _("Can view a port profile object")), ) verbose_name = _("Port profile") verbose_name_plural = _("Port profiles") - security_parameters_fields = ['loop_protect', 'ra_guard', 'arp_protect', 'dhcpv6_snooping', 'dhcp_snooping', 'flow_control'] + security_parameters_fields = [ + 'loop_protect', + 'ra_guard', + 'arp_protect', + 'dhcpv6_snooping', + 'dhcp_snooping', + 'flow_control' + ] @cached_property def security_parameters_enabled(self): - return [parameter for parameter in self.security_parameters_fields if getattr(self, parameter)] + return [ + parameter + for parameter in self.security_parameters_fields + if getattr(self, parameter) + ] @cached_property def security_parameters_as_str(self): @@ -654,45 +675,55 @@ def ap_post_save(**_kwargs): regen('unifi-ap-names') regen("graph_topo") + @receiver(post_delete, sender=AccessPoint) def ap_post_delete(**_kwargs): """Regeneration des noms des bornes vers le controleur""" regen('unifi-ap-names') regen("graph_topo") + @receiver(post_delete, sender=Stack) def stack_post_delete(**_kwargs): """Vide les id des switches membres d'une stack supprimée""" Switch.objects.filter(stack=None).update(stack_member_id=None) + @receiver(post_save, sender=Port) def port_post_save(**_kwargs): regen("graph_topo") + @receiver(post_delete, sender=Port) def port_post_delete(**_kwargs): regen("graph_topo") + @receiver(post_save, sender=ModelSwitch) def modelswitch_post_save(**_kwargs): regen("graph_topo") + @receiver(post_delete, sender=ModelSwitch) def modelswitch_post_delete(**_kwargs): regen("graph_topo") + @receiver(post_save, sender=Building) def building_post_save(**_kwargs): regen("graph_topo") + @receiver(post_delete, sender=Building) def building_post_delete(**_kwargs): regen("graph_topo") + @receiver(post_save, sender=Switch) def switch_post_save(**_kwargs): regen("graph_topo") + @receiver(post_delete, sender=Switch) def switch_post_delete(**_kwargs): regen("graph_topo") diff --git a/topologie/templates/topologie/aff_port_profile.html b/topologie/templates/topologie/aff_port_profile.html index 12c8b365..65446236 100644 --- a/topologie/templates/topologie/aff_port_profile.html +++ b/topologie/templates/topologie/aff_port_profile.html @@ -29,9 +29,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% include "pagination.html" with list=port_profile_list %} {% endif %} - + @@ -49,7 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% trans "Name" %} {% trans "Default for" %}{{port_profile.profil_default}} {% if port_profile.vlan_untagged %} - Untagged : {{port_profile.vlan_untagged}} + Untagged : {{port_profile.vlan_untagged}}
{% endif %} {% if port_profile.vlan_tagged.all %} diff --git a/topologie/views.py b/topologie/views.py index e5453982..a6c6ab69 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -42,11 +42,7 @@ from django.contrib.auth.decorators import login_required from django.db import IntegrityError from django.db.models import ProtectedError, Prefetch from django.core.exceptions import ValidationError -from django.contrib.staticfiles.storage import staticfiles_storage -from django.template.loader import get_template from django.template import Context, Template, loader -from django.db.models.signals import post_save -from django.dispatch import receiver from django.utils.translation import ugettext as _ import tempfile @@ -105,8 +101,7 @@ from subprocess import ( PIPE ) -from os.path import isfile -from os import remove +from os.path import isfile @login_required @@ -128,13 +123,17 @@ def index(request): SortTable.TOPOLOGIE_INDEX ) - pagination_number = GeneralOption.get_cached_value('pagination_number') switch_list = re2o_paginator(request, switch_list, pagination_number) - if any(service_link.need_regen for service_link in Service_link.objects.filter(service__service_type='graph_topo')): + if any( + service_link.need_regen + for service_link in Service_link.objects.filter( + service__service_type='graph_topo') + ): make_machine_graph() - for service_link in Service_link.objects.filter(service__service_type='graph_topo'): + for service_link in Service_link.objects.filter( + service__service_type='graph_topo'): service_link.done_regen() if not isfile("/var/www/re2o/media/images/switchs.png"): @@ -150,8 +149,10 @@ def index(request): @can_view_all(PortProfile) 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) + port_profile_list = PortProfile.objects.all().select_related( + 'vlan_untagged') + port_profile_list = re2o_paginator( + request, port_profile_list, pagination_number) return render( request, 'topologie/index_portprofile.html', @@ -460,7 +461,7 @@ def new_switch(request): ) domain = DomainForm( request.POST or None, - ) + ) if switch.is_valid() and interface.is_valid(): user = AssoOption.get_cached_value('utilisateur_asso') if not user: @@ -530,7 +531,7 @@ def create_ports(request, switchid): return redirect(reverse( 'topologie:index-port', kwargs={'switchid': switchid} - )) + )) return form( {'id_switch': switchid, 'topoform': port_form}, 'topologie/switch.html', @@ -548,16 +549,16 @@ def edit_switch(request, switch, switchid): request.POST or None, instance=switch, user=request.user - ) + ) interface_form = EditInterfaceForm( request.POST or None, instance=switch.interface_set.first(), user=request.user - ) + ) domain_form = DomainForm( request.POST or None, instance=switch.interface_set.first().domain - ) + ) if switch_form.is_valid() and interface_form.is_valid(): new_switch_obj = switch_form.save(commit=False) new_interface_obj = interface_form.save(commit=False) @@ -601,7 +602,7 @@ def new_ap(request): ) domain = DomainForm( request.POST or None, - ) + ) if ap.is_valid() and interface.is_valid(): user = AssoOption.get_cached_value('utilisateur_asso') if not user: @@ -656,7 +657,7 @@ def edit_ap(request, ap, **_kwargs): domain_form = DomainForm( request.POST or None, instance=ap.interface_set.first().domain - ) + ) if ap_form.is_valid() and interface_form.is_valid(): user = AssoOption.get_cached_value('utilisateur_asso') if not user: @@ -970,7 +971,7 @@ def del_constructor_switch(request, constructor_switch, **_kwargs): return form({ 'objet': constructor_switch, 'objet_name': 'Constructeur de switch' - }, 'topologie/delete.html', request) + }, 'topologie/delete.html', request) @login_required @@ -993,7 +994,8 @@ def new_port_profile(request): @can_edit(PortProfile) def edit_port_profile(request, port_profile, **_kwargs): """Edit a port profile""" - port_profile = EditPortProfileForm(request.POST or None, instance=port_profile) + port_profile = EditPortProfileForm( + request.POST or None, instance=port_profile) if port_profile.is_valid(): if port_profile.changed_data: port_profile.save() @@ -1006,7 +1008,6 @@ def edit_port_profile(request, port_profile, **_kwargs): ) - @login_required @can_delete(PortProfile) def del_port_profile(request, port_profile, **_kwargs): @@ -1014,25 +1015,26 @@ def del_port_profile(request, port_profile, **_kwargs): if request.method == 'POST': try: port_profile.delete() - messages.success(request, - _("The port profile was successfully deleted")) + messages.success(request, + _("The port profile was successfully deleted")) except ProtectedError: - messages.success(request, - _("Impossible to delete the port profile")) + messages.success(request, + _("Impossible to delete the port profile")) return redirect(reverse('topologie:index')) return form( - {'objet': port_profile, 'objet_name': _("Port profile")}, - 'topologie/delete.html', - request + {'objet': port_profile, 'objet_name': _("Port profile")}, + 'topologie/delete.html', + request ) + def make_machine_graph(): """ Create the graph of switchs, machines and access points. """ dico = { 'subs': [], - 'links' : [], + 'links': [], 'alone': [], 'colors': { 'head': "#7f0505", # Color parameters for the graph @@ -1041,23 +1043,23 @@ def make_machine_graph(): 'border_bornes': "#02078e", 'head_bornes': "#25771c", 'head_server': "#1c3777" - } } + } missing = list(Switch.objects.all()) detected = [] for building in Building.objects.all(): # Visit all buildings dico['subs'].append( { - 'bat_id': building.id, - 'bat_name': building, - 'switchs': [], - 'bornes': [], - 'machines': [] + 'bat_id': building.id, + 'bat_name': building, + 'switchs': [], + 'bornes': [], + 'machines': [] } ) # Visit all switchs in this building - for switch in Switch.objects.filter(switchbay__building=building): + for switch in Switch.objects.filter(switchbay__building=building): dico['subs'][-1]['switchs'].append({ 'name': switch.main_interface().domain.name, 'nombre': switch.number, @@ -1067,7 +1069,7 @@ def make_machine_graph(): 'ports': [] }) # visit all ports of this switch and add the switchs linked to it - for port in switch.ports.filter(related__isnull=False): + for port in switch.ports.filter(related__isnull=False): dico['subs'][-1]['switchs'][-1]['ports'].append({ 'numero': port.port, 'related': port.related.switch.main_interface().domain.name @@ -1085,50 +1087,58 @@ def make_machine_graph(): dico['subs'][-1]['machines'].append({ 'name': server.short_name, 'switch': server.switch()[0].main_interface().domain.name, - 'port': Port.objects.filter(machine_interface__machine=server)[0].port + 'port': Port.objects.filter( + machine_interface__machine=server + )[0].port }) # While the list of forgotten ones is not empty while missing: if missing[0].ports.count(): # The switch is not empty - links, new_detected = recursive_switchs(missing[0], None, [missing[0]]) + links, new_detected = recursive_switchs( + missing[0], None, [missing[0]]) for link in links: dico['links'].append(link) # Update the lists of missings and already detected switchs - missing=[i for i in missing if i not in new_detected] + missing = [i for i in missing if i not in new_detected] detected += new_detected - else: # If the switch have no ports, don't explore it and hop to the next one + # If the switch have no ports, don't explore it and hop to the next one + else: del missing[0] # Switchs that are not connected or not in a building - for switch in Switch.objects.filter(switchbay__isnull=True).exclude(ports__related__isnull=False): + for switch in Switch.objects.filter( + switchbay__isnull=True).exclude(ports__related__isnull=False): dico['alone'].append({ 'id': switch.id, 'name': switch.main_interface().domain.name - }) + }) + # generate the dot file + dot_data = generate_dot(dico, 'topologie/graph_switch.dot') - dot_data=generate_dot(dico,'topologie/graph_switch.dot') # generate the dot file - - f = tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False) # Create a temporary file to store the dot data + # Create a temporary file to store the dot data + f = tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False) with f: - f.write(dot_data) + f.write(dot_data) unflatten = Popen( # unflatten the graph to make it look better - ["unflatten","-l", "3", f.name], + ["unflatten", "-l", "3", f.name], stdout=PIPE ) - image = Popen( # pipe the result of the first command into the second + Popen( # pipe the result of the first command into the second ["dot", "-Tpng", "-o", MEDIA_ROOT + "/images/switchs.png"], stdin=unflatten.stdout, stdout=PIPE ) -def generate_dot(data,template): + +def generate_dot(data, template): """create the dot file :param data: dictionary passed to the template :param template: path to the dot template :return: all the lines of the dot file""" t = loader.get_template(template) - if not isinstance(t, Template) and not (hasattr(t, 'template') and isinstance(t.template, Template)): + if not isinstance(t, Template) and \ + not (hasattr(t, 'template') and isinstance(t.template, Template)): raise Exception("Le template par défaut de Django n'est pas utilisé." "Cela peut mener à des erreurs de rendu." "Vérifiez les paramètres") @@ -1136,27 +1146,40 @@ def generate_dot(data,template): dot = t.render(c) return(dot) + def recursive_switchs(switch_start, switch_before, detected): """Visit the switch and travel to the switchs linked to it. :param switch_start: the switch to begin the visit on - :param switch_before: the switch that you come from. None if switch_start is the first one - :param detected: list of all switchs already visited. None if switch_start is the first one - :return: A list of all the links found and a list of all the switchs visited""" + :param switch_before: the switch that you come from. + None if switch_start is the first one + :param detected: list of all switchs already visited. + None if switch_start is the first one + :return: A list of all the links found and a list of + all the switchs visited + """ detected.append(switch_start) - links_return=[] # list of dictionaries of the links to be detected - for port in switch_start.ports.filter(related__isnull=False): # create links to every switchs below - if port.related.switch != switch_before and port.related.switch != port.switch and port.related.switch not in detected: # Not the switch that we come from, not the current switch + links_return = [] # list of dictionaries of the links to be detected + # create links to every switchs below + for port in switch_start.ports.filter(related__isnull=False): + # Not the switch that we come from, not the current switch + if port.related.switch != switch_before \ + and port.related.switch != port.switch \ + and port.related.switch not in detected: links = { # Dictionary of a link - 'depart':switch_start.id, - 'arrive':port.related.switch.id + 'depart': switch_start.id, + 'arrive': port.related.switch.id } links_return.append(links) # Add current and below levels links - for port in switch_start.ports.filter(related__isnull=False): # go down on every related switchs - if port.related.switch not in detected: # The switch at the end of this link has not been visited - links_down, detected = recursive_switchs(port.related.switch, switch_start, detected) # explore it and get the results - for link in links_down: # Add the non empty links to the current list + # go down on every related switchs + for port in switch_start.ports.filter(related__isnull=False): + # The switch at the end of this link has not been visited + if port.related.switch not in detected: + # explore it and get the results + links_down, detected = recursive_switchs( + port.related.switch, switch_start, detected) + # Add the non empty links to the current list + for link in links_down: if link: - links_return.append(link) + links_return.append(link) return (links_return, detected) -