diff --git a/netbox_acls/api/serializers.py b/netbox_acls/api/serializers.py index e2e42531..6fd85f99 100644 --- a/netbox_acls/api/serializers.py +++ b/netbox_acls/api/serializers.py @@ -5,7 +5,7 @@ from django.contrib.contenttypes.models import ContentType from drf_spectacular.utils import extend_schema_field -from ipam.api.serializers import PrefixSerializer +from ipam.api.serializers import PrefixSerializer, IPRangeSerializer, IPAddressSerializer, AggregateSerializer, ServiceSerializer from netbox.api.fields import ContentTypeField from netbox.api.serializers import NetBoxModelSerializer from rest_framework import serializers @@ -27,14 +27,20 @@ "ACLExtendedRuleSerializer", ] + # Sets a standard error message for ACL rules with an action of remark, but no remark set. error_message_no_remark = "Action is set to remark, you MUST add a remark." -# Sets a standard error message for ACL rules with an action of remark, but no source_prefix is set. -error_message_action_remark_source_prefix_set = "Action is set to remark, Source Prefix CANNOT be set." +# Sets a standard error message for ACL rules with an action of remark, but no source/destination is set. +error_message_action_remark_source_set = "Action is set to remark, Source CANNOT be set." +error_message_action_remark_destination_set = "Action is set to remark, Destination CANNOT be set." # Sets a standard error message for ACL rules with an action not set to remark, but no remark is set. error_message_remark_without_action_remark = "CANNOT set remark unless action is set to remark." # Sets a standard error message for ACL rules no associated to an ACL of the same type. error_message_acl_type = "Provided parent Access List is not of right type." +# Sets a standard error message for ACL rules when more than one IP/Host sources are set. +error_message_sources_more_than_one = "Only one IP/Host related Source can be specified." +# Sets a standard error message for ACL rules when more than one IP/Host destinations are set. +error_message_destinations_more_than_one = "Only one IP/Host related Destination can be specified." class AccessListSerializer(NetBoxModelSerializer): @@ -190,6 +196,30 @@ class ACLStandardRuleSerializer(NetBoxModelSerializer): default=None, nested=True ) + source_iprange = IPRangeSerializer( + required=False, + allow_null=True, + default=None, + nested=True + ) + source_ipaddress = IPAddressSerializer( + required=False, + allow_null=True, + default=None, + nested=True + ) + source_aggregate = AggregateSerializer( + required=False, + allow_null=True, + default=None, + nested=True + ) + source_service = ServiceSerializer( + required=False, + allow_null=True, + default=None, + nested=True + ) class Meta: """ @@ -211,6 +241,10 @@ class Meta: "custom_fields", "last_updated", "source_prefix", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service", ) brief_fields = ("id", "url", "display") @@ -218,21 +252,28 @@ def validate(self, data): """ Validate the ACLStandardRule django model's inputs before allowing it to update the instance: - Check if action set to remark, but no remark set. - - Check if action set to remark, but source_prefix set. + - Check if action set to remark, but source set. + - Check not more than one source is set. """ error_message = {} + sources = ["source_prefix", "source_iprange", "source_ipaddress", "source_aggregate", "source_service"] + if data.get("action") == "remark": # Check if action set to remark, but no remark set. if data.get("remark") is None: error_message["remark"] = [ error_message_no_remark, ] - # Check if action set to remark, but source_prefix set. - if data.get("source_prefix"): - error_message["source_prefix"] = [ - error_message_action_remark_source_prefix_set, - ] + # Check if action set to remark, but source set. + if any(data.get(source) for source in sources): + for source in sources: + error_message[source] = [error_message_action_remark_source_set] + + # Check not more than one source is set. + if sum(bool(data.get(source)) for source in sources) > 1: + for source in sources: + error_message[source] = [error_message_sources_more_than_one] if error_message: raise serializers.ValidationError(error_message) @@ -249,18 +290,68 @@ class ACLExtendedRuleSerializer(NetBoxModelSerializer): view_name="plugins-api:netbox_acls-api:aclextendedrule-detail", ) access_list = NestedAccessListSerializer() + source_prefix = PrefixSerializer( required=False, allow_null=True, default=None, nested=True ) + source_iprange = IPRangeSerializer( + required=False, + allow_null=True, + default=None, + nested=True + ) + source_ipaddress = IPAddressSerializer( + required=False, + allow_null=True, + default=None, + nested=True + ) + source_aggregate = AggregateSerializer( + required=False, + allow_null=True, + default=None, + nested=True + ) + source_service = ServiceSerializer( + required=False, + allow_null=True, + default=None, + nested=True + ) + destination_prefix = PrefixSerializer( required=False, allow_null=True, default=None, nested=True ) + destination_iprange = IPRangeSerializer( + required=False, + allow_null=True, + default=None, + nested=True + ) + destination_ipaddress = IPAddressSerializer( + required=False, + allow_null=True, + default=None, + nested=True + ) + destination_aggregate = AggregateSerializer( + required=False, + allow_null=True, + default=None, + nested=True + ) + destination_service = ServiceSerializer( + required=False, + allow_null=True, + default=None, + nested=True + ) class Meta: """ @@ -280,9 +371,20 @@ class Meta: "created", "custom_fields", "last_updated", + "source_prefix", - "source_ports", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service", + "destination_prefix", + "destination_iprange", + "destination_ipaddress", + "destination_aggregate", + "destination_service", + + "source_ports", "destination_ports", "protocol", "remark", @@ -292,36 +394,38 @@ def validate(self, data): """ Validate the ACLExtendedRule django model's inputs before allowing it to update the instance: - Check if action set to remark, but no remark set. - - Check if action set to remark, but source_prefix set. + - Check if action set to remark, but source set. + - Check if action set to remark, but destination set. - Check if action set to remark, but source_ports set. - - Check if action set to remark, but destination_prefix set. - Check if action set to remark, but destination_ports set. - Check if action set to remark, but protocol set. - - Check if action set to remark, but protocol set. + - Check not more than one source is set. + - Check not more than one destination is set. """ error_message = {} + sources = ["source_prefix", "source_iprange", "source_ipaddress", "source_aggregate", "source_service"] + destinations = ["destination_prefix", "destination_iprange", "destination_ipaddress", "destination_aggregate", "destination_service"] + if data.get("action") == "remark": # Check if action set to remark, but no remark set. if data.get("remark") is None: error_message["remark"] = [ error_message_no_remark, ] - # Check if action set to remark, but source_prefix set. - if data.get("source_prefix"): - error_message["source_prefix"] = [ - error_message_action_remark_source_prefix_set, - ] + # Check if action set to remark, but source set. + if any(data.get(source) for source in sources): + for source in sources: + error_message[source] = [error_message_action_remark_source_set] + # Check if action set to remark, but destination set. + if any(data.get(destination) for destination in destinations): + for destination in destinations: + error_message[destination] = [error_message_action_remark_destination_set] # Check if action set to remark, but source_ports set. if data.get("source_ports"): error_message["source_ports"] = [ "Action is set to remark, Source Ports CANNOT be set.", ] - # Check if action set to remark, but destination_prefix set. - if data.get("destination_prefix"): - error_message["destination_prefix"] = [ - "Action is set to remark, Destination Prefix CANNOT be set.", - ] # Check if action set to remark, but destination_ports set. if data.get("destination_ports"): error_message["destination_ports"] = [ @@ -332,6 +436,16 @@ def validate(self, data): error_message["protocol"] = [ "Action is set to remark, Protocol CANNOT be set.", ] + + # Check not more than one source is set. + if sum(bool(data.get(source)) for source in sources) > 1: + for source in sources: + error_message[source] = [error_message_sources_more_than_one] + + # Check not more than one destination is set. + if sum(bool(data.get(destination)) for destination in destinations) > 1: + for destination in destinations: + error_message[destination] = [error_message_destinations_more_than_one] if error_message: raise serializers.ValidationError(error_message) diff --git a/netbox_acls/api/views.py b/netbox_acls/api/views.py index cdff48d1..a6587786 100644 --- a/netbox_acls/api/views.py +++ b/netbox_acls/api/views.py @@ -60,7 +60,7 @@ class ACLStandardRuleViewSet(NetBoxModelViewSet): queryset = models.ACLStandardRule.objects.prefetch_related( "access_list", "tags", - "source_prefix", + "source_prefix", "source_iprange", "source_ipaddress", "source_aggregate", "source_service" ) serializer_class = ACLStandardRuleSerializer filterset_class = filtersets.ACLStandardRuleFilterSet @@ -74,8 +74,8 @@ class ACLExtendedRuleViewSet(NetBoxModelViewSet): queryset = models.ACLExtendedRule.objects.prefetch_related( "access_list", "tags", - "source_prefix", - "destination_prefix", + "source_prefix", "source_iprange", "source_ipaddress", "source_aggregate", "source_service", + "destination_prefix", "destination_iprange", "destination_ipaddress", "destination_aggregate", "destination_service", ) serializer_class = ACLExtendedRuleSerializer filterset_class = filtersets.ACLExtendedRuleFilterSet diff --git a/netbox_acls/filtersets.py b/netbox_acls/filtersets.py index 7a8e6d04..97852335 100644 --- a/netbox_acls/filtersets.py +++ b/netbox_acls/filtersets.py @@ -181,7 +181,17 @@ class Meta: """ model = ACLStandardRule - fields = ("id", "access_list", "index", "action") + fields = ( + "id", + "access_list", + "index", + "action", + "source_prefix", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service", + ) def search(self, queryset, name, value): """ @@ -191,6 +201,11 @@ def search(self, queryset, name, value): Q(access_list__name__icontains=value) | Q(index__icontains=value) | Q(action__icontains=value) + | Q(source_prefix__icontains=value) + | Q(source_iprange__icontains=value) + | Q(source_ipaddress__icontains=value) + | Q(source_aggregate__icontains=value) + | Q(source_service__icontains=value) ) return queryset.filter(query) @@ -206,7 +221,23 @@ class Meta: """ model = ACLExtendedRule - fields = ("id", "access_list", "index", "action", "protocol") + fields = ( + "id", + "access_list", + "index", + "action", + "protocol", + "source_prefix", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service", + "destination_prefix", + "destination_iprange", + "destination_ipaddress", + "destination_aggregate", + "destination_service" + ) def search(self, queryset, name, value): """ @@ -217,5 +248,15 @@ def search(self, queryset, name, value): | Q(index__icontains=value) | Q(action__icontains=value) | Q(protocol__icontains=value) + | Q(source_prefix__icontains=value) + | Q(source_iprange__icontains=value) + | Q(source_ipaddress__icontains=value) + | Q(source_aggregate__icontains=value) + | Q(source_service__icontains=value) + | Q(destination_prefix__icontains=value) + | Q(destination_iprange__icontains=value) + | Q(destination_ipaddress__icontains=value) + | Q(destination_aggregate__icontains=value) + | Q(destination_service__icontains=value) ) return queryset.filter(query) diff --git a/netbox_acls/forms/filtersets.py b/netbox_acls/forms/filtersets.py index 41715ffd..c9eda7f7 100644 --- a/netbox_acls/forms/filtersets.py +++ b/netbox_acls/forms/filtersets.py @@ -5,7 +5,13 @@ from dcim.models import Device, Interface, Region, Site, SiteGroup, VirtualChassis from django import forms from django.utils.translation import gettext as _ -from ipam.models import Prefix +from ipam.models import ( + Prefix, + IPRange, + IPAddress, + Aggregate, + Service, +) from netbox.forms import NetBoxModelFilterSetForm from utilities.forms.rendering import FieldSet from utilities.forms.fields import ( @@ -14,6 +20,7 @@ TagFilterField, ) from utilities.forms.utils import add_blank_choice +from utilities.forms.rendering import FieldSet, TabbedGroups from virtualization.models import VirtualMachine, VMInterface @@ -181,15 +188,56 @@ class ACLStandardRuleFilterForm(NetBoxModelFilterSetForm): required=False, label="Source Prefix", ) + source_prefix = DynamicModelMultipleChoiceField( + queryset=Prefix.objects.all(), + required=False, + label="Source Prefix", + ) + source_iprange = DynamicModelMultipleChoiceField( + queryset=IPRange.objects.all(), + required=False, + label="Source IP-Range", + ) + source_ipaddress = DynamicModelMultipleChoiceField( + queryset=IPAddress.objects.all(), + required=False, + label="Source IP-Address", + ) + source_aggregate = DynamicModelMultipleChoiceField( + queryset=Aggregate.objects.all(), + required=False, + label="Source Aggregate", + ) + source_service = DynamicModelMultipleChoiceField( + queryset=Service.objects.all(), + required=False, + label="Source Service", + ) action = forms.ChoiceField( choices=add_blank_choice(ACLRuleActionChoices), required=False, ) fieldsets = ( - FieldSet("access_list", "action", "source_prefix", name=_('Rule Details')), - FieldSet("q", "tag",name=None) + FieldSet("q", "tag",name=None), + FieldSet( + "access_list", + "action", + name=_('Rule Details') + ), + FieldSet( + TabbedGroups( + FieldSet('source_prefix', name=_('Prefix')), + FieldSet('source_iprange', name=_('IP Range')), + FieldSet('source_ipaddress', name=_('IP Address')), + FieldSet('source_aggregate', name=_('Aggregate')), + FieldSet('source_service', name=_('Service')), + ) + ) ) + + + class ACLExtendedRuleFilterForm(NetBoxModelFilterSetForm): """ GUI filter form to search the django ACLExtendedRule model. @@ -213,17 +261,88 @@ class ACLExtendedRuleFilterForm(NetBoxModelFilterSetForm): required=False, label="Source Prefix", ) - desintation_prefix = DynamicModelMultipleChoiceField( + source_prefix = DynamicModelMultipleChoiceField( + queryset=Prefix.objects.all(), + required=False, + label="Source Prefix", + ) + source_iprange = DynamicModelMultipleChoiceField( + queryset=IPRange.objects.all(), + required=False, + label="Source IP-Range", + ) + source_ipaddress = DynamicModelMultipleChoiceField( + queryset=IPAddress.objects.all(), + required=False, + label="Source IP-Address", + ) + source_aggregate = DynamicModelMultipleChoiceField( + queryset=Aggregate.objects.all(), + required=False, + label="Source Aggregate", + ) + source_service = DynamicModelMultipleChoiceField( + queryset=Service.objects.all(), + required=False, + label="Source Service", + ) + + destination_prefix = DynamicModelMultipleChoiceField( queryset=Prefix.objects.all(), required=False, label="Destination Prefix", ) + destination_iprange = DynamicModelMultipleChoiceField( + queryset=IPRange.objects.all(), + required=False, + label="Destination IP-Range", + ) + destination_ipaddress = DynamicModelMultipleChoiceField( + queryset=IPAddress.objects.all(), + required=False, + label="Destination IP-Address", + ) + destination_aggregate = DynamicModelMultipleChoiceField( + queryset=Aggregate.objects.all(), + required=False, + label="Destination Aggregate", + ) + destination_service = DynamicModelMultipleChoiceField( + queryset=Service.objects.all(), + required=False, + label="Destination Service", + ) protocol = forms.ChoiceField( choices=add_blank_choice(ACLProtocolChoices), required=False, ) fieldsets = ( - FieldSet("access_list", "action", "source_prefix", "desintation_prefix", "protocol", name=_('Rule Details')), - FieldSet("q", "tag",name=None) + FieldSet("q", "tag",name=None), + FieldSet( + "access_list", + "action", + "protocol", + name=_('Rule Details') + ), + FieldSet( + TabbedGroups( + FieldSet('source_prefix', name=_('Prefix')), + FieldSet('source_iprange', name=_('IP Range')), + FieldSet('source_ipaddress', name=_('IP Address')), + FieldSet('source_aggregate', name=_('Aggregate')), + FieldSet('source_service', name=_('Service')), + ), + "source_ports", + ), + FieldSet( + TabbedGroups( + FieldSet('destination_prefix', name=_('Prefix')), + FieldSet('destination_iprange', name=_('IP Range')), + FieldSet('destination_ipaddress', name=_('IP Address')), + FieldSet('destination_aggregate', name=_('Aggregate')), + FieldSet('destination_service', name=_('Service')), + ), + "destination_ports", + ), ) diff --git a/netbox_acls/forms/models.py b/netbox_acls/forms/models.py index d25bab2b..2cde4cdb 100644 --- a/netbox_acls/forms/models.py +++ b/netbox_acls/forms/models.py @@ -6,7 +6,14 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.utils.safestring import mark_safe -from ipam.models import Prefix +from django.utils.translation import gettext_lazy as _ +from ipam.models import ( + Prefix, + IPRange, + IPAddress, + Aggregate, + Service, +) from netbox.forms import NetBoxModelForm from utilities.forms.rendering import FieldSet from utilities.forms.fields import CommentField, DynamicModelChoiceField @@ -17,7 +24,7 @@ VirtualMachine, VMInterface, ) - +from utilities.forms.rendering import FieldSet, TabbedGroups from ..choices import ACLTypeChoices from ..models import ( AccessList, @@ -44,11 +51,18 @@ # Sets a standard error message for ACL rules with an action of remark, but no remark set. error_message_no_remark = "Action is set to remark, you MUST add a remark." -# Sets a standard error message for ACL rules with an action of remark, but no source_prefix is set. -error_message_action_remark_source_prefix_set = "Action is set to remark, Source Prefix CANNOT be set." # Sets a standard error message for ACL rules with an action not set to remark, but no remark is set. error_message_remark_without_action_remark = "CANNOT set remark unless action is set to remark." +# Sets a standard error message for ACL rules with an action of remark, but no source is set. +error_message_action_remark_source_set = "Action is set to remark, Source CANNOT be set." +# Sets a standard error message for ACL rules with an action of remark, but no destination is set. +error_message_action_remark_destination_set = "Action is set to remark, Destination CANNOT be set." + +# Sets a standard error message for ACL rules when more than one IP/Host sources are set. +error_message_sources_more_than_one = "Only one IP/Host related Source can be specified." +# Sets a standard error message for ACL rules when more than one IP/Host destinations are set. +error_message_destinations_more_than_one = "Only one IP/Host related Destination can be specified." class AccessListForm(NetBoxModelForm): """ @@ -445,17 +459,64 @@ class ACLStandardRuleForm(NetBoxModelForm): ), label="Access List", ) + source_prefix = DynamicModelChoiceField( queryset=Prefix.objects.all(), required=False, help_text=help_text_acl_rule_logic, label="Source Prefix", ) + source_iprange = DynamicModelChoiceField( + queryset=IPRange.objects.all(), + required=False, + help_text=help_text_acl_rule_logic, + label="Source IP-Range", + ) + source_ipaddress = DynamicModelChoiceField( + queryset=IPAddress.objects.all(), + required=False, + help_text=help_text_acl_rule_logic, + label="Source IP-Address", + ) + source_aggregate = DynamicModelChoiceField( + queryset=Aggregate.objects.all(), + required=False, + help_text=help_text_acl_rule_logic, + label="Source Aggregate", + ) + source_service = DynamicModelChoiceField( + queryset=Service.objects.all(), + required=False, + help_text=help_text_acl_rule_logic, + label="Source Service", + ) fieldsets = ( - FieldSet("access_list", "description", "tags", name=_('Access List Details')), - FieldSet("index", "action", "remark", "source_prefix", name=_('Rule Definition')) + FieldSet( + "access_list", + "description", + "tags", + name=_('Access List Details') + ), + FieldSet( + "index", + "action", + "remark", + name=_('Rule Definition') + ), + FieldSet( + TabbedGroups( + FieldSet('source_prefix', name=_('Prefix')), + FieldSet('source_iprange', name=_('IP Range')), + FieldSet('source_ipaddress', name=_('IP Address')), + FieldSet('source_aggregate', name=_('Aggregate')), + FieldSet('source_service', name=_('Service')), + ) + ) ) + + + class Meta: model = ACLStandardRule fields = ( @@ -463,7 +524,13 @@ class Meta: "index", "action", "remark", + "source_prefix", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service", + "tags", "description", ) @@ -475,7 +542,7 @@ class Meta: "index": help_text_acl_rule_index, "action": help_text_acl_action, "remark": mark_safe( - "*Note: CANNOT be set if source prefix OR action is set.", + "*Note: CANNOT be set if source OR action is set.", ), } @@ -483,8 +550,9 @@ def clean(self): """ Validates form inputs before submitting: - Check if action set to remark, but no remark set. - - Check if action set to remark, but source_prefix set. + - Check if action set to remark, but source set. - Check remark set, but action not set to remark. + - Check not more than one source is set. """ super().clean() cleaned_data = self.cleaned_data @@ -492,18 +560,23 @@ def clean(self): action = cleaned_data.get("action") remark = cleaned_data.get("remark") - source_prefix = cleaned_data.get("source_prefix") + sources = ["source_prefix", "source_iprange", "source_ipaddress", "source_aggregate", "source_service"] if action == "remark": # Check if action set to remark, but no remark set. if not remark: error_message["remark"] = [error_message_no_remark] - # Check if action set to remark, but source_prefix set. - if source_prefix: - error_message["source_prefix"] = [error_message_action_remark_source_prefix_set] + # Check if action set to remark, but source set. + if any(cleaned_data.get(source) for source in sources): + for source in sources: + error_message[source] = [error_message_action_remark_source_set] # Check remark set, but action not set to remark. elif remark: error_message["remark"] = [error_message_remark_without_action_remark] + # Check not more than one source is set. + elif sum(bool(cleaned_data.get(source)) for source in sources) > 1: + for source in sources: + error_message[source] = [error_message_sources_more_than_one] if error_message: raise ValidationError(error_message) @@ -533,15 +606,96 @@ class ACLExtendedRuleForm(NetBoxModelForm): help_text=help_text_acl_rule_logic, label="Source Prefix", ) + source_iprange = DynamicModelChoiceField( + queryset=IPRange.objects.all(), + required=False, + help_text=help_text_acl_rule_logic, + label="Source IP-Range", + ) + source_ipaddress = DynamicModelChoiceField( + queryset=IPAddress.objects.all(), + required=False, + help_text=help_text_acl_rule_logic, + label="Source IP-Address", + ) + source_aggregate = DynamicModelChoiceField( + queryset=Aggregate.objects.all(), + required=False, + help_text=help_text_acl_rule_logic, + label="Source Aggregate", + ) + source_service = DynamicModelChoiceField( + queryset=Service.objects.all(), + required=False, + help_text=help_text_acl_rule_logic, + label="Source Service", + ) + destination_prefix = DynamicModelChoiceField( queryset=Prefix.objects.all(), required=False, help_text=help_text_acl_rule_logic, label="Destination Prefix", ) + destination_iprange = DynamicModelChoiceField( + queryset=IPRange.objects.all(), + required=False, + help_text=help_text_acl_rule_logic, + label="Destination IP-Range", + ) + destination_ipaddress = DynamicModelChoiceField( + queryset=IPAddress.objects.all(), + required=False, + help_text=help_text_acl_rule_logic, + label="Destination IP-Address", + ) + destination_aggregate = DynamicModelChoiceField( + queryset=Aggregate.objects.all(), + required=False, + help_text=help_text_acl_rule_logic, + label="Destination Aggregate", + ) + destination_service = DynamicModelChoiceField( + queryset=Service.objects.all(), + required=False, + help_text=help_text_acl_rule_logic, + label="Destination Service", + ) + fieldsets = ( - FieldSet("access_list", "description", "tags", name=_('Access List Details')), - FieldSet("index", "action", "remark", "source_prefix", "source_ports", "destination_prefix", "destination_ports", "protocol", name=_('Rule Definition')) + FieldSet( + "access_list", + "description", + "tags", + name=_('Access List Details') + ), + FieldSet( + "index", + "action", + "remark", + "protocol", + name=_('Rule Definition') + ), + FieldSet( + TabbedGroups( + FieldSet('source_prefix', name=_('Prefix')), + FieldSet('source_iprange', name=_('IP Range')), + FieldSet('source_ipaddress', name=_('IP Address')), + FieldSet('source_aggregate', name=_('Aggregate')), + FieldSet('source_service', name=_('Service')), + ), + "source_ports", + ), + FieldSet( + TabbedGroups( + FieldSet('destination_prefix', name=_('Prefix')), + FieldSet('destination_iprange', name=_('IP Range')), + FieldSet('destination_ipaddress', name=_('IP Address')), + FieldSet('destination_aggregate', name=_('Aggregate')), + FieldSet('destination_service', name=_('Service')), + ), + "destination_ports", + ), ) class Meta: model = ACLExtendedRule @@ -550,9 +704,20 @@ class Meta: "index", "action", "remark", + "source_prefix", - "source_ports", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service", + "destination_prefix", + "destination_iprange", + "destination_ipaddress", + "destination_aggregate", + "destination_service", + + "source_ports", "destination_ports", "protocol", "tags", @@ -560,26 +725,26 @@ class Meta: ) help_texts = { - "action": help_text_acl_action, - "destination_ports": help_text_acl_rule_logic, "index": help_text_acl_rule_index, - "protocol": help_text_acl_rule_logic, + "action": help_text_acl_action, "remark": mark_safe( "*Note: CANNOT be set if action is not set to remark.", ), "source_ports": help_text_acl_rule_logic, + "destination_ports": help_text_acl_rule_logic, + "protocol": help_text_acl_rule_logic, } def clean(self): """ Validates form inputs before submitting: - Check if action set to remark, but no remark set. - - Check if action set to remark, but source_prefix set. - - Check if action set to remark, but source_ports set. - - Check if action set to remark, but destination_prefix set. - - Check if action set to remark, but destination_ports set. - - Check if action set to remark, but protocol set. + - Check if action set to remark, but source set. + - Check if action set to remark, but destination set. + - Check if action set to remark, but protocol set - Check remark set, but action not set to remark. + - Check not more than one source is set. + - Check not more than one destination is set. """ super().clean() cleaned_data = self.cleaned_data @@ -587,21 +752,28 @@ def clean(self): action = cleaned_data.get("action") remark = cleaned_data.get("remark") - source_prefix = cleaned_data.get("source_prefix") + + sources = ["source_prefix", "source_iprange", "source_ipaddress", "source_aggregate", "source_service"] + destinations = ["destination_prefix", "destination_iprange", "destination_ipaddress", "destination_aggregate", "destination_service"] + source_ports = cleaned_data.get("source_ports") - destination_prefix = cleaned_data.get("destination_prefix") destination_ports = cleaned_data.get("destination_ports") protocol = cleaned_data.get("protocol") if action == "remark": if not remark: error_message["remark"] = [error_message_no_remark] - if source_prefix: - error_message["source_prefix"] = [error_message_action_remark_source_prefix_set] + + # Check if action set to remark, but source set. + for source in sources: + error_message[source] = [error_message_action_remark_source_set] + + # Check if action set to remark, but destination set. + for destination in destinations: + error_message[destination] = [error_message_action_remark_destination_set] + if source_ports: error_message["source_ports"] = ["Action is set to remark, Source Ports CANNOT be set."] - if destination_prefix: - error_message["destination_prefix"] = ["Action is set to remark, Destination Prefix CANNOT be set."] if destination_ports: error_message["destination_ports"] = ["Action is set to remark, Destination Ports CANNOT be set."] if protocol: @@ -609,5 +781,15 @@ def clean(self): elif remark: error_message["remark"] = [error_message_remark_without_action_remark] + # Check not more than one source is set. + elif sum(bool(cleaned_data.get(source)) for source in sources) > 1: + for source in sources: + error_message[source] = [error_message_sources_more_than_one] + + # Check not more than one destination is set. + elif sum(bool(cleaned_data.get(destination)) for destination in destinations) > 1: + for destination in destinations: + error_message[destination] = [error_message_destinations_more_than_one] + if error_message: raise ValidationError(error_message) diff --git a/netbox_acls/migrations/0005_add_community_requested_source_and_destination_objects.py b/netbox_acls/migrations/0005_add_community_requested_source_and_destination_objects.py new file mode 100644 index 00000000..75ad4c34 --- /dev/null +++ b/netbox_acls/migrations/0005_add_community_requested_source_and_destination_objects.py @@ -0,0 +1,88 @@ +# Generated by Django 5.0.6 on 2024-09-02 15:10 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('extras', '0115_convert_dashboard_widgets'), + ('ipam', '0069_gfk_indexes'), + ('netbox_acls', '0004_netbox_acls'), + ] + + operations = [ + migrations.AddField( + model_name='aclextendedrule', + name='destination_aggregate', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='ipam.aggregate'), + ), + migrations.AddField( + model_name='aclextendedrule', + name='destination_ipaddress', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='ipam.ipaddress'), + ), + migrations.AddField( + model_name='aclextendedrule', + name='destination_iprange', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='ipam.iprange'), + ), + migrations.AddField( + model_name='aclextendedrule', + name='destination_service', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='ipam.service'), + ), + migrations.AddField( + model_name='aclextendedrule', + name='source_aggregate', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='ipam.aggregate'), + ), + migrations.AddField( + model_name='aclextendedrule', + name='source_ipaddress', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='ipam.ipaddress'), + ), + migrations.AddField( + model_name='aclextendedrule', + name='source_iprange', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='ipam.iprange'), + ), + migrations.AddField( + model_name='aclextendedrule', + name='source_service', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='ipam.service'), + ), + migrations.AddField( + model_name='aclstandardrule', + name='source_aggregate', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='ipam.aggregate'), + ), + migrations.AddField( + model_name='aclstandardrule', + name='source_ipaddress', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='ipam.ipaddress'), + ), + migrations.AddField( + model_name='aclstandardrule', + name='source_iprange', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='ipam.iprange'), + ), + migrations.AddField( + model_name='aclstandardrule', + name='source_service', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='ipam.service'), + ), + migrations.AddConstraint( + model_name='aclextendedrule', + constraint=models.CheckConstraint(check=models.Q(models.Q(('source_prefix__isnull', True), ('source_iprange__isnull', True), ('source_ipaddress__isnull', True), ('source_aggregate__isnull', True), ('source_service__isnull', True)), models.Q(('source_prefix__isnull', False), ('source_iprange__isnull', True), ('source_ipaddress__isnull', True), ('source_aggregate__isnull', True), ('source_service__isnull', True)), models.Q(('source_prefix__isnull', True), ('source_iprange__isnull', False), ('source_ipaddress__isnull', True), ('source_aggregate__isnull', True), ('source_service__isnull', True)), models.Q(('source_prefix__isnull', True), ('source_iprange__isnull', True), ('source_ipaddress__isnull', False), ('source_aggregate__isnull', True), ('source_service__isnull', True)), models.Q(('source_prefix__isnull', True), ('source_iprange__isnull', True), ('source_ipaddress__isnull', True), ('source_aggregate__isnull', False), ('source_service__isnull', True)), models.Q(('source_prefix__isnull', True), ('source_iprange__isnull', True), ('source_ipaddress__isnull', True), ('source_aggregate__isnull', True), ('source_service__isnull', False)), _connector='OR'), name='not_more_than_one_source_for_extended_rule'), + ), + migrations.AddConstraint( + model_name='aclextendedrule', + constraint=models.CheckConstraint(check=models.Q(models.Q(('destination_prefix__isnull', True), ('destination_iprange__isnull', True), ('destination_ipaddress__isnull', True), ('destination_aggregate__isnull', True), ('destination_service__isnull', True)), models.Q(('destination_prefix__isnull', False), ('destination_iprange__isnull', True), ('destination_ipaddress__isnull', True), ('destination_aggregate__isnull', True), ('destination_service__isnull', True)), models.Q(('destination_prefix__isnull', True), ('destination_iprange__isnull', False), ('destination_ipaddress__isnull', True), ('destination_aggregate__isnull', True), ('destination_service__isnull', True)), models.Q(('destination_prefix__isnull', True), ('destination_iprange__isnull', True), ('destination_ipaddress__isnull', False), ('destination_aggregate__isnull', True), ('destination_service__isnull', True)), models.Q(('destination_prefix__isnull', True), ('destination_iprange__isnull', True), ('destination_ipaddress__isnull', True), ('destination_aggregate__isnull', False), ('destination_service__isnull', True)), models.Q(('destination_prefix__isnull', True), ('destination_iprange__isnull', True), ('destination_ipaddress__isnull', True), ('destination_aggregate__isnull', True), ('destination_service__isnull', False)), _connector='OR'), name='not_more_than_one_destination_for_extended_rule'), + ), + migrations.AddConstraint( + model_name='aclstandardrule', + constraint=models.CheckConstraint(check=models.Q(models.Q(('source_prefix__isnull', True), ('source_iprange__isnull', True), ('source_ipaddress__isnull', True), ('source_aggregate__isnull', True), ('source_service__isnull', True)), models.Q(('source_prefix__isnull', False), ('source_iprange__isnull', True), ('source_ipaddress__isnull', True), ('source_aggregate__isnull', True), ('source_service__isnull', True)), models.Q(('source_prefix__isnull', True), ('source_iprange__isnull', False), ('source_ipaddress__isnull', True), ('source_aggregate__isnull', True), ('source_service__isnull', True)), models.Q(('source_prefix__isnull', True), ('source_iprange__isnull', True), ('source_ipaddress__isnull', False), ('source_aggregate__isnull', True), ('source_service__isnull', True)), models.Q(('source_prefix__isnull', True), ('source_iprange__isnull', True), ('source_ipaddress__isnull', True), ('source_aggregate__isnull', False), ('source_service__isnull', True)), models.Q(('source_prefix__isnull', True), ('source_iprange__isnull', True), ('source_ipaddress__isnull', True), ('source_aggregate__isnull', True), ('source_service__isnull', False)), _connector='OR'), name='not_more_than_one_source_for_standard_rule'), + ), + ] diff --git a/netbox_acls/models/access_list_rules.py b/netbox_acls/models/access_list_rules.py index 56567204..47c335ef 100644 --- a/netbox_acls/models/access_list_rules.py +++ b/netbox_acls/models/access_list_rules.py @@ -5,8 +5,10 @@ from django.apps import apps from django.contrib.postgres.fields import ArrayField from django.db import models +from django.db.models import Q from django.urls import reverse from netbox.models import NetBoxModel +from ipam.models import Prefix, IPRange, IPAddress, Aggregate, Service from ..choices import ACLProtocolChoices, ACLRuleActionChoices, ACLTypeChoices from .access_lists import AccessList @@ -17,7 +19,6 @@ "ACLExtendedRule", ) - class ACLRule(NetBoxModel): """ Abstract model for ACL Rules. @@ -43,16 +44,57 @@ class ACLRule(NetBoxModel): choices=ACLRuleActionChoices, max_length=30, ) + source_prefix = models.ForeignKey( blank=True, null=True, on_delete=models.PROTECT, related_name="+", - to="ipam.Prefix", + to=Prefix, verbose_name="Source Prefix", ) + source_iprange = models.ForeignKey( + blank=True, + null=True, + on_delete=models.PROTECT, + related_name="+", + to=IPRange, + verbose_name="Source IP-Range", + ) + source_ipaddress = models.ForeignKey( + blank=True, + null=True, + on_delete=models.PROTECT, + related_name="+", + to=IPAddress, + verbose_name="Source IP-Address", + ) + source_aggregate = models.ForeignKey( + blank=True, + null=True, + on_delete=models.PROTECT, + related_name="+", + to=Aggregate, + verbose_name="Source Aggregate", + ) + source_service = models.ForeignKey( + blank=True, + null=True, + on_delete=models.PROTECT, + related_name="+", + to=Service, + verbose_name="Source Service", + ) - clone_fields = ("access_list", "action", "source_prefix") + clone_fields = ( + "access_list", + "action", + "source_prefix", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service" + ) def __str__(self): return f"{self.access_list}: Rule {self.index}" @@ -62,7 +104,14 @@ def get_action_color(self): @classmethod def get_prerequisite_models(cls): - return [apps.get_model("ipam.Prefix"), AccessList] + return [ + Prefix, + IPRange, + IPAddress, + Aggregate, + Service, + AccessList + ] class Meta: """ @@ -99,7 +148,14 @@ def get_absolute_url(self): @classmethod def get_prerequisite_models(cls): - return [AccessList] + return [ + Prefix, + IPRange, + IPAddress, + Aggregate, + Service, + AccessList + ] class Meta(ACLRule.Meta): """ @@ -112,33 +168,93 @@ class Meta(ACLRule.Meta): verbose_name = "ACL Standard Rule" verbose_name_plural = "ACL Standard Rules" + constraints = [ + models.CheckConstraint( + check=( + ( + Q(source_prefix__isnull=True) & Q(source_iprange__isnull=True) & Q(source_ipaddress__isnull=True) & Q(source_aggregate__isnull=True) & Q(source_service__isnull=True) + ) | + ( + Q(source_prefix__isnull=False) & Q(source_iprange__isnull=True) & Q(source_ipaddress__isnull=True) & Q(source_aggregate__isnull=True) & Q(source_service__isnull=True) + ) | + ( + Q(source_prefix__isnull=True) & Q(source_iprange__isnull=False) & Q(source_ipaddress__isnull=True) & Q(source_aggregate__isnull=True) & Q(source_service__isnull=True) + ) | + ( + Q(source_prefix__isnull=True) & Q(source_iprange__isnull=True) & Q(source_ipaddress__isnull=False) & Q(source_aggregate__isnull=True) & Q(source_service__isnull=True) + ) | + ( + Q(source_prefix__isnull=True) & Q(source_iprange__isnull=True) & Q(source_ipaddress__isnull=True) & Q(source_aggregate__isnull=False) & Q(source_service__isnull=True) + ) | + ( + Q(source_prefix__isnull=True) & Q(source_iprange__isnull=True) & Q(source_ipaddress__isnull=True) & Q(source_aggregate__isnull=True) & Q(source_service__isnull=False) + ) + ), + name='not_more_than_one_source_for_standard_rule' + ) + ] + class ACLExtendedRule(ACLRule): """ Inherits ACLRule. - Add ACLExtendedRule specific fields: source_ports, desintation_prefix, destination_ports, and protocol + Add ACLExtendedRule specific fields: destination, source_ports, destination_ports and protocol """ access_list = models.ForeignKey( on_delete=models.CASCADE, to=AccessList, verbose_name="Extended Access List", - limit_choices_to={"type": "extended"}, + limit_choices_to={"type": ACLTypeChoices.TYPE_EXTENDED}, related_name="aclextendedrules", ) - source_ports = ArrayField( - base_field=models.PositiveIntegerField(), + + destination_prefix = models.ForeignKey( blank=True, null=True, - verbose_name="Soure Ports", + on_delete=models.PROTECT, + related_name="+", + to=Prefix, + verbose_name="Destination Prefix", ) - destination_prefix = models.ForeignKey( + destination_iprange = models.ForeignKey( blank=True, null=True, on_delete=models.PROTECT, related_name="+", - to="ipam.Prefix", - verbose_name="Destination Prefix", + to=IPRange, + verbose_name="Destination IP-Range", + ) + destination_ipaddress = models.ForeignKey( + blank=True, + null=True, + on_delete=models.PROTECT, + related_name="+", + to=IPAddress, + verbose_name="Destination IP-Address", + ) + destination_aggregate = models.ForeignKey( + blank=True, + null=True, + on_delete=models.PROTECT, + related_name="+", + to=Aggregate, + verbose_name="Destination Aggregate", + ) + destination_service = models.ForeignKey( + blank=True, + null=True, + on_delete=models.PROTECT, + related_name="+", + to=Service, + verbose_name="Destination Service", + ) + + source_ports = ArrayField( + base_field=models.PositiveIntegerField(), + blank=True, + null=True, + verbose_name="Source Ports", ) destination_ports = ArrayField( base_field=models.PositiveIntegerField(), @@ -164,7 +280,14 @@ def get_protocol_color(self): @classmethod def get_prerequisite_models(cls): - return [apps.get_model("ipam.Prefix"), AccessList] + return [ + Prefix, + IPRange, + IPAddress, + Aggregate, + Service, + AccessList + ] class Meta(ACLRule.Meta): """ @@ -176,3 +299,52 @@ class Meta(ACLRule.Meta): verbose_name = "ACL Extended Rule" verbose_name_plural = "ACL Extended Rules" + + constraints = [ + models.CheckConstraint( + check=( + ( + Q(source_prefix__isnull=True) & Q(source_iprange__isnull=True) & Q(source_ipaddress__isnull=True) & Q(source_aggregate__isnull=True) & Q(source_service__isnull=True) + ) | + ( + Q(source_prefix__isnull=False) & Q(source_iprange__isnull=True) & Q(source_ipaddress__isnull=True) & Q(source_aggregate__isnull=True) & Q(source_service__isnull=True) + ) | + ( + Q(source_prefix__isnull=True) & Q(source_iprange__isnull=False) & Q(source_ipaddress__isnull=True) & Q(source_aggregate__isnull=True) & Q(source_service__isnull=True) + ) | + ( + Q(source_prefix__isnull=True) & Q(source_iprange__isnull=True) & Q(source_ipaddress__isnull=False) & Q(source_aggregate__isnull=True) & Q(source_service__isnull=True) + ) | + ( + Q(source_prefix__isnull=True) & Q(source_iprange__isnull=True) & Q(source_ipaddress__isnull=True) & Q(source_aggregate__isnull=False) & Q(source_service__isnull=True) + ) | + ( + Q(source_prefix__isnull=True) & Q(source_iprange__isnull=True) & Q(source_ipaddress__isnull=True) & Q(source_aggregate__isnull=True) & Q(source_service__isnull=False) + ) + ), + name='not_more_than_one_source_for_extended_rule' + ), + models.CheckConstraint( + check=( + ( + Q(destination_prefix__isnull=True) & Q(destination_iprange__isnull=True) & Q(destination_ipaddress__isnull=True) & Q(destination_aggregate__isnull=True) & Q(destination_service__isnull=True) + ) | + ( + Q(destination_prefix__isnull=False) & Q(destination_iprange__isnull=True) & Q(destination_ipaddress__isnull=True) & Q(destination_aggregate__isnull=True) & Q(destination_service__isnull=True) + ) | + ( + Q(destination_prefix__isnull=True) & Q(destination_iprange__isnull=False) & Q(destination_ipaddress__isnull=True) & Q(destination_aggregate__isnull=True) & Q(destination_service__isnull=True) + ) | + ( + Q(destination_prefix__isnull=True) & Q(destination_iprange__isnull=True) & Q(destination_ipaddress__isnull=False) & Q(destination_aggregate__isnull=True) & Q(destination_service__isnull=True) + ) | + ( + Q(destination_prefix__isnull=True) & Q(destination_iprange__isnull=True) & Q(destination_ipaddress__isnull=True) & Q(destination_aggregate__isnull=False) & Q(destination_service__isnull=True) + ) | + ( + Q(destination_prefix__isnull=True) & Q(destination_iprange__isnull=True) & Q(destination_ipaddress__isnull=True) & Q(destination_aggregate__isnull=True) & Q(destination_service__isnull=False) + ) + ), + name='not_more_than_one_destination_for_extended_rule' + ) + ] \ No newline at end of file diff --git a/netbox_acls/tables.py b/netbox_acls/tables.py index b5a045ff..223cb781 100644 --- a/netbox_acls/tables.py +++ b/netbox_acls/tables.py @@ -22,7 +22,26 @@ {{ record.assigned_object.virtual_machine|placeholder }} {% endif %} """ - +COL_SOURCE_AND_DESTINATION_ASSIGNMENT = """ + {% if record.#replaceme#_prefix %} + {{ record.#replaceme#_prefix|placeholder }} + Prefix + {% elif record.#replaceme#_iprange %} + {{ record.#replaceme#_iprange|placeholder }} + IP-Range + {% elif record.#replaceme#_ipaddress %} + {{ record.#replaceme#_ipaddress|placeholder }} + IP-Address + {% elif record.#replaceme#_aggregate %} + {{ record.#replaceme#_aggregate|placeholder }} + Aggregate + {% elif record.#replaceme#_service %} + {{ record.#replaceme#_service|placeholder }} + Service + {% else %} + {{ ''|placeholder }} + {% endif %} + """ class AccessListTable(NetBoxTable): """ @@ -92,6 +111,7 @@ class ACLInterfaceAssignmentTable(NetBoxTable): direction = ChoiceFieldColumn() host = tables.TemplateColumn( template_code=COL_HOST_ASSIGNMENT, + orderable=False, ) assigned_object = tables.Column( linkify=True, @@ -138,6 +158,10 @@ class ACLStandardRuleTable(NetBoxTable): tags = columns.TagColumn( url_name="plugins:netbox_acls:aclstandardrule_list", ) + source = tables.TemplateColumn( + template_code=COL_SOURCE_AND_DESTINATION_ASSIGNMENT.replace('#replaceme#', 'source'), + order_by=('source_prefix', 'source_iprange', 'source_ipaddress', 'source_aggregate', 'source_service') + ) class Meta(NetBoxTable.Meta): model = ACLStandardRule @@ -150,14 +174,14 @@ class Meta(NetBoxTable.Meta): "remark", "tags", "description", - "source_prefix", + "source", ) default_columns = ( "access_list", "index", "action", "remark", - "source_prefix", + "source", "tags", ) @@ -177,6 +201,14 @@ class ACLExtendedRuleTable(NetBoxTable): tags = columns.TagColumn( url_name="plugins:netbox_acls:aclextendedrule_list", ) + source = tables.TemplateColumn( + template_code=COL_SOURCE_AND_DESTINATION_ASSIGNMENT.replace('#replaceme#', 'source'), + order_by=('source_prefix', 'source_iprange', 'source_ipaddress', 'source_aggregate', 'source_service') + ) + destination = tables.TemplateColumn( + template_code=COL_SOURCE_AND_DESTINATION_ASSIGNMENT.replace('#replaceme#', 'destination'), + order_by=('destination_prefix', 'destination_iprange', 'destination_ipaddress', 'destination_aggregate', 'destination_service') + ) protocol = ChoiceFieldColumn() class Meta(NetBoxTable.Meta): @@ -190,9 +222,9 @@ class Meta(NetBoxTable.Meta): "remark", "tags", "description", - "source_prefix", + "source", "source_ports", - "destination_prefix", + "destination", "destination_ports", "protocol", ) @@ -202,9 +234,11 @@ class Meta(NetBoxTable.Meta): "action", "remark", "tags", - "source_prefix", + "source", "source_ports", - "destination_prefix", + "destination", "destination_ports", "protocol", ) + + diff --git a/netbox_acls/templates/netbox_acls/aclextendedrule.html b/netbox_acls/templates/netbox_acls/aclextendedrule.html index 29e14419..fc690a5c 100644 --- a/netbox_acls/templates/netbox_acls/aclextendedrule.html +++ b/netbox_acls/templates/netbox_acls/aclextendedrule.html @@ -38,10 +38,23 @@

Details

{{ object.get_protocol_display|placeholder }} - Source Prefix + Source {% if object.source_prefix %} + Prefix {{ object.source_prefix }} + {% elif object.source_iprange %} + IP-Range + {{ object.source_iprange }} + {% elif object.source_ipaddress %} + IP-Address + {{ object.source_ipaddress }} + {% elif object.source_aggregate %} + Aggregate + {{ object.source_aggregate }} + {% elif object.source_service %} + Service + {{ object.source_service }} {% else %} {{ ''|placeholder }} {% endif %} @@ -52,10 +65,23 @@

Details

{{ object.source_ports|join:", "|placeholder }} - Destination Prefix + Destination {% if object.destination_prefix %} + Prefix {{ object.destination_prefix }} + {% elif object.destination_iprange %} + IP-Range + {{ object.destination_iprange }} + {% elif object.destination_ipaddress %} + IP-Address + {{ object.destination_ipaddress }} + {% elif object.destination_aggregate %} + Aggregate + {{ object.destination_aggregate }} + {% elif object.destination_service %} + Service + {{ object.destination_service }} {% else %} {{ ''|placeholder }} {% endif %} diff --git a/netbox_acls/templates/netbox_acls/aclstandardrule.html b/netbox_acls/templates/netbox_acls/aclstandardrule.html index 4211ba1e..87206624 100644 --- a/netbox_acls/templates/netbox_acls/aclstandardrule.html +++ b/netbox_acls/templates/netbox_acls/aclstandardrule.html @@ -38,10 +38,23 @@

Details

{{ object.get_protocol_display|placeholder }} - Source Prefix + Source {% if object.source_prefix %} + Prefix {{ object.source_prefix }} + {% elif object.source_iprange %} + IP-Range + {{ object.source_iprange }} + {% elif object.source_ipaddress %} + IP-Address + {{ object.source_ipaddress }} + {% elif object.source_aggregate %} + Aggregate + {{ object.source_aggregate }} + {% elif object.source_service %} + Service + {{ object.source_service }} {% else %} {{ ''|placeholder }} {% endif %} diff --git a/netbox_acls/views.py b/netbox_acls/views.py index 9f1fa81d..6c4076dd 100644 --- a/netbox_acls/views.py +++ b/netbox_acls/views.py @@ -323,6 +323,10 @@ class ACLStandardRuleView(generic.ObjectView): "access_list", "tags", "source_prefix", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service" ) @@ -335,6 +339,10 @@ class ACLStandardRuleListView(generic.ObjectListView): "access_list", "tags", "source_prefix", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service" ) table = tables.ACLStandardRuleTable filterset = filtersets.ACLStandardRuleFilterSet @@ -346,11 +354,14 @@ class ACLStandardRuleEditView(generic.ObjectEditView): """ Defines the edit view for the ACLStandardRule django model. """ - queryset = models.ACLStandardRule.objects.prefetch_related( "access_list", "tags", "source_prefix", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service" ) form = forms.ACLStandardRuleForm @@ -363,7 +374,6 @@ def get_extra_addanother_params(self, request): "access_list": request.GET.get("access_list") or request.POST.get("access_list"), } - @register_model_view(models.ACLStandardRule, "delete") class ACLStandardRuleDeleteView(generic.ObjectDeleteView): """ @@ -374,6 +384,10 @@ class ACLStandardRuleDeleteView(generic.ObjectDeleteView): "access_list", "tags", "source_prefix", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service" ) @@ -382,6 +396,10 @@ class ACLStandardRuleBulkDeleteView(generic.BulkDeleteView): "access_list", "tags", "source_prefix", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service" ) filterset = filtersets.ACLStandardRuleFilterSet table = tables.ACLStandardRuleTable @@ -402,7 +420,15 @@ class ACLExtendedRuleView(generic.ObjectView): "access_list", "tags", "source_prefix", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service", "destination_prefix", + "destination_iprange", + "destination_ipaddress", + "destination_aggregate", + "destination_service" ) @@ -415,7 +441,15 @@ class ACLExtendedRuleListView(generic.ObjectListView): "access_list", "tags", "source_prefix", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service", "destination_prefix", + "destination_iprange", + "destination_ipaddress", + "destination_aggregate", + "destination_service" ) table = tables.ACLExtendedRuleTable filterset = filtersets.ACLExtendedRuleFilterSet @@ -432,7 +466,15 @@ class ACLExtendedRuleEditView(generic.ObjectEditView): "access_list", "tags", "source_prefix", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service", "destination_prefix", + "destination_iprange", + "destination_ipaddress", + "destination_aggregate", + "destination_service" ) form = forms.ACLExtendedRuleForm @@ -456,7 +498,15 @@ class ACLExtendedRuleDeleteView(generic.ObjectDeleteView): "access_list", "tags", "source_prefix", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service", "destination_prefix", + "destination_iprange", + "destination_ipaddress", + "destination_aggregate", + "destination_service" ) @@ -465,7 +515,15 @@ class ACLExtendedRuleBulkDeleteView(generic.BulkDeleteView): "access_list", "tags", "source_prefix", + "source_iprange", + "source_ipaddress", + "source_aggregate", + "source_service", "destination_prefix", + "destination_iprange", + "destination_ipaddress", + "destination_aggregate", + "destination_service" ) filterset = filtersets.ACLExtendedRuleFilterSet table = tables.ACLExtendedRuleTable