From a66501250e10f09641e5b0bef927db10014323aa Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 20 Oct 2021 10:58:15 -0400 Subject: [PATCH] Add wireless authentication attributes --- .../wireless/inc/authentication_attrs.html | 21 ++++++++++ netbox/templates/wireless/wirelesslan.html | 3 +- netbox/templates/wireless/wirelesslink.html | 7 +++- netbox/wireless/api/serializers.py | 10 ++++- netbox/wireless/choices.py | 26 ++++++++++++ netbox/wireless/constants.py | 1 + netbox/wireless/filtersets.py | 17 +++++++- netbox/wireless/forms/bulk_edit.py | 27 +++++++++++- netbox/wireless/forms/bulk_import.py | 25 ++++++++++- netbox/wireless/forms/filtersets.py | 27 ++++++++++++ netbox/wireless/forms/models.py | 19 +++++++-- .../wireless/migrations/0002_wireless_auth.py | 41 +++++++++++++++++++ netbox/wireless/models.py | 34 +++++++++++++-- netbox/wireless/tables.py | 15 +++++-- 14 files changed, 252 insertions(+), 21 deletions(-) create mode 100644 netbox/templates/wireless/inc/authentication_attrs.html create mode 100644 netbox/wireless/migrations/0002_wireless_auth.py diff --git a/netbox/templates/wireless/inc/authentication_attrs.html b/netbox/templates/wireless/inc/authentication_attrs.html new file mode 100644 index 000000000..ed4c7546c --- /dev/null +++ b/netbox/templates/wireless/inc/authentication_attrs.html @@ -0,0 +1,21 @@ +{% load helpers %} + +
+
Authentication
+
+ + + + + + + + + + + + + +
Type{{ object.get_auth_type_display|placeholder }}
Cipher{{ object.get_auth_cipher_display|placeholder }}
PSK{{ object.auth_psk|placeholder }}
+
+
diff --git a/netbox/templates/wireless/wirelesslan.html b/netbox/templates/wireless/wirelesslan.html index cfe13ca45..5c6784de4 100644 --- a/netbox/templates/wireless/wirelesslan.html +++ b/netbox/templates/wireless/wirelesslan.html @@ -40,10 +40,11 @@ - {% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='wireless:wirelesslan_list' %} + {% include 'wireless/inc/authentication_attrs.html' %} {% plugin_left_page object %}
+ {% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='wireless:wirelesslan_list' %} {% include 'inc/custom_fields_panel.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/wireless/wirelesslink.html b/netbox/templates/wireless/wirelesslink.html index 45ec6b0c9..afdeff357 100644 --- a/netbox/templates/wireless/wirelesslink.html +++ b/netbox/templates/wireless/wirelesslink.html @@ -17,7 +17,9 @@ - + @@ -30,6 +32,7 @@
Status{{ object.get_status_display }} + {{ object.get_status_display }} +
SSID
+ {% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='wireless:wirelesslink_list' %} {% plugin_left_page object %}
@@ -39,8 +42,8 @@ {% include 'wireless/inc/wirelesslink_interface.html' with interface=object.interface_b %}
+ {% include 'wireless/inc/authentication_attrs.html' %} {% include 'inc/custom_fields_panel.html' %} - {% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='wireless:wirelesslink_list' %} {% plugin_right_page object %} diff --git a/netbox/wireless/api/serializers.py b/netbox/wireless/api/serializers.py index 24395b77c..e9be35618 100644 --- a/netbox/wireless/api/serializers.py +++ b/netbox/wireless/api/serializers.py @@ -5,6 +5,7 @@ from dcim.api.serializers import NestedInterfaceSerializer from ipam.api.serializers import NestedVLANSerializer from netbox.api import ChoiceField from netbox.api.serializers import NestedGroupModelSerializer, PrimaryModelSerializer +from wireless.choices import * from wireless.models import * from .nested_serializers import * @@ -30,11 +31,13 @@ class WirelessLANGroupSerializer(NestedGroupModelSerializer): class WirelessLANSerializer(PrimaryModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslan-detail') vlan = NestedVLANSerializer(required=False, allow_null=True) + auth_type = ChoiceField(choices=WirelessAuthTypeChoices, required=False, allow_blank=True) + auth_cipher = ChoiceField(choices=WirelessAuthCipherChoices, required=False, allow_blank=True) class Meta: model = WirelessLAN fields = [ - 'id', 'url', 'display', 'ssid', 'description', 'vlan', + 'id', 'url', 'display', 'ssid', 'description', 'vlan', 'auth_type', 'auth_cipher', 'auth_psk', ] @@ -43,9 +46,12 @@ class WirelessLinkSerializer(PrimaryModelSerializer): status = ChoiceField(choices=LinkStatusChoices, required=False) interface_a = NestedInterfaceSerializer() interface_b = NestedInterfaceSerializer() + auth_type = ChoiceField(choices=WirelessAuthTypeChoices, required=False, allow_blank=True) + auth_cipher = ChoiceField(choices=WirelessAuthCipherChoices, required=False, allow_blank=True) class Meta: model = WirelessLink fields = [ - 'id', 'url', 'display', 'interface_a', 'interface_b', 'ssid', 'status', 'description', + 'id', 'url', 'display', 'interface_a', 'interface_b', 'ssid', 'status', 'description', 'auth_type', + 'auth_cipher', 'auth_psk', ] diff --git a/netbox/wireless/choices.py b/netbox/wireless/choices.py index 8a710b532..c8e7fd09f 100644 --- a/netbox/wireless/choices.py +++ b/netbox/wireless/choices.py @@ -163,3 +163,29 @@ class WirelessChannelChoices(ChoiceSet): ) ), ) + + +class WirelessAuthTypeChoices(ChoiceSet): + TYPE_OPEN = 'open' + TYPE_WEP = 'wep' + TYPE_WPA_PERSONAL = 'wpa-personal' + TYPE_WPA_ENTERPRISE = 'wpa-enterprise' + + CHOICES = ( + (TYPE_OPEN, 'Open'), + (TYPE_WEP, 'WEP'), + (TYPE_WPA_PERSONAL, 'WPA Personal (PSK)'), + (TYPE_WPA_ENTERPRISE, 'WPA Enterprise'), + ) + + +class WirelessAuthCipherChoices(ChoiceSet): + CIPHER_AUTO = 'auto' + CIPHER_TKIP = 'tkip' + CIPHER_AES = 'aes' + + CHOICES = ( + (CIPHER_AUTO, 'Auto'), + (CIPHER_TKIP, 'TKIP'), + (CIPHER_AES, 'AES'), + ) diff --git a/netbox/wireless/constants.py b/netbox/wireless/constants.py index 188c4abd9..63de2b136 100644 --- a/netbox/wireless/constants.py +++ b/netbox/wireless/constants.py @@ -1 +1,2 @@ SSID_MAX_LENGTH = 32 # Per IEEE 802.11-2007 +PSK_MAX_LENGTH = 64 diff --git a/netbox/wireless/filtersets.py b/netbox/wireless/filtersets.py index a5d9b7d75..cc67c1fc3 100644 --- a/netbox/wireless/filtersets.py +++ b/netbox/wireless/filtersets.py @@ -4,6 +4,7 @@ from django.db.models import Q from dcim.choices import LinkStatusChoices from extras.filters import TagFilter from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet +from .choices import * from .models import * __all__ = ( @@ -36,11 +37,17 @@ class WirelessLANFilterSet(PrimaryModelFilterSet): group_id = django_filters.ModelMultipleChoiceFilter( queryset=WirelessLANGroup.objects.all() ) + auth_type = django_filters.MultipleChoiceFilter( + choices=WirelessAuthTypeChoices + ) + auth_cipher = django_filters.MultipleChoiceFilter( + choices=WirelessAuthCipherChoices + ) tag = TagFilter() class Meta: model = WirelessLAN - fields = ['id', 'ssid'] + fields = ['id', 'ssid', 'auth_psk'] def search(self, queryset, name, value): if not value.strip(): @@ -60,11 +67,17 @@ class WirelessLinkFilterSet(PrimaryModelFilterSet): status = django_filters.MultipleChoiceFilter( choices=LinkStatusChoices ) + auth_type = django_filters.MultipleChoiceFilter( + choices=WirelessAuthTypeChoices + ) + auth_cipher = django_filters.MultipleChoiceFilter( + choices=WirelessAuthCipherChoices + ) tag = TagFilter() class Meta: model = WirelessLink - fields = ['id', 'ssid'] + fields = ['id', 'ssid', 'auth_psk'] def search(self, queryset, name, value): if not value.strip(): diff --git a/netbox/wireless/forms/bulk_edit.py b/netbox/wireless/forms/bulk_edit.py index c0d5a925e..1da98026c 100644 --- a/netbox/wireless/forms/bulk_edit.py +++ b/netbox/wireless/forms/bulk_edit.py @@ -4,6 +4,7 @@ from dcim.choices import LinkStatusChoices from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm from ipam.models import VLAN from utilities.forms import BootstrapMixin, DynamicModelChoiceField +from wireless.choices import * from wireless.constants import SSID_MAX_LENGTH from wireless.models import * @@ -52,9 +53,20 @@ class WirelessLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldMode description = forms.CharField( required=False ) + auth_type = forms.ChoiceField( + choices=WirelessAuthTypeChoices, + required=False + ) + auth_cipher = forms.ChoiceField( + choices=WirelessAuthCipherChoices, + required=False + ) + auth_psk = forms.CharField( + required=False + ) class Meta: - nullable_fields = ['ssid', 'group', 'vlan', 'description'] + nullable_fields = ['ssid', 'group', 'vlan', 'description', 'auth_type', 'auth_cipher', 'auth_psk'] class WirelessLinkBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm): @@ -73,6 +85,17 @@ class WirelessLinkBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldMod description = forms.CharField( required=False ) + auth_type = forms.ChoiceField( + choices=WirelessAuthTypeChoices, + required=False + ) + auth_cipher = forms.ChoiceField( + choices=WirelessAuthCipherChoices, + required=False + ) + auth_psk = forms.CharField( + required=False + ) class Meta: - nullable_fields = ['ssid', 'description'] + nullable_fields = ['ssid', 'description', 'auth_type', 'auth_cipher', 'auth_psk'] diff --git a/netbox/wireless/forms/bulk_import.py b/netbox/wireless/forms/bulk_import.py index 6b22728f6..e9e9afed6 100644 --- a/netbox/wireless/forms/bulk_import.py +++ b/netbox/wireless/forms/bulk_import.py @@ -3,6 +3,7 @@ from dcim.models import Interface from extras.forms import CustomFieldModelCSVForm from ipam.models import VLAN from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField +from wireless.choices import * from wireless.models import * __all__ = ( @@ -38,10 +39,20 @@ class WirelessLANCSVForm(CustomFieldModelCSVForm): to_field_name='name', help_text='Bridged VLAN' ) + auth_type = CSVChoiceField( + choices=WirelessAuthTypeChoices, + required=False, + help_text='Authentication type' + ) + auth_cipher = CSVChoiceField( + choices=WirelessAuthCipherChoices, + required=False, + help_text='Authentication cipher' + ) class Meta: model = WirelessLAN - fields = ('ssid', 'group', 'description', 'vlan') + fields = ('ssid', 'group', 'description', 'vlan', 'auth_type', 'auth_cipher', 'auth_psk') class WirelessLinkCSVForm(CustomFieldModelCSVForm): @@ -55,7 +66,17 @@ class WirelessLinkCSVForm(CustomFieldModelCSVForm): interface_b = CSVModelChoiceField( queryset=Interface.objects.all() ) + auth_type = CSVChoiceField( + choices=WirelessAuthTypeChoices, + required=False, + help_text='Authentication type' + ) + auth_cipher = CSVChoiceField( + choices=WirelessAuthCipherChoices, + required=False, + help_text='Authentication cipher' + ) class Meta: model = WirelessLink - fields = ('interface_a', 'interface_b', 'ssid', 'description') + fields = ('interface_a', 'interface_b', 'ssid', 'description', 'auth_type', 'auth_cipher', 'auth_psk') diff --git a/netbox/wireless/forms/filtersets.py b/netbox/wireless/forms/filtersets.py index 13aae99a5..483d74a7c 100644 --- a/netbox/wireless/forms/filtersets.py +++ b/netbox/wireless/forms/filtersets.py @@ -6,6 +6,7 @@ from extras.forms import CustomFieldModelFilterForm from utilities.forms import ( add_blank_choice, BootstrapMixin, DynamicModelMultipleChoiceField, StaticSelect, TagFilterField, ) +from wireless.choices import * from wireless.models import * __all__ = ( @@ -52,6 +53,19 @@ class WirelessLANFilterForm(BootstrapMixin, CustomFieldModelFilterForm): label=_('Group'), fetch_trigger='open' ) + auth_type = forms.ChoiceField( + required=False, + choices=add_blank_choice(WirelessAuthTypeChoices), + widget=StaticSelect() + ) + auth_cipher = forms.ChoiceField( + required=False, + choices=add_blank_choice(WirelessAuthCipherChoices), + widget=StaticSelect() + ) + auth_psk = forms.CharField( + required=False + ) tag = TagFilterField(model) @@ -74,4 +88,17 @@ class WirelessLinkFilterForm(BootstrapMixin, CustomFieldModelFilterForm): choices=add_blank_choice(LinkStatusChoices), widget=StaticSelect() ) + auth_type = forms.ChoiceField( + required=False, + choices=add_blank_choice(WirelessAuthTypeChoices), + widget=StaticSelect() + ) + auth_cipher = forms.ChoiceField( + required=False, + choices=add_blank_choice(WirelessAuthCipherChoices), + widget=StaticSelect() + ) + auth_psk = forms.CharField( + required=False + ) tag = TagFilterField(model) diff --git a/netbox/wireless/forms/models.py b/netbox/wireless/forms/models.py index 9a7b78b31..aa453ba64 100644 --- a/netbox/wireless/forms/models.py +++ b/netbox/wireless/forms/models.py @@ -35,7 +35,8 @@ class WirelessLANForm(BootstrapMixin, CustomFieldModelForm): ) vlan = DynamicModelChoiceField( queryset=VLAN.objects.all(), - required=False + required=False, + label='VLAN' ) tags = DynamicModelMultipleChoiceField( queryset=Tag.objects.all(), @@ -45,12 +46,17 @@ class WirelessLANForm(BootstrapMixin, CustomFieldModelForm): class Meta: model = WirelessLAN fields = [ - 'ssid', 'group', 'description', 'vlan', 'tags', + 'ssid', 'group', 'description', 'vlan', 'auth_type', 'auth_cipher', 'auth_psk', 'tags', ] fieldsets = ( ('Wireless LAN', ('ssid', 'group', 'description', 'tags')), ('VLAN', ('vlan',)), + ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')), ) + widgets = { + 'auth_type': StaticSelect, + 'auth_cipher': StaticSelect, + } class WirelessLinkForm(BootstrapMixin, CustomFieldModelForm): @@ -94,8 +100,15 @@ class WirelessLinkForm(BootstrapMixin, CustomFieldModelForm): class Meta: model = WirelessLink fields = [ - 'device_a', 'interface_a', 'device_b', 'interface_b', 'status', 'ssid', 'description', 'tags', + 'device_a', 'interface_a', 'device_b', 'interface_b', 'status', 'ssid', 'description', 'auth_type', + 'auth_cipher', 'auth_psk', 'tags', ] + fieldsets = ( + ('Link', ('device_a', 'interface_a', 'device_b', 'interface_b', 'status', 'ssid', 'description', 'tags')), + ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')), + ) widgets = { 'status': StaticSelect, + 'auth_type': StaticSelect, + 'auth_cipher': StaticSelect, } diff --git a/netbox/wireless/migrations/0002_wireless_auth.py b/netbox/wireless/migrations/0002_wireless_auth.py new file mode 100644 index 000000000..9ca4e351c --- /dev/null +++ b/netbox/wireless/migrations/0002_wireless_auth.py @@ -0,0 +1,41 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wireless', '0001_wireless'), + ] + + operations = [ + migrations.AddField( + model_name='wirelesslan', + name='auth_cipher', + field=models.CharField(blank=True, max_length=50), + ), + migrations.AddField( + model_name='wirelesslan', + name='auth_psk', + field=models.CharField(blank=True, max_length=64), + ), + migrations.AddField( + model_name='wirelesslan', + name='auth_type', + field=models.CharField(blank=True, max_length=50), + ), + migrations.AddField( + model_name='wirelesslink', + name='auth_cipher', + field=models.CharField(blank=True, max_length=50), + ), + migrations.AddField( + model_name='wirelesslink', + name='auth_psk', + field=models.CharField(blank=True, max_length=64), + ), + migrations.AddField( + model_name='wirelesslink', + name='auth_type', + field=models.CharField(blank=True, max_length=50), + ), + ] diff --git a/netbox/wireless/models.py b/netbox/wireless/models.py index b0cacde15..43818279e 100644 --- a/netbox/wireless/models.py +++ b/netbox/wireless/models.py @@ -8,7 +8,8 @@ from dcim.constants import WIRELESS_IFACE_TYPES from extras.utils import extras_features from netbox.models import BigIDModel, NestedGroupModel, PrimaryModel from utilities.querysets import RestrictedQuerySet -from .constants import SSID_MAX_LENGTH +from .choices import * +from .constants import * __all__ = ( 'WirelessLAN', @@ -17,6 +18,30 @@ __all__ = ( ) +class WirelessAuthenticationBase(models.Model): + """ + Abstract model for attaching attributes related to wireless authentication. + """ + auth_type = models.CharField( + max_length=50, + choices=WirelessAuthTypeChoices, + blank=True + ) + auth_cipher = models.CharField( + max_length=50, + choices=WirelessAuthCipherChoices, + blank=True + ) + auth_psk = models.CharField( + max_length=PSK_MAX_LENGTH, + blank=True, + verbose_name='Pre-shared key' + ) + + class Meta: + abstract = True + + @extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class WirelessLANGroup(NestedGroupModel): """ @@ -49,12 +74,15 @@ class WirelessLANGroup(NestedGroupModel): ('parent', 'name') ) + def __str__(self): + return self.name + def get_absolute_url(self): return reverse('wireless:wirelesslangroup', args=[self.pk]) @extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks') -class WirelessLAN(PrimaryModel): +class WirelessLAN(WirelessAuthenticationBase, PrimaryModel): """ A wireless network formed among an arbitrary number of access point and clients. """ @@ -95,7 +123,7 @@ class WirelessLAN(PrimaryModel): @extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks') -class WirelessLink(PrimaryModel): +class WirelessLink(WirelessAuthenticationBase, PrimaryModel): """ A point-to-point connection between two wireless Interfaces. """ diff --git a/netbox/wireless/tables.py b/netbox/wireless/tables.py index 486fa2a71..ec8f3ddd2 100644 --- a/netbox/wireless/tables.py +++ b/netbox/wireless/tables.py @@ -48,8 +48,11 @@ class WirelessLANTable(BaseTable): class Meta(BaseTable.Meta): model = WirelessLAN - fields = ('pk', 'ssid', 'group', 'description', 'vlan', 'interface_count', 'tags') - default_columns = ('pk', 'ssid', 'group', 'description', 'vlan', 'interface_count') + fields = ( + 'pk', 'ssid', 'group', 'description', 'vlan', 'interface_count', 'auth_type', 'auth_cipher', 'auth_psk', + 'tags', + ) + default_columns = ('pk', 'ssid', 'group', 'description', 'vlan', 'auth_type', 'interface_count') class WirelessLANInterfacesTable(BaseTable): @@ -94,7 +97,11 @@ class WirelessLinkTable(BaseTable): class Meta(BaseTable.Meta): model = WirelessLink - fields = ('pk', 'id', 'status', 'device_a', 'interface_a', 'device_b', 'interface_b', 'ssid', 'description') - default_columns = ( + fields = ( 'pk', 'id', 'status', 'device_a', 'interface_a', 'device_b', 'interface_b', 'ssid', 'description', + 'auth_type', 'auth_cipher', 'auth_psk', 'tags', + ) + default_columns = ( + 'pk', 'id', 'status', 'device_a', 'interface_a', 'device_b', 'interface_b', 'ssid', 'auth_type', + 'description', )