diff --git a/cotisations/migrations/0024_auto_20171015_2033.py b/cotisations/migrations/0024_auto_20171015_2033.py new file mode 100644 index 00000000..b52dad62 --- /dev/null +++ b/cotisations/migrations/0024_auto_20171015_2033.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-10-15 18:33 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cotisations', '0023_auto_20170902_1303'), + ] + + operations = [ + migrations.AlterField( + model_name='article', + name='duration', + field=models.PositiveIntegerField(blank=True, help_text='Durée exprimée en mois entiers', null=True, validators=[django.core.validators.MinValueValidator(0)]), + ), + migrations.AlterField( + model_name='vente', + name='duration', + field=models.PositiveIntegerField(blank=True, help_text='Durée exprimée en mois entiers', null=True), + ), + ] diff --git a/cotisations/models.py b/cotisations/models.py index 54843076..0b3aef35 100644 --- a/cotisations/models.py +++ b/cotisations/models.py @@ -132,7 +132,7 @@ class Vente(models.Model): name = models.CharField(max_length=255) prix = models.DecimalField(max_digits=5, decimal_places=2) iscotisation = models.BooleanField() - duration = models.IntegerField( + duration = models.PositiveIntegerField( help_text="Durée exprimée en mois entiers", blank=True, null=True) @@ -222,7 +222,7 @@ class Article(models.Model): name = models.CharField(max_length=255, unique=True) prix = models.DecimalField(max_digits=5, decimal_places=2) iscotisation = models.BooleanField() - duration = models.IntegerField( + duration = models.PositiveIntegerField( help_text="Durée exprimée en mois entiers", blank=True, null=True, diff --git a/cotisations/urls.py b/cotisations/urls.py index f59fd678..d3e56f36 100644 --- a/cotisations/urls.py +++ b/cotisations/urls.py @@ -99,18 +99,18 @@ urlpatterns = [ views.index_paiement, name='index-paiement' ), - url(r'^history/(?Pfacture)/(?P[0-9]+)$', + url(r'^history/(?Pfacture)/(?P[0-9]+)$', views.history, name='history' ), - url(r'^history/(?Particle)/(?P[0-9]+)$', + url(r'^history/(?Particle)/(?P[0-9]+)$', views.history, name='history' ), - url(r'^history/(?Ppaiement)/(?P[0-9]+)$', + url(r'^history/(?Ppaiement)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Pbanque)/(?P[0-9]+)$', + url(r'^history/(?Pbanque)/(?P[0-9]+)$', views.history, name='history' ), diff --git a/cotisations/views.py b/cotisations/views.py index e44eee65..4e2c5304 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -533,7 +533,8 @@ def control(request): facture_list = paginator.page(1) except EmptyPage: facture_list = paginator.page(paginator.num.pages) - page_query = Facture.objects.order_by('date').reverse().filter( + page_query = Facture.objects.order_by('date').select_related('user')\ + .select_related('paiement').reverse().filter( id__in=[facture.id for facture in facture_list] ) controlform = controlform_set(request.POST or None, queryset=page_query) @@ -603,9 +604,9 @@ def index(request): @login_required -def history(request, object, object_id): +def history(request, object_name, object_id): """Affiche l'historique de chaque objet""" - if object == 'facture': + if object_name == 'facture': try: object_instance = Facture.objects.get(pk=object_id) except Facture.DoesNotExist: @@ -616,19 +617,19 @@ def history(request, object, object_id): messages.error(request, "Vous ne pouvez pas afficher l'historique\ d'une facture d'un autre user que vous sans droit cableur") return redirect("/users/profil/" + str(request.user.id)) - elif object == 'paiement' and request.user.has_perms(('cableur',)): + elif object_name == 'paiement' and request.user.has_perms(('cableur',)): try: object_instance = Paiement.objects.get(pk=object_id) except Paiement.DoesNotExist: messages.error(request, "Paiement inexistant") return redirect("/cotisations/") - elif object == 'article' and request.user.has_perms(('cableur',)): + elif object_name == 'article' and request.user.has_perms(('cableur',)): try: object_instance = Article.objects.get(pk=object_id) except Article.DoesNotExist: messages.error(request, "Article inexistante") return redirect("/cotisations/") - elif object == 'banque' and request.user.has_perms(('cableur',)): + elif object_name == 'banque' and request.user.has_perms(('cableur',)): try: object_instance = Banque.objects.get(pk=object_id) except Banque.DoesNotExist: diff --git a/docs_utils/re2o-archi.dia b/docs_utils/re2o-archi.dia new file mode 100644 index 00000000..1137480e Binary files /dev/null and b/docs_utils/re2o-archi.dia differ diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index e3e272c9..2d1ebfe2 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -292,7 +292,7 @@ def decide_vlan_and_register_switch(nas, nas_type, port_number, mac_address): if not port.room: return (sw_name, u'Chambre inconnue', VLAN_NOK) - room_user = User.objects.filter(room=Room.objects.filter(name=port.room)) + room_user = User.objects.filter(room=port.room) if not room_user: return (sw_name, u'Chambre non cotisante', VLAN_NOK) elif not room_user.first().has_access(): diff --git a/machines/admin.py b/machines/admin.py index 49b02a7e..e0256fe0 100644 --- a/machines/admin.py +++ b/machines/admin.py @@ -30,51 +30,67 @@ from .models import IpType, Machine, MachineType, Domain, IpList, Interface from .models import Extension, Mx, Ns, Vlan, Text, Nas, Service, OuverturePort from .models import OuverturePortList + class MachineAdmin(VersionAdmin): pass + class IpTypeAdmin(VersionAdmin): pass + class MachineTypeAdmin(VersionAdmin): pass + class VlanAdmin(VersionAdmin): pass + class ExtensionAdmin(VersionAdmin): pass + class MxAdmin(VersionAdmin): pass + class NsAdmin(VersionAdmin): pass + class TextAdmin(VersionAdmin): pass + class NasAdmin(VersionAdmin): pass + class IpListAdmin(VersionAdmin): pass + class OuverturePortAdmin(VersionAdmin): pass + class OuverturePortListAdmin(VersionAdmin): pass + class InterfaceAdmin(VersionAdmin): list_display = ('machine','type','mac_address','ipv4','details') + class DomainAdmin(VersionAdmin): list_display = ('interface_parent', 'name', 'extension', 'cname') + class ServiceAdmin(VersionAdmin): list_display = ('service_type', 'min_time_regen', 'regular_time_regen') + admin.site.register(Machine, MachineAdmin) admin.site.register(MachineType, MachineTypeAdmin) admin.site.register(IpType, IpTypeAdmin) diff --git a/machines/forms.py b/machines/forms.py index 6112a182..80df9215 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -21,20 +21,43 @@ # 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. +""" +Formulaires d'ajout, edition et suppressions de : + - machines + - interfaces + - domain (noms de machine) + - alias (cname) + - service (dhcp, dns..) + - ns (serveur dns) + - mx (serveur mail) + - ports ouverts et profils d'ouverture par interface +""" from __future__ import unicode_literals -import re - -from django.forms import ModelForm, Form, ValidationError +from django.forms import ModelForm, Form from django import forms -from .models import Domain, Machine, Interface, IpList, MachineType, Extension, Mx, Text, Ns, Service, Vlan, Nas, IpType, OuverturePortList, OuverturePort -from django.db.models import Q -from django.core.validators import validate_email -from users.models import User +from .models import ( + Domain, + Machine, + Interface, + IpList, + MachineType, + Extension, + Mx, + Text, + Ns, + Service, + Vlan, + Nas, + IpType, + OuverturePortList, +) + class EditMachineForm(ModelForm): + """Formulaire d'édition d'une machine""" class Meta: model = Machine fields = '__all__' @@ -44,15 +67,22 @@ class EditMachineForm(ModelForm): super(EditMachineForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields['name'].label = 'Nom de la machine' + class NewMachineForm(EditMachineForm): + """Creation d'une machine, ne renseigne que le nom""" class Meta(EditMachineForm.Meta): fields = ['name'] + class BaseEditMachineForm(EditMachineForm): + """Edition basique, ne permet que de changer le nom et le statut. + Réservé aux users sans droits spécifiques""" class Meta(EditMachineForm.Meta): - fields = ['name','active'] + fields = ['name', 'active'] + class EditInterfaceForm(ModelForm): + """Edition d'une interface. Edition complète""" class Meta: model = Interface fields = ['machine', 'type', 'ipv4', 'mac_address', 'details'] @@ -64,85 +94,126 @@ class EditInterfaceForm(ModelForm): self.fields['type'].label = 'Type de machine' self.fields['type'].empty_label = "Séléctionner un type de machine" if "ipv4" in self.fields: - self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True) + self.fields['ipv4'].empty_label = "Assignation automatique\ + de l'ipv4" + self.fields['ipv4'].queryset = IpList.objects.filter( + interface__isnull=True + ) # Add it's own address - self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance) + self.fields['ipv4'].queryset |= IpList.objects.filter( + interface=self.instance + ) if "machine" in self.fields: - self.fields['machine'].queryset = Machine.objects.all().select_related('user') + self.fields['machine'].queryset = Machine.objects.all()\ + .select_related('user') + class AddInterfaceForm(EditInterfaceForm): + """Ajout d'une interface à une machine. En fonction des droits, + affiche ou non l'ensemble des ip disponibles""" class Meta(EditInterfaceForm.Meta): - fields = ['type','ipv4','mac_address','details'] + fields = ['type', 'ipv4', 'mac_address', 'details'] def __init__(self, *args, **kwargs): infra = kwargs.pop('infra') super(AddInterfaceForm, self).__init__(*args, **kwargs) self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" if not infra: - self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False)) - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)) + self.fields['type'].queryset = MachineType.objects.filter( + ip_type__in=IpType.objects.filter(need_infra=False) + ) + self.fields['ipv4'].queryset = IpList.objects.filter( + interface__isnull=True + ).filter(ip_type__in=IpType.objects.filter(need_infra=False)) else: - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True) + self.fields['ipv4'].queryset = IpList.objects.filter( + interface__isnull=True + ) + class NewInterfaceForm(EditInterfaceForm): + """Formulaire light, sans choix de l'ipv4; d'ajout d'une interface""" class Meta(EditInterfaceForm.Meta): - fields = ['type','mac_address','details'] + fields = ['type', 'mac_address', 'details'] + class BaseEditInterfaceForm(EditInterfaceForm): + """Edition basique d'une interface. En fonction des droits, + ajoute ou non l'ensemble des ipv4 disponibles (infra)""" class Meta(EditInterfaceForm.Meta): - fields = ['type','ipv4','mac_address','details'] + fields = ['type', 'ipv4', 'mac_address', 'details'] def __init__(self, *args, **kwargs): infra = kwargs.pop('infra') super(BaseEditInterfaceForm, self).__init__(*args, **kwargs) self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" if not infra: - self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False)) - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)) + self.fields['type'].queryset = MachineType.objects.filter( + ip_type__in=IpType.objects.filter(need_infra=False) + ) + self.fields['ipv4'].queryset = IpList.objects.filter( + interface__isnull=True + ).filter(ip_type__in=IpType.objects.filter(need_infra=False)) # Add it's own address - self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance) + self.fields['ipv4'].queryset |= IpList.objects.filter( + interface=self.instance + ) else: - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True) - self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance) + self.fields['ipv4'].queryset = IpList.objects.filter( + interface__isnull=True + ) + self.fields['ipv4'].queryset |= IpList.objects.filter( + interface=self.instance + ) + class AliasForm(ModelForm): + """Ajout d'un alias (et edition), CNAME, contenant nom et extension""" class Meta: model = Domain - fields = ['name','extension'] + fields = ['name', 'extension'] def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) - if 'infra' in kwargs: - infra = kwargs.pop('infra') super(AliasForm, self).__init__(*args, prefix=prefix, **kwargs) + class DomainForm(AliasForm): + """Ajout et edition d'un enregistrement de nom, relié à interface""" class Meta(AliasForm.Meta): fields = ['name'] def __init__(self, *args, **kwargs): if 'user' in kwargs: user = kwargs.pop('user') - nb_machine = kwargs.pop('nb_machine') initial = kwargs.get('initial', {}) initial['name'] = user.get_next_domain_name() - kwargs['initial'] = initial + kwargs['initial'] = initial prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(DomainForm, self).__init__(*args, prefix=prefix, **kwargs) - + + class DelAliasForm(Form): - alias = forms.ModelMultipleChoiceField(queryset=Domain.objects.all(), label="Alias actuels", widget=forms.CheckboxSelectMultiple) + """Suppression d'un ou plusieurs objets alias""" + alias = forms.ModelMultipleChoiceField( + queryset=Domain.objects.all(), + label="Alias actuels", + widget=forms.CheckboxSelectMultiple + ) def __init__(self, *args, **kwargs): interface = kwargs.pop('interface') super(DelAliasForm, self).__init__(*args, **kwargs) - self.fields['alias'].queryset = Domain.objects.filter(cname__in=Domain.objects.filter(interface_parent=interface)) + self.fields['alias'].queryset = Domain.objects.filter( + cname__in=Domain.objects.filter(interface_parent=interface) + ) + class MachineTypeForm(ModelForm): + """Ajout et edition d'un machinetype, relié à un iptype""" class Meta: model = MachineType - fields = ['type','ip_type'] + fields = ['type', 'ip_type'] def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) @@ -150,54 +221,97 @@ class MachineTypeForm(ModelForm): self.fields['type'].label = 'Type de machine à ajouter' self.fields['ip_type'].label = "Type d'ip relié" + class DelMachineTypeForm(Form): - machinetypes = forms.ModelMultipleChoiceField(queryset=MachineType.objects.all(), label="Types de machines actuelles", widget=forms.CheckboxSelectMultiple) + """Suppression d'un ou plusieurs machinetype""" + machinetypes = forms.ModelMultipleChoiceField( + queryset=MachineType.objects.all(), + label="Types de machines actuelles", + widget=forms.CheckboxSelectMultiple + ) + class IpTypeForm(ModelForm): + """Formulaire d'ajout d'un iptype. Pas d'edition de l'ip de start et de + stop après creation""" class Meta: model = IpType - fields = ['type','extension','need_infra','domaine_ip_start','domaine_ip_stop', 'prefix_v6', 'vlan'] - + fields = ['type', 'extension', 'need_infra', 'domaine_ip_start', + 'domaine_ip_stop', 'prefix_v6', 'vlan', 'ouverture_ports'] + def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(IpTypeForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields['type'].label = 'Type ip à ajouter' + class EditIpTypeForm(IpTypeForm): + """Edition d'un iptype. Pas d'edition du rangev4 possible, car il faudrait + synchroniser les objets iplist""" class Meta(IpTypeForm.Meta): - fields = ['extension','type','need_infra', 'prefix_v6', 'vlan'] + fields = ['extension', 'type', 'need_infra', 'prefix_v6', 'vlan', + 'ouverture_ports'] + class DelIpTypeForm(Form): - iptypes = forms.ModelMultipleChoiceField(queryset=IpType.objects.all(), label="Types d'ip actuelles", widget=forms.CheckboxSelectMultiple) + """Suppression d'un ou plusieurs iptype""" + iptypes = forms.ModelMultipleChoiceField( + queryset=IpType.objects.all(), + label="Types d'ip actuelles", + widget=forms.CheckboxSelectMultiple + ) + class ExtensionForm(ModelForm): + """Formulaire d'ajout et edition d'une extension""" class Meta: model = Extension - fields = ['name', 'need_infra', 'origin'] + fields = '__all__' def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(ExtensionForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields['name'].label = 'Extension à ajouter' self.fields['origin'].label = 'Enregistrement A origin' + self.fields['origin_v6'].label = 'Enregistrement AAAA origin' + class DelExtensionForm(Form): - extensions = forms.ModelMultipleChoiceField(queryset=Extension.objects.all(), label="Extensions actuelles", widget=forms.CheckboxSelectMultiple) + """Suppression d'une ou plusieurs extensions""" + extensions = forms.ModelMultipleChoiceField( + queryset=Extension.objects.all(), + label="Extensions actuelles", + widget=forms.CheckboxSelectMultiple + ) + class MxForm(ModelForm): + """Ajout et edition d'un MX""" class Meta: model = Mx fields = ['zone', 'priority', 'name'] - + def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(MxForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['name'].queryset = Domain.objects.exclude(interface_parent=None) - + self.fields['name'].queryset = Domain.objects.exclude( + interface_parent=None + ).select_related('extension') + + class DelMxForm(Form): - mx = forms.ModelMultipleChoiceField(queryset=Mx.objects.all(), label="MX actuels", widget=forms.CheckboxSelectMultiple) + """Suppression d'un ou plusieurs MX""" + mx = forms.ModelMultipleChoiceField( + queryset=Mx.objects.all(), + label="MX actuels", + widget=forms.CheckboxSelectMultiple + ) + class NsForm(ModelForm): + """Ajout d'un NS pour une zone + On exclue les CNAME dans les objets domain (interdit par la rfc) + donc on prend uniquemet """ class Meta: model = Ns fields = ['zone', 'ns'] @@ -205,12 +319,22 @@ class NsForm(ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(NsForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields['ns'].queryset = Domain.objects.exclude(interface_parent=None) + self.fields['ns'].queryset = Domain.objects.exclude( + interface_parent=None + ).select_related('extension') + class DelNsForm(Form): - ns = forms.ModelMultipleChoiceField(queryset=Ns.objects.all(), label="Enregistrements NS actuels", widget=forms.CheckboxSelectMultiple) + """Suppresion d'un ou plusieurs NS""" + ns = forms.ModelMultipleChoiceField( + queryset=Ns.objects.all(), + label="Enregistrements NS actuels", + widget=forms.CheckboxSelectMultiple + ) + class TxtForm(ModelForm): + """Ajout d'un txt pour une zone""" class Meta: model = Text fields = '__all__' @@ -219,10 +343,19 @@ class TxtForm(ModelForm): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(TxtForm, self).__init__(*args, prefix=prefix, **kwargs) + class DelTxtForm(Form): - txt = forms.ModelMultipleChoiceField(queryset=Text.objects.all(), label="Enregistrements Txt actuels", widget=forms.CheckboxSelectMultiple) + """Suppression d'un ou plusieurs TXT""" + txt = forms.ModelMultipleChoiceField( + queryset=Text.objects.all(), + label="Enregistrements Txt actuels", + widget=forms.CheckboxSelectMultiple + ) + class NasForm(ModelForm): + """Ajout d'un type de nas (machine d'authentification, + swicths, bornes...)""" class Meta: model = Nas fields = '__all__' @@ -231,10 +364,18 @@ class NasForm(ModelForm): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(NasForm, self).__init__(*args, prefix=prefix, **kwargs) + class DelNasForm(Form): - nas = forms.ModelMultipleChoiceField(queryset=Nas.objects.all(), label="Enregistrements Nas actuels", widget=forms.CheckboxSelectMultiple) + """Suppression d'un ou plusieurs nas""" + nas = forms.ModelMultipleChoiceField( + queryset=Nas.objects.all(), + label="Enregistrements Nas actuels", + widget=forms.CheckboxSelectMultiple + ) + class ServiceForm(ModelForm): + """Ajout et edition d'une classe de service : dns, dhcp, etc""" class Meta: model = Service fields = '__all__' @@ -242,6 +383,8 @@ class ServiceForm(ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(ServiceForm, self).__init__(*args, prefix=prefix, **kwargs) + self.fields['servers'].queryset = Interface.objects.all()\ + .select_related('domain__extension') def save(self, commit=True): instance = super(ServiceForm, self).save(commit=False) @@ -250,10 +393,18 @@ class ServiceForm(ModelForm): instance.process_link(self.cleaned_data.get('servers')) return instance + class DelServiceForm(Form): - service = forms.ModelMultipleChoiceField(queryset=Service.objects.all(), label="Services actuels", widget=forms.CheckboxSelectMultiple) + """Suppression d'un ou plusieurs service""" + service = forms.ModelMultipleChoiceField( + queryset=Service.objects.all(), + label="Services actuels", + widget=forms.CheckboxSelectMultiple + ) + class VlanForm(ModelForm): + """Ajout d'un vlan : id, nom""" class Meta: model = Vlan fields = '__all__' @@ -262,24 +413,43 @@ class VlanForm(ModelForm): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(VlanForm, self).__init__(*args, prefix=prefix, **kwargs) + class DelVlanForm(Form): - vlan = forms.ModelMultipleChoiceField(queryset=Vlan.objects.all(), label="Vlan actuels", widget=forms.CheckboxSelectMultiple) + """Suppression d'un ou plusieurs vlans""" + vlan = forms.ModelMultipleChoiceField( + queryset=Vlan.objects.all(), + label="Vlan actuels", + widget=forms.CheckboxSelectMultiple + ) + class EditOuverturePortConfigForm(ModelForm): + """Edition de la liste des profils d'ouverture de ports + pour l'interface""" class Meta: model = Interface fields = ['port_lists'] def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) - super(EditOuverturePortConfigForm, self).__init__(*args, prefix=prefix, **kwargs) + super(EditOuverturePortConfigForm, self).__init__( + *args, + prefix=prefix, + **kwargs + ) + class EditOuverturePortListForm(ModelForm): + """Edition de la liste des ports et profils d'ouverture + des ports""" class Meta: model = OuverturePortList fields = '__all__' def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) - super(EditOuverturePortListForm, self).__init__(*args, prefix=prefix, **kwargs) - + super(EditOuverturePortListForm, self).__init__( + *args, + prefix=prefix, + **kwargs + ) diff --git a/machines/migrations/0060_iptype_ouverture_ports.py b/machines/migrations/0060_iptype_ouverture_ports.py new file mode 100644 index 00000000..e35f398f --- /dev/null +++ b/machines/migrations/0060_iptype_ouverture_ports.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-10-03 16:08 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0059_iptype_prefix_v6'), + ] + + operations = [ + migrations.AddField( + model_name='iptype', + name='ouverture_ports', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='machines.OuverturePortList'), + ), + ] diff --git a/machines/migrations/0061_auto_20171015_2033.py b/machines/migrations/0061_auto_20171015_2033.py new file mode 100644 index 00000000..6153bbd0 --- /dev/null +++ b/machines/migrations/0061_auto_20171015_2033.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-10-15 18:33 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0060_iptype_ouverture_ports'), + ] + + operations = [ + migrations.AlterField( + model_name='mx', + name='priority', + field=models.PositiveIntegerField(unique=True), + ), + migrations.AlterField( + model_name='ouvertureport', + name='begin', + field=models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(65535)]), + ), + migrations.AlterField( + model_name='ouvertureport', + name='end', + field=models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(65535)]), + ), + migrations.AlterField( + model_name='vlan', + name='vlan_id', + field=models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(4095)]), + ), + ] diff --git a/machines/migrations/0062_extension_origin_v6.py b/machines/migrations/0062_extension_origin_v6.py new file mode 100644 index 00000000..1c3d869a --- /dev/null +++ b/machines/migrations/0062_extension_origin_v6.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-10-18 14:08 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0061_auto_20171015_2033'), + ] + + operations = [ + migrations.AddField( + model_name='extension', + name='origin_v6', + field=models.GenericIPAddressField(blank=True, null=True, protocol='IPv6'), + ), + ] diff --git a/machines/models.py b/machines/models.py index 698fdb90..e3c14ea4 100644 --- a/machines/models.py +++ b/machines/models.py @@ -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,13 +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) + 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): @@ -95,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) @@ -116,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() @@ -132,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) @@ -149,19 +186,22 @@ class IpType(models.Model): def __str__(self): return self.type + class Vlan(models.Model): - """ Un vlan : vlan_id et nom""" + """ Un vlan : vlan_id et nom + On limite le vlan id entre 0 et 4096, comme défini par la norme""" PRETTY_NAME = "Vlans" - vlan_id = models.IntegerField() + vlan_id = models.PositiveIntegerField(validators=[MaxValueValidator(4095)]) name = models.CharField(max_length=256) comment = models.CharField(max_length=256, blank=True) 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" @@ -173,48 +213,84 @@ 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 + ) + origin_v6 = models.GenericIPAddressField( + protocol='IPv6', + null=True, + blank=True + ) @cached_property def dns_entry(self): - """ Une entrée DNS A""" - return "@ IN A " + str(self.origin) + """ Une entrée DNS A et AAAA sur origin (zone self)""" + entry = "" + if self.origin: + entry += "@ IN A " + str(self.origin) + if self.origin_v6: + if entry: + entry += "\n" + entry += "@ IN AAAA " + str(self.origin_v6) + return entry 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" zone = models.ForeignKey('Extension', on_delete=models.PROTECT) - priority = models.IntegerField(unique=True) + priority = models.PositiveIntegerField(unique=True) name = models.OneToOneField('Domain', on_delete=models.PROTECT) @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) @@ -222,11 +298,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" @@ -234,24 +312,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) @@ -265,12 +352,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 @@ -284,10 +373,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): @@ -305,7 +395,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): @@ -320,8 +411,10 @@ class Interface(models.Model): def save(self, *args, **kwargs): 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") + if self.ipv4: + if self.type.ip_type != self.ipv4.ip_type: + raise ValidationError("L'ipv4 et le type de la machine ne\ + correspondent pas") super(Interface, self).save(*args, **kwargs) def __str__(self): @@ -340,18 +433,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"),) @@ -361,30 +470,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-]+(? 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() @@ -392,10 +506,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() @@ -404,6 +519,7 @@ class Domain(models.Model): def __str__(self): return str(self.name) + str(self.extension) + class IpList(models.Model): PRETTY_NAME = "Addresses ipv4" @@ -412,13 +528,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): @@ -428,24 +546,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): @@ -454,13 +584,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) @@ -475,11 +608,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) @@ -487,143 +625,202 @@ 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 """ TCP = 'T' UDP = 'U' IN = 'I' OUT = 'O' - begin = models.IntegerField() - end = models.IntegerField() - port_list = models.ForeignKey('OuverturePortList', on_delete=models.CASCADE) + begin = models.PositiveIntegerField(validators=[MaxValueValidator(65535)]) + end = models.PositiveIntegerField(validators=[MaxValueValidator(65535)]) + 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') diff --git a/machines/serializers.py b/machines/serializers.py index 2cf3d3e8..7d222ef0 100644 --- a/machines/serializers.py +++ b/machines/serializers.py @@ -24,20 +24,42 @@ #Augustin Lemesle from rest_framework import serializers -from machines.models import Interface, IpType, Extension, IpList, MachineType, Domain, Text, Mx, Service_link, Ns +from machines.models import ( + Interface, + IpType, + Extension, + IpList, + MachineType, + Domain, + Text, + Mx, + Service_link, + Ns, + OuverturePortList, + OuverturePort +) + class IpTypeField(serializers.RelatedField): + """Serialisation d'une iptype, renvoie son evaluation str""" def to_representation(self, value): return value.type + class IpListSerializer(serializers.ModelSerializer): + """Serialisation d'une iplist, ip_type etant une foreign_key, + on evalue sa methode str""" ip_type = IpTypeField(read_only=True) class Meta: model = IpList fields = ('ipv4', 'ip_type') + class InterfaceSerializer(serializers.ModelSerializer): + """Serialisation d'une interface, ipv4, domain et extension sont + des foreign_key, on les override et on les evalue avec des fonctions + get_...""" ipv4 = IpListSerializer(read_only=True) mac_address = serializers.SerializerMethodField('get_macaddress') domain = serializers.SerializerMethodField('get_dns') @@ -56,7 +78,9 @@ class InterfaceSerializer(serializers.ModelSerializer): def get_macaddress(self, obj): return str(obj.mac_address) + class FullInterfaceSerializer(serializers.ModelSerializer): + """Serialisation complete d'une interface avec l'ipv6 en plus""" ipv4 = IpListSerializer(read_only=True) mac_address = serializers.SerializerMethodField('get_macaddress') domain = serializers.SerializerMethodField('get_dns') @@ -75,24 +99,69 @@ class FullInterfaceSerializer(serializers.ModelSerializer): def get_macaddress(self, obj): return str(obj.mac_address) + class ExtensionNameField(serializers.RelatedField): + """Evaluation str d'un objet extension (.example.org)""" def to_representation(self, value): return value.name + class TypeSerializer(serializers.ModelSerializer): + """Serialisation d'un iptype : extension et la liste des + ouvertures de port son evalués en get_... etant des + foreign_key ou des relations manytomany""" extension = ExtensionNameField(read_only=True) + ouverture_ports_tcp_in = serializers\ + .SerializerMethodField('get_port_policy_input_tcp') + ouverture_ports_tcp_out = serializers\ + .SerializerMethodField('get_port_policy_output_tcp') + ouverture_ports_udp_in = serializers\ + .SerializerMethodField('get_port_policy_input_udp') + ouverture_ports_udp_out = serializers\ + .SerializerMethodField('get_port_policy_output_udp') class Meta: model = IpType - fields = ('type', 'extension', 'domaine_ip_start', 'domaine_ip_stop') + fields = ('type', 'extension', 'domaine_ip_start', 'domaine_ip_stop', + 'ouverture_ports_tcp_in', 'ouverture_ports_tcp_out', + 'ouverture_ports_udp_in', 'ouverture_ports_udp_out',) + + def get_port_policy(self, obj, protocole, io): + if obj.ouverture_ports is None: + return [] + return map( + str, + obj.ouverture_ports.ouvertureport_set.filter( + protocole=protocole + ).filter(io=io) + ) + + def get_port_policy_input_tcp(self, obj): + """Renvoie la liste des ports ouverts en entrée tcp""" + return self.get_port_policy(obj, OuverturePort.TCP, OuverturePort.IN) + + def get_port_policy_output_tcp(self, obj): + """Renvoie la liste des ports ouverts en sortie tcp""" + return self.get_port_policy(obj, OuverturePort.TCP, OuverturePort.OUT) + + def get_port_policy_input_udp(self, obj): + """Renvoie la liste des ports ouverts en entrée udp""" + return self.get_port_policy(obj, OuverturePort.UDP, OuverturePort.IN) + + def get_port_policy_output_udp(self, obj): + """Renvoie la liste des ports ouverts en sortie udp""" + return self.get_port_policy(obj, OuverturePort.UDP, OuverturePort.OUT) + class ExtensionSerializer(serializers.ModelSerializer): + """Serialisation d'une extension : origin_ip et la zone sont + des foreign_key donc evalués en get_...""" origin = serializers.SerializerMethodField('get_origin_ip') zone_entry = serializers.SerializerMethodField('get_zone_name') class Meta: model = Extension - fields = ('name', 'origin', 'zone_entry') + fields = ('name', 'origin', 'origin_v6', 'zone_entry') def get_origin_ip(self, obj): return obj.origin.ipv4 @@ -100,7 +169,10 @@ class ExtensionSerializer(serializers.ModelSerializer): def get_zone_name(self, obj): return str(obj.dns_entry) + class MxSerializer(serializers.ModelSerializer): + """Serialisation d'un MX, evaluation du nom, de la zone + et du serveur cible, etant des foreign_key""" name = serializers.SerializerMethodField('get_entry_name') zone = serializers.SerializerMethodField('get_zone_name') mx_entry = serializers.SerializerMethodField('get_mx_name') @@ -118,13 +190,16 @@ class MxSerializer(serializers.ModelSerializer): def get_mx_name(self, obj): return str(obj.dns_entry) + class TextSerializer(serializers.ModelSerializer): + """Serialisation d'un txt : zone cible et l'entrée txt + sont evaluées à part""" zone = serializers.SerializerMethodField('get_zone_name') text_entry = serializers.SerializerMethodField('get_text_name') class Meta: model = Text - fields = ('zone','text_entry','field1', 'field2') + fields = ('zone', 'text_entry', 'field1', 'field2') def get_zone_name(self, obj): return str(obj.zone.name) @@ -132,10 +207,13 @@ class TextSerializer(serializers.ModelSerializer): def get_text_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""" zone = serializers.SerializerMethodField('get_zone_name') ns = serializers.SerializerMethodField('get_domain_name') - ns_entry = serializers.SerializerMethodField('get_text_name') + ns_entry = serializers.SerializerMethodField('get_text_name') class Meta: model = Ns @@ -150,10 +228,13 @@ class NsSerializer(serializers.ModelSerializer): def get_text_name(self, obj): return str(obj.dns_entry) + class DomainSerializer(serializers.ModelSerializer): + """Serialisation d'un domain, extension, cname sont des foreign_key, + et l'entrée complète, sont évalués à part""" extension = serializers.SerializerMethodField('get_zone_name') cname = serializers.SerializerMethodField('get_alias_name') - cname_entry = serializers.SerializerMethodField('get_cname_name') + cname_entry = serializers.SerializerMethodField('get_cname_name') class Meta: model = Domain @@ -168,7 +249,9 @@ class DomainSerializer(serializers.ModelSerializer): def get_cname_name(self, obj): return str(obj.dns_entry) + class ServiceServersSerializer(serializers.ModelSerializer): + """Evaluation d'un Service, et serialisation""" server = serializers.SerializerMethodField('get_server_name') service = serializers.SerializerMethodField('get_service_name') need_regen = serializers.SerializerMethodField('get_regen_status') @@ -185,3 +268,31 @@ class ServiceServersSerializer(serializers.ModelSerializer): def get_regen_status(self, obj): return obj.need_regen() + + +class OuverturePortsSerializer(serializers.Serializer): + """Serialisation de l'ouverture des ports""" + ipv4 = serializers.SerializerMethodField() + ipv6 = serializers.SerializerMethodField() + + def get_ipv4(): + return {i.ipv4.ipv4: + { + "tcp_in":[j.tcp_ports_in() for j in i.port_lists.all()], + "tcp_out":[j.tcp_ports_out()for j in i.port_lists.all()], + "udp_in":[j.udp_ports_in() for j in i.port_lists.all()], + "udp_out":[j.udp_ports_out() for j in i.port_lists.all()], + } + for i in Interface.objects.all() if i.ipv4 + } + + def get_ipv6(): + return {i.ipv6: + { + "tcp_in":[j.tcp_ports_in() for j in i.port_lists.all()], + "tcp_out":[j.tcp_ports_out()for j in i.port_lists.all()], + "udp_in":[j.udp_ports_in() for j in i.port_lists.all()], + "udp_out":[j.udp_ports_out() for j in i.port_lists.all()], + } + for i in Interface.objects.all() if i.ipv6 + } diff --git a/machines/templates/machines/aff_extension.html b/machines/templates/machines/aff_extension.html index 18fd7c4b..069f545a 100644 --- a/machines/templates/machines/aff_extension.html +++ b/machines/templates/machines/aff_extension.html @@ -28,7 +28,8 @@ with this program; if not, write to the Free Software Foundation, Inc., Extension Autorisation infra pour utiliser l'extension Enregistrement A origin - + Enregistrement AAAA origin + {% for extension in extension_list %} @@ -36,6 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {{ extension.name }} {{ extension.need_infra }} {{ extension.origin }} + {{ extension.origin_v6 }} {% if is_infra %} {% include 'buttons/edit.html' with href='machines:edit-extension' id=extension.id %} diff --git a/machines/templates/machines/aff_iptype.html b/machines/templates/machines/aff_iptype.html index aafc4c1d..454b169d 100644 --- a/machines/templates/machines/aff_iptype.html +++ b/machines/templates/machines/aff_iptype.html @@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc., Fin Préfixe v6 Sur vlan + Ouverture ports par défault @@ -45,6 +46,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {{ type.domaine_ip_stop }} {{ type.prefix_v6 }} {{ type.vlan }} + {{ type.ouverture_ports }} {% if is_infra %} {% include 'buttons/edit.html' with href='machines:edit-iptype' id=type.id %} diff --git a/machines/templates/machines/sidebar.html b/machines/templates/machines/sidebar.html index e635d69a..6ca3a07f 100644 --- a/machines/templates/machines/sidebar.html +++ b/machines/templates/machines/sidebar.html @@ -58,7 +58,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if is_cableur %} - Configuration de ports + Ouverture de ports {%endif%} {% endblock %} diff --git a/machines/urls.py b/machines/urls.py index 1ce5db7d..fa9b3497 100644 --- a/machines/urls.py +++ b/machines/urls.py @@ -93,6 +93,7 @@ urlpatterns = [ url(r'^rest/text/$', views.text, name='text'), 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'), url(r'index_portlist/$', views.index_portlist, name='index-portlist'), url(r'^edit_portlist/(?P[0-9]+)$', views.edit_portlist, name='edit-portlist'), url(r'^del_portlist/(?P[0-9]+)$', views.del_portlist, name='del-portlist'), diff --git a/machines/views.py b/machines/views.py index 92ceedfa..1271e277 100644 --- a/machines/views.py +++ b/machines/views.py @@ -43,18 +43,79 @@ from django.contrib.auth import authenticate, login from django.views.decorators.csrf import csrf_exempt from rest_framework.renderers import JSONRenderer -from machines.serializers import FullInterfaceSerializer, InterfaceSerializer, TypeSerializer, DomainSerializer, TextSerializer, MxSerializer, ExtensionSerializer, ServiceServersSerializer, NsSerializer +from machines.serializers import ( FullInterfaceSerializer, + InterfaceSerializer, + TypeSerializer, + DomainSerializer, + TextSerializer, + MxSerializer, + ExtensionSerializer, + ServiceServersSerializer, + NsSerializer, + OuverturePortsSerializer +) from reversion import revisions as reversion from reversion.models import Version import re -from .forms import NewMachineForm, EditMachineForm, EditInterfaceForm, AddInterfaceForm, MachineTypeForm, DelMachineTypeForm, ExtensionForm, DelExtensionForm, BaseEditInterfaceForm, BaseEditMachineForm -from .forms import EditIpTypeForm, IpTypeForm, DelIpTypeForm, DomainForm, AliasForm, DelAliasForm, NsForm, DelNsForm, TxtForm, DelTxtForm, MxForm, DelMxForm, VlanForm, DelVlanForm, ServiceForm, DelServiceForm, NasForm, DelNasForm +from .forms import ( + NewMachineForm, + EditMachineForm, + EditInterfaceForm, + AddInterfaceForm, + MachineTypeForm, + DelMachineTypeForm, + ExtensionForm, + DelExtensionForm, + BaseEditInterfaceForm, + BaseEditMachineForm +) +from .forms import ( + EditIpTypeForm, + IpTypeForm, + DelIpTypeForm, + DomainForm, + AliasForm, + DelAliasForm, + NsForm, + DelNsForm, + TxtForm, + DelTxtForm, + MxForm, + DelMxForm, + VlanForm, + DelVlanForm, + ServiceForm, + DelServiceForm, + NasForm, + DelNasForm +) from .forms import EditOuverturePortListForm, EditOuverturePortConfigForm -from .models import IpType, Machine, Interface, IpList, MachineType, Extension, Mx, Ns, Domain, Service, Service_link, Vlan, Nas, Text, OuverturePortList, OuverturePort +from .models import ( + IpType, + Machine, + Interface, + IpList, + MachineType, + Extension, + Mx, + Ns, + Domain, + Service, + Service_link, + Vlan, + Nas, + Text, + OuverturePortList, + OuverturePort +) from users.models import User from preferences.models import GeneralOption, OptionalMachine -from re2o.utils import all_active_assigned_interfaces, all_has_access +from re2o.utils import ( + all_active_assigned_interfaces, + all_has_access, + filter_active_interfaces +) from re2o.views import form def f_type_id( is_type_tt ): @@ -71,7 +132,8 @@ def generate_ipv4_choices( form ) : choices = '{"":[{key:"",value:"Choisissez d\'abord un type de machine"},' mtype_id = -1 - for ip in f_ipv4.queryset.annotate(mtype_id=F('ip_type__machinetype__id')).order_by('mtype_id', 'id') : + for ip in f_ipv4.queryset.annotate(mtype_id=F('ip_type__machinetype__id'))\ + .order_by('mtype_id', 'id') : if mtype_id != ip.mtype_id : mtype_id = ip.mtype_id used_mtype_id.append(mtype_id) @@ -140,8 +202,8 @@ def generate_ipv4_mbf_param( form, is_type_tt ): @login_required def new_machine(request, userid): - """ Fonction de creation d'une machine. Cree l'objet machine, le sous objet interface et l'objet domain - à partir de model forms. + """ Fonction de creation d'une machine. Cree l'objet machine, + le sous objet interface et l'objet domain à partir de model forms. Trop complexe, devrait être simplifié""" try: user = User.objects.get(pk=userid) @@ -152,15 +214,16 @@ def new_machine(request, userid): max_lambdauser_interfaces = options.max_lambdauser_interfaces if not request.user.has_perms(('cableur',)): if user != request.user: - messages.error(request, "Vous ne pouvez pas ajouter une machine à un autre user que vous sans droit") + messages.error( + request, + "Vous ne pouvez pas ajouter une machine à un autre user que vous sans droit") return redirect("/users/profil/" + str(request.user.id)) if user.user_interfaces().count() >= max_lambdauser_interfaces: messages.error(request, "Vous avez atteint le maximum d'interfaces autorisées que vous pouvez créer vous même (%s) " % max_lambdauser_interfaces) return redirect("/users/profil/" + str(request.user.id)) machine = NewMachineForm(request.POST or None) interface = AddInterfaceForm(request.POST or None, infra=request.user.has_perms(('infra',))) - nb_machine = Interface.objects.filter(machine__user=userid).count() - domain = DomainForm(request.POST or None, user=user, nb_machine=nb_machine) + domain = DomainForm(request.POST or None, user=user) if machine.is_valid() and interface.is_valid(): new_machine = machine.save(commit=False) new_machine.user = user @@ -993,7 +1056,9 @@ def history(request, object, id): @login_required @permission_required('cableur') def index_portlist(request): - port_list = OuverturePortList.objects.prefetch_related('ouvertureport_set').prefetch_related('interface_set').order_by('name') + port_list = OuverturePortList.objects.prefetch_related('ouvertureport_set')\ + .prefetch_related('interface_set__domain__extension')\ + .prefetch_related('interface_set__machine__user').order_by('name') return render(request, "machines/index_portlist.html", {'port_list':port_list}) @login_required @@ -1184,6 +1249,34 @@ def service_servers(request): @csrf_exempt @login_required @permission_required('serveur') +def ouverture_ports(request): + r = {'ipv4':{}, 'ipv6':{}} + for o in OuverturePortList.objects.all().prefetch_related('ouvertureport_set').prefetch_related('interface_set', 'interface_set__ipv4'): + pl = { + "tcp_in":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.TCP, io=OuverturePort.IN))), + "tcp_out":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.TCP, io=OuverturePort.OUT))), + "udp_in":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.UDP, io=OuverturePort.IN))), + "udp_out":set(map(str,o.ouvertureport_set.filter(protocole=OuverturePort.UDP, io=OuverturePort.OUT))), + } + for i in filter_active_interfaces(o.interface_set): + if i.may_have_port_open(): + d = r['ipv4'].get(i.ipv4.ipv4, {}) + d["tcp_in"] = d.get("tcp_in",set()).union(pl["tcp_in"]) + d["tcp_out"] = d.get("tcp_out",set()).union(pl["tcp_out"]) + d["udp_in"] = d.get("udp_in",set()).union(pl["udp_in"]) + d["udp_out"] = d.get("udp_out",set()).union(pl["udp_out"]) + r['ipv4'][i.ipv4.ipv4] = d + if i.ipv6_object: + d = r['ipv6'].get(i.ipv6, {}) + d["tcp_in"] = d.get("tcp_in",set()).union(pl["tcp_in"]) + d["tcp_out"] = d.get("tcp_out",set()).union(pl["tcp_out"]) + d["udp_in"] = d.get("udp_in",set()).union(pl["udp_in"]) + d["udp_out"] = d.get("udp_out",set()).union(pl["udp_out"]) + r['ipv6'][i.ipv6] = d + return JSONResponse(r) +@csrf_exempt +@login_required +@permission_required('serveur') def regen_achieved(request): obj = Service_link.objects.filter(service__in=Service.objects.filter(service_type=request.POST['service']), server__in=Interface.objects.filter(domain__in=Domain.objects.filter(name=request.POST['server']))) if obj: diff --git a/preferences/migrations/0021_auto_20171015_1741.py b/preferences/migrations/0021_auto_20171015_1741.py new file mode 100644 index 00000000..cc94720a --- /dev/null +++ b/preferences/migrations/0021_auto_20171015_1741.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-10-15 15:41 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('preferences', '0020_optionalmachine_ipv6'), + ] + + operations = [ + migrations.AlterField( + model_name='optionaltopologie', + name='radius_general_policy', + field=models.CharField(choices=[('MACHINE', 'Sur le vlan de la plage ip machine'), ('DEFINED', 'Prédéfini dans "Vlan où placer les machines après acceptation RADIUS"')], default='DEFINED', max_length=32), + ), + ] diff --git a/preferences/migrations/0022_auto_20171015_1758.py b/preferences/migrations/0022_auto_20171015_1758.py new file mode 100644 index 00000000..ea389a32 --- /dev/null +++ b/preferences/migrations/0022_auto_20171015_1758.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-10-15 15:58 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('preferences', '0021_auto_20171015_1741'), + ] + + operations = [ + migrations.AlterField( + model_name='optionaltopologie', + name='radius_general_policy', + field=models.CharField(choices=[('MACHINE', 'Sur le vlan de la plage ip machine'), ('DEFINED', 'Prédéfini dans "Vlan où placer les machines après acceptation RADIUS"')], default='DEFINED', max_length=32), + ), + ] diff --git a/preferences/migrations/0023_auto_20171015_2033.py b/preferences/migrations/0023_auto_20171015_2033.py new file mode 100644 index 00000000..3235e49f --- /dev/null +++ b/preferences/migrations/0023_auto_20171015_2033.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-10-15 18:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('preferences', '0022_auto_20171015_1758'), + ] + + operations = [ + migrations.AlterField( + model_name='optionaltopologie', + name='radius_general_policy', + field=models.CharField(choices=[('MACHINE', 'Sur le vlan de la plage ip machine'), ('DEFINED', 'Prédéfini dans "Vlan où placer les machines après acceptation RADIUS"')], default='DEFINED', max_length=32), + ), + ] diff --git a/preferences/urls.py b/preferences/urls.py index 2169f83c..f10d25a0 100644 --- a/preferences/urls.py +++ b/preferences/urls.py @@ -69,7 +69,7 @@ urlpatterns = [ ), url(r'^del_services/$', views.del_services, name='del-services'), url( - r'^history/(?Pservice)/(?P[0-9]+)$', + r'^history/(?Pservice)/(?P[0-9]+)$', views.history, name='history' ), diff --git a/re2o/utils.py b/re2o/utils.py index e2ca6db9..8abee181 100644 --- a/re2o/utils.py +++ b/re2o/utils.py @@ -104,9 +104,9 @@ def all_has_access(search_time=DT_NOW): ).distinct() -def all_active_interfaces(): - """Renvoie l'ensemble des machines autorisées à sortir sur internet """ - return Interface.objects.filter( +def filter_active_interfaces(interface_set): + """Filtre les machines autorisées à sortir sur internet dans une requête""" + return interface_set.filter( machine__in=Machine.objects.filter( user__in=all_has_access() ).filter(active=True) @@ -116,6 +116,11 @@ def all_active_interfaces(): .distinct() +def all_active_interfaces(): + """Renvoie l'ensemble des machines autorisées à sortir sur internet """ + return filter_active_interfaces(Interface.objects) + + def all_active_assigned_interfaces(): """ Renvoie l'ensemble des machines qui ont une ipv4 assignées et disposant de l'accès internet""" diff --git a/topologie/forms.py b/topologie/forms.py index 267d64b0..c8e39d6a 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -82,6 +82,11 @@ class AddPortForm(ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(AddPortForm, self).__init__(*args, prefix=prefix, **kwargs) + self.fields['machine_interface'].queryset = Interface.objects.all()\ + .select_related('domain__extension') + self.fields['related'].queryset = Port.objects.all()\ + .select_related('switch__switch_interface__domain__extension')\ + .order_by('switch', 'port') class StackForm(ModelForm): @@ -105,6 +110,8 @@ class EditSwitchForm(ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(EditSwitchForm, self).__init__(*args, prefix=prefix, **kwargs) + self.fields['switch_interface'].queryset = Interface.objects.all()\ + .select_related('domain__extension') self.fields['location'].label = 'Localisation' self.fields['number'].label = 'Nombre de ports' diff --git a/topologie/migrations/0031_auto_20171015_2033.py b/topologie/migrations/0031_auto_20171015_2033.py new file mode 100644 index 00000000..674af6c6 --- /dev/null +++ b/topologie/migrations/0031_auto_20171015_2033.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-10-15 18:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0030_auto_20171004_0235'), + ] + + operations = [ + migrations.AlterField( + model_name='port', + name='port', + field=models.PositiveIntegerField(), + ), + migrations.AlterField( + model_name='stack', + name='member_id_max', + field=models.PositiveIntegerField(), + ), + migrations.AlterField( + model_name='stack', + name='member_id_min', + field=models.PositiveIntegerField(), + ), + migrations.AlterField( + model_name='switch', + name='number', + field=models.PositiveIntegerField(), + ), + migrations.AlterField( + model_name='switch', + name='stack_member_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + ] diff --git a/topologie/models.py b/topologie/models.py index 086e0aff..adcc7a57 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -52,8 +52,8 @@ class Stack(models.Model): name = models.CharField(max_length=32, blank=True, null=True) stack_id = models.CharField(max_length=32, unique=True) details = models.CharField(max_length=255, blank=True, null=True) - member_id_min = models.IntegerField() - member_id_max = models.IntegerField() + member_id_min = models.PositiveIntegerField() + member_id_max = models.PositiveIntegerField() def __str__(self): return " ".join([self.name, self.stack_id]) @@ -90,7 +90,7 @@ class Switch(models.Model): on_delete=models.CASCADE ) location = models.CharField(max_length=255) - number = models.IntegerField() + number = models.PositiveIntegerField() details = models.CharField(max_length=255, blank=True) stack = models.ForeignKey( Stack, @@ -98,7 +98,7 @@ class Switch(models.Model): null=True, on_delete=models.SET_NULL ) - stack_member_id = models.IntegerField(blank=True, null=True) + stack_member_id = models.PositiveIntegerField(blank=True, null=True) class Meta: unique_together = ('stack', 'stack_member_id') @@ -145,8 +145,12 @@ class Port(models.Model): ('COMMON', 'COMMON'), ) - switch = models.ForeignKey('Switch', related_name="ports") - port = models.IntegerField() + switch = models.ForeignKey( + 'Switch', + related_name="ports", + on_delete=models.CASCADE + ) + port = models.PositiveIntegerField() room = models.ForeignKey( 'Room', on_delete=models.PROTECT, diff --git a/topologie/templates/topologie/aff_switch.html b/topologie/templates/topologie/aff_switch.html index 7096909b..611e6c39 100644 --- a/topologie/templates/topologie/aff_switch.html +++ b/topologie/templates/topologie/aff_switch.html @@ -51,7 +51,10 @@ with this program; if not, write to the Free Software Foundation, Inc., - + + {% include 'buttons/edit.html' with href='topologie:edit-switch' id=switch.pk %} + {% include 'buttons/suppr.html' with href='machines:del-interface' id=switch.switch_interface.id %} + {% endfor %} diff --git a/topologie/urls.py b/topologie/urls.py index 4d0a6779..77a78b97 100644 --- a/topologie/urls.py +++ b/topologie/urls.py @@ -42,16 +42,16 @@ urlpatterns = [ url(r'^switch/(?P[0-9]+)$', views.index_port, name='index-port'), - url(r'^history/(?Pswitch)/(?P[0-9]+)$', + url(r'^history/(?Pswitch)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Pport)/(?P[0-9]+)$', + url(r'^history/(?Pport)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Proom)/(?P[0-9]+)$', + url(r'^history/(?Proom)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Pstack)/(?P[0-9]+)$', + url(r'^history/(?Pstack)/(?P[0-9]+)$', views.history, name='history'), url(r'^edit_port/(?P[0-9]+)$', views.edit_port, name='edit-port'), diff --git a/topologie/views.py b/topologie/views.py index f1e8740c..5eb2de74 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -50,7 +50,7 @@ from topologie.forms import EditPortForm, NewSwitchForm, EditSwitchForm from topologie.forms import AddPortForm, EditRoomForm, StackForm from users.views import form -from machines.forms import AliasForm, NewMachineForm, EditMachineForm, EditInterfaceForm, AddInterfaceForm +from machines.forms import DomainForm, NewMachineForm, EditMachineForm, EditInterfaceForm, AddInterfaceForm from machines.views import generate_ipv4_mbf_param from preferences.models import AssoOption, GeneralOption @@ -135,7 +135,8 @@ def index_port(request, switch_id): port_list = Port.objects.filter(switch=switch)\ .select_related('room')\ .select_related('machine_interface__domain__extension')\ - .select_related('related')\ + .select_related('machine_interface__machine__user')\ + .select_related('related__switch__switch_interface__domain__extension')\ .select_related('switch')\ .order_by('port') return render(request, 'topologie/index_p.html', { @@ -344,9 +345,8 @@ def new_switch(request): request.POST or None, infra=request.user.has_perms(('infra',)) ) - domain = AliasForm( + domain = DomainForm( request.POST or None, - infra=request.user.has_perms(('infra',)) ) if switch.is_valid() and machine.is_valid() and interface.is_valid(): options, _created = AssoOption.objects.get_or_create() @@ -410,9 +410,8 @@ def edit_switch(request, switch_id): request.POST or None, instance=switch.switch_interface ) - domain_form = AliasForm( + domain_form = DomainForm( request.POST or None, - infra=request.user.has_perms(('infra',)), instance=switch.switch_interface.domain ) if switch_form.is_valid() and machine_form.is_valid()\ diff --git a/users/forms.py b/users/forms.py index fd81b426..93ef35b5 100644 --- a/users/forms.py +++ b/users/forms.py @@ -452,13 +452,14 @@ class RightForm(ModelForm): class DelRightForm(Form): """Suppression d'un droit d'un user""" rights = forms.ModelMultipleChoiceField( - queryset=Right.objects.all(), + queryset=Right.objects.select_related('user'), widget=forms.CheckboxSelectMultiple ) def __init__(self, right, *args, **kwargs): super(DelRightForm, self).__init__(*args, **kwargs) - self.fields['rights'].queryset = Right.objects.filter(right=right) + self.fields['rights'].queryset = Right.objects.select_related('user')\ + .select_related('right').filter(right=right) class BanForm(ModelForm): diff --git a/users/migrations/0056_auto_20171015_2033.py b/users/migrations/0056_auto_20171015_2033.py new file mode 100644 index 00000000..a47aca6a --- /dev/null +++ b/users/migrations/0056_auto_20171015_2033.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-10-15 18:33 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models +import users.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0055_auto_20171003_0556'), + ] + + operations = [ + migrations.AlterField( + model_name='listright', + name='gid', + field=models.PositiveIntegerField(null=True, unique=True), + ), + migrations.AlterField( + model_name='listright', + name='listright', + field=models.CharField(max_length=255, unique=True, validators=[django.core.validators.RegexValidator('^[a-z]+$', message='Les groupes unix ne peuvent contenir que des lettres minuscules')]), + ), + migrations.AlterField( + model_name='user', + name='rezo_rez_uid', + field=models.PositiveIntegerField(blank=True, null=True, unique=True), + ), + migrations.AlterField( + model_name='user', + name='uid_number', + field=models.PositiveIntegerField(default=users.models.User.auto_uid, unique=True), + ), + ] diff --git a/users/models.py b/users/models.py index 2f8f888f..550bc770 100644 --- a/users/models.py +++ b/users/models.py @@ -243,8 +243,8 @@ class User(AbstractBaseUser): state = models.IntegerField(choices=STATES, default=STATE_ACTIVE) registered = models.DateTimeField(auto_now_add=True) telephone = models.CharField(max_length=15, blank=True, null=True) - uid_number = models.IntegerField(default=auto_uid, unique=True) - rezo_rez_uid = models.IntegerField(unique=True, blank=True, null=True) + uid_number = models.PositiveIntegerField(default=auto_uid, unique=True) + rezo_rez_uid = models.PositiveIntegerField(unique=True, blank=True, null=True) USERNAME_FIELD = 'pseudo' REQUIRED_FIELDS = ['name', 'surname', 'email'] @@ -840,7 +840,7 @@ class ListRight(models.Model): que des lettres minuscules" )] ) - gid = models.IntegerField(unique=True, null=True) + gid = models.PositiveIntegerField(unique=True, null=True) details = models.CharField( help_text="Description", max_length=255, diff --git a/users/templates/users/del_right.html b/users/templates/users/del_right.html index 30edf666..57e706a7 100644 --- a/users/templates/users/del_right.html +++ b/users/templates/users/del_right.html @@ -34,19 +34,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% csrf_token %} - +
{% for key, values in userform.items %}
- {{ key }} ( {{values.rights|length }} users )
-
+
    {% for user in values.rights %}
  • {{ user }}
  • @@ -58,9 +58,18 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endfor %}
- {% bootstrap_button "Modifier" button_type="submit" icon="star" %} + {% bootstrap_button "Supprimer" button_type="submit" icon="star" %}
+ +


diff --git a/users/urls.py b/users/urls.py index 531e0826..201568cd 100644 --- a/users/urls.py +++ b/users/urls.py @@ -88,32 +88,32 @@ urlpatterns = [ url(r'^reset_password/$', views.reset_password, name='reset-password'), url(r'^mass_archive/$', views.mass_archive, name='mass-archive'), url( - r'^history/(?Puser)/(?P[0-9]+)$', + r'^history/(?Puser)/(?P[0-9]+)$', views.history, name='history' ), url( - r'^history/(?Pban)/(?P[0-9]+)$', + r'^history/(?Pban)/(?P[0-9]+)$', views.history, name='history' ), url( - r'^history/(?Pwhitelist)/(?P[0-9]+)$', + r'^history/(?Pwhitelist)/(?P[0-9]+)$', views.history, name='history' ), url( - r'^history/(?Pschool)/(?P[0-9]+)$', + r'^history/(?Pschool)/(?P[0-9]+)$', views.history, name='history' ), url( - r'^history/(?Plistright)/(?P[0-9]+)$', + r'^history/(?Plistright)/(?P[0-9]+)$', views.history, name='history' ), url( - r'^history/(?Pserviceuser)/(?P[0-9]+)$', + r'^history/(?Pserviceuser)/(?P[0-9]+)$', views.history, name='history' ),