From b675e68b5792d43ae8db17de9274025616866f35 Mon Sep 17 00:00:00 2001 From: Vincent Simonin <vincent.simonin@ext.ec.europa.eu> Date: Mon, 31 Jul 2023 13:13:09 +0200 Subject: [PATCH] Add Mapping Saml configuration UI --- .../netbox_rps_plugin/__init__.py | 2 +- .../netbox_rps_plugin/forms.py | 89 +++++++---- .../templates/netbox_rps_plugin/mapping.html | 151 +++++++++++------- .../netbox_rps_plugin/saml_config.html | 41 +++++ .../netbox_rps_plugin/urls.py | 16 +- .../netbox_rps_plugin/views.py | 36 ++++- plugins/netbox-rps-plugin/setup.py | 2 +- 7 files changed, 236 insertions(+), 101 deletions(-) create mode 100644 plugins/netbox-rps-plugin/netbox_rps_plugin/templates/netbox_rps_plugin/saml_config.html diff --git a/plugins/netbox-rps-plugin/netbox_rps_plugin/__init__.py b/plugins/netbox-rps-plugin/netbox_rps_plugin/__init__.py index 14b8baf..5629ea1 100644 --- a/plugins/netbox-rps-plugin/netbox_rps_plugin/__init__.py +++ b/plugins/netbox-rps-plugin/netbox_rps_plugin/__init__.py @@ -5,7 +5,7 @@ class NetBoxRpsConfig(PluginConfig): name = 'netbox_rps_plugin' verbose_name = 'NetBox RPS' description = 'A Netbox plugin to add RPS resources' - version = '0.9.0' + version = '0.10.0' author = "Vincent Simonin" author_email = "vincent.simonin@ext.ec.europa.eu" base_url = 'rps' diff --git a/plugins/netbox-rps-plugin/netbox_rps_plugin/forms.py b/plugins/netbox-rps-plugin/netbox_rps_plugin/forms.py index aa9eb67..13b9453 100644 --- a/plugins/netbox-rps-plugin/netbox_rps_plugin/forms.py +++ b/plugins/netbox-rps-plugin/netbox_rps_plugin/forms.py @@ -1,73 +1,100 @@ from django import forms from django.utils.translation import gettext as _ -from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm -from .models import Mapping, AuthenticationChoices, HttpHeader, ApplyToChoices +from netbox.forms import ( + NetBoxModelForm, + NetBoxModelFilterSetForm, + NetBoxModelImportForm, +) +from .models import Mapping, AuthenticationChoices, HttpHeader, ApplyToChoices, SamlConfig from utilities.forms.fields import DynamicModelMultipleChoiceField, TagFilterField from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice class MappingForm(NetBoxModelForm): - class Meta: model = Mapping fields = ( - 'source', 'target', 'authentication', 'webdav', 'testingpage', 'Comment', 'tags', + "source", + "target", + "authentication", + "webdav", + "testingpage", + "Comment", + "tags", ) - help_texts = { - 'target': 'URL-friendly unique shorthand' - } + help_texts = {"target": "URL-friendly unique shorthand"} labels = { - 'source': 'Source', - 'target': 'Target', + "source": "Source", + "target": "Target", } class MappingFilterForm(NetBoxModelFilterSetForm): model = Mapping - source = forms.CharField(max_length=120, min_length=1, required=False, label='Source URL') - target = forms.CharField(max_length=120, min_length=1, required=False, label='Target URL') + source = forms.CharField( + max_length=120, min_length=1, required=False, label="Source URL" + ) + target = forms.CharField( + max_length=120, min_length=1, required=False, label="Target URL" + ) authentication = forms.MultipleChoiceField( choices=AuthenticationChoices, required=False, ) webdav = forms.BooleanField( - required=False, - widget=forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES) + required=False, widget=forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES) + ) + testingpage = forms.CharField( + max_length=120, min_length=1, required=False, label="Testing URL" + ) + Comment = forms.CharField( + max_length=500, min_length=1, required=False, label="Comment" ) - testingpage = forms.CharField(max_length=120, min_length=1, required=False, label='Testing URL') - Comment = forms.CharField(max_length=500, min_length=1, required=False, label='Comment') tag = TagFilterField(model) class MappingImportForm(NetBoxModelImportForm): - class Meta: model = Mapping - fields = ('source', 'target', 'authentication', 'testingpage', 'webdav') + fields = ("source", "target", "authentication", "testingpage", "webdav") class HttpHeaderForm(NetBoxModelForm): - class Meta: model = HttpHeader - fields = ( - 'mapping', 'name', 'value', 'apply_to' - ) + fields = ("mapping", "name", "value", "apply_to") labels = { - 'mapping': 'Mapping', - 'name': 'Name', - 'value': 'Value', - 'apply_to': 'Apply to', + "mapping": "Mapping", + "name": "Name", + "value": "Value", + "apply_to": "Apply to", } + class HttpHeaderFilterForm(NetBoxModelFilterSetForm): model = HttpHeader - name = forms.CharField(max_length=120, min_length=1, required=False, label='Header name') - value = forms.CharField(max_length=256, min_length=1, required=False, label='Header value') - apply_to = forms.ChoiceField(choices=add_blank_choice(ApplyToChoices), required=False, label='Apply to') + name = forms.CharField( + max_length=120, min_length=1, required=False, label="Header name" + ) + value = forms.CharField( + max_length=256, min_length=1, required=False, label="Header value" + ) + apply_to = forms.ChoiceField( + choices=add_blank_choice(ApplyToChoices), required=False, label="Apply to" + ) mapping = DynamicModelMultipleChoiceField( - queryset=Mapping.objects.all(), - required=False, - label=_('Mapping') + queryset=Mapping.objects.all(), required=False, label=_("Mapping") ) tag = TagFilterField(model) + + +class SamlConfigForm(NetBoxModelForm): + class Meta: + model = SamlConfig + fields = ("mapping", "acs_url", "logout_url", "force_nauth") + labels = { + "mapping": "Mapping", + "acs_url": "ACS URL", + "logout_url": "Logout URL", + "force_nauth": "Force AuthnRequest", + } diff --git a/plugins/netbox-rps-plugin/netbox_rps_plugin/templates/netbox_rps_plugin/mapping.html b/plugins/netbox-rps-plugin/netbox_rps_plugin/templates/netbox_rps_plugin/mapping.html index 980d6d9..49327a7 100644 --- a/plugins/netbox-rps-plugin/netbox_rps_plugin/templates/netbox_rps_plugin/mapping.html +++ b/plugins/netbox-rps-plugin/netbox_rps_plugin/templates/netbox_rps_plugin/mapping.html @@ -1,76 +1,105 @@ {% extends 'generic/object.html' %} {% block extra_controls %} - <a href="{% url 'plugins:netbox_rps_plugin:httpheader_add' %}?mapping={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-primary"> - <span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add HTTP Header - </a> +<a href="{% url 'plugins:netbox_rps_plugin:httpheader_add' %}?mapping={{ object.pk }}&return_url={{ object.get_absolute_url }}" + class="btn btn-sm btn-primary"> + <span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add HTTP Header +</a> +{% if not object.saml_config %} +<a href="{% url 'plugins:netbox_rps_plugin:samlconfig_add' %}?mapping={{ object.pk }}&return_url={{ object.get_absolute_url }}" + class="btn btn-sm btn-primary"> + <span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add <abbr + title="Security Assertion Markup Language">SAML</abbr> Configuration +</a> +{% endif %} {% endblock %} {% block content %} - <div class="row mb-3"> - <div class="col col-md-6"> - <div class="card"> - <h5 class="card-header">MAPPINGS</h5> - <div class="card-body"> - <table class="table table-hover attr-table"> - <tr> - <th scope="row">Name</th> - <td>{{ object.source }}</td> - </tr> - <tr> - <th scope="row">Comment</th> - <td> - {% if object.Comment %} - {{ object.Comment }} +<div class="row mb-3"> + <div class="col col-md-6"> + <div class="card"> + <h5 class="card-header">MAPPINGS</h5> + <div class="card-body"> + <table class="table table-hover attr-table"> + <tr> + <th scope="row">Name</th> + <td>{{ object.source }}</td> + </tr> + <tr> + <th scope="row">Comment</th> + <td> + {% if object.Comment %} + {{ object.Comment }} {% else %} - {{ ''|placeholder }} + {{ ''|placeholder }} {% endif %} </td> - </tr> - </table> - </div> + </tr> + </table> </div> - {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/tags.html' %} </div> - <div class="col col-md-6"> - <div class="card"> - <h5 class="card-header">Details</h5> - <div class="card-body"> - <table class="table table-hover attr-table"> - <tr> - <th scope="row">Source</th> - <td>{{ object.source }}</td> - </tr> - <tr> - <th scope="row">Target</th> - <td>{{ object.target }}</td> - </tr> - <tr> - <th scope="row">Authentication</th> - <td>{{ object.authentication }}</td> - </tr> - <tr> - <th scope="row">Testing page</th> - <td>{{ object.testingpage }}</td> - </tr> - <tr> - <th scope="row">Webdav</th> - <td>{{ object.webdav }}</td> - </tr> - <tr> - <th scope="row">Comment</th> - <td> - {% if object.Comment %} - {{ object.Comment }} - {% else %} - {{ ''|placeholder }} - {% endif %} - </td> - </tr> - </table> - </div> + {% include 'inc/panels/custom_fields.html' %} + {% include 'inc/panels/tags.html' %} + </div> + <div class="col col-md-6"> + <div class="card"> + <h5 class="card-header">Details</h5> + <div class="card-body"> + <table class="table table-hover attr-table"> + <tr> + <th scope="row">Source</th> + <td>{{ object.source }}</td> + </tr> + <tr> + <th scope="row">Target</th> + <td>{{ object.target }}</td> + </tr> + <tr> + <th scope="row">Authentication</th> + <td>{{ object.authentication }}</td> + </tr> + <tr> + <th scope="row">Testing page</th> + <td>{{ object.testingpage }}</td> + </tr> + <tr> + <th scope="row">Webdav</th> + <td>{{ object.webdav }}</td> + </tr> + <tr> + <th scope="row">Comment</th> + <td> + {% if object.Comment %} + {{ object.Comment }} + {% else %} + {{ ''|placeholder }} + {% endif %} + </td> + </tr> + </table> + </div> + </div> + {% if object.saml_config %} + <div class="card"> + <h5 class="card-header">SAML Configuration</h5> + <div class="card-body"> + <table class="table table-hover attr-table"> + <tr> + <th scope="row">ACS URL</th> + <td>{{ object.saml_config.acs_url }}</td> + </tr> + <tr> + <th scope="row">Logout URL</th> + <td>{{ object.saml_config.logout_url }}</td> + </tr> + <tr> + <th scope="row">Force AuthnRequest</th> + <td>{{ object.saml_config.force_nauth }}</td> + </tr> + </table> </div> </div> + {% endif %} </div> +</div> {% endblock content %} diff --git a/plugins/netbox-rps-plugin/netbox_rps_plugin/templates/netbox_rps_plugin/saml_config.html b/plugins/netbox-rps-plugin/netbox_rps_plugin/templates/netbox_rps_plugin/saml_config.html new file mode 100644 index 0000000..4d356c8 --- /dev/null +++ b/plugins/netbox-rps-plugin/netbox_rps_plugin/templates/netbox_rps_plugin/saml_config.html @@ -0,0 +1,41 @@ +{% extends 'generic/object.html' %} +{% load buttons %} +{% load perms %} + +{% block content %} +<div class="row mb-3"> + <div class="col col-md-6"> + <div class="card"> + <h5 class="card-header">SAML Configuration</h5> + <div class="card-body"> + <table class="table table-hover attr-table"> + <tr> + <th scope="row">ACS URL</th> + <td>{{ object.saml_config.acs_url }}</td> + </tr> + <tr> + <th scope="row">Logout URL</th> + <td>{{ object.saml_config.logout_url }}</td> + </tr> + <tr> + <th scope="row">Force AuthnRequest</th> + <td>{{ object.saml_config.force_nauth }}</td> + </tr> + </table> + </div> + </div> + {% include 'inc/panels/custom_fields.html' %} + {% include 'inc/panels/tags.html' %} + </div> +</div> +<div class="controls"> + <div class="control-group"> + {% if request.user|can_change:object.saml_config %} + {% edit_button object.saml_config %} + {% endif %} + {% if request.user|can_delete:object.saml_config %} + {% delete_button object.saml_config %} + {% endif %} + </div> +</div> +{% endblock content %} diff --git a/plugins/netbox-rps-plugin/netbox_rps_plugin/urls.py b/plugins/netbox-rps-plugin/netbox_rps_plugin/urls.py index dbe1e7d..417b7e9 100644 --- a/plugins/netbox-rps-plugin/netbox_rps_plugin/urls.py +++ b/plugins/netbox-rps-plugin/netbox_rps_plugin/urls.py @@ -14,6 +14,7 @@ urlpatterns = ( path('mappings/<int:pk>/edit/', views.MappingEditView.as_view(), name='mapping_edit'), path('mappings/<int:pk>/delete/', views.MappingDeleteView.as_view(), name='mapping_delete'), path('mappings/<int:pk>/http-headers/', views.MappingHttpHeadersView.as_view(), name='mapping_httpheader'), + path('mappings/<int:pk>/saml-config/', views.MappingSamlConfigView.as_view(), name='mapping_samlconfig'), path('mappings/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='mapping_changelog', kwargs={ 'model': models.Mapping }), @@ -32,6 +33,19 @@ urlpatterns = ( 'model': models.HttpHeader }), path('http-headers/<int:pk>/journal/', ObjectJournalView.as_view(), name='httpheader_journal', kwargs={ - 'model': models.Mapping + 'model': models.HttpHeader }), + + # Saml Config + #path('saml-configs/', views.HttpHeaderListView.as_view(), name='samlconfig_list'), + path('saml-configs/add/', views.SamlConfigEditView.as_view(), name='samlconfig_add'), + #path('saml-configs/<int:pk>/', views.HttpHeaderView.as_view(), name='samlconfig'), + path('saml-configs/<int:pk>/edit/', views.SamlConfigEditView.as_view(), name='samlconfig_edit'), + path('saml-configs/<int:pk>/delete/', views.SamlConfigDeleteView.as_view(), name='samlconfig_delete'), + #path('saml-configs/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='samlconfig_changelog', kwargs={ + # 'model': models.SamlConfig + #}), + #path('saml-configs/<int:pk>/journal/', ObjectJournalView.as_view(), name='samlconfig_journal', kwargs={ + # 'model': models.SamlConfig + #}), ) diff --git a/plugins/netbox-rps-plugin/netbox_rps_plugin/views.py b/plugins/netbox-rps-plugin/netbox_rps_plugin/views.py index 1990591..bf99e09 100644 --- a/plugins/netbox-rps-plugin/netbox_rps_plugin/views.py +++ b/plugins/netbox-rps-plugin/netbox_rps_plugin/views.py @@ -6,13 +6,16 @@ from django.utils.translation import gettext as _ class MappingView(generic.ObjectView): - queryset = models.Mapping.objects.all().prefetch_related('http_headers') + queryset = ( + models.Mapping.objects.all() + .prefetch_related("http_headers") + .prefetch_related("saml_config") + ) class MappingListView(generic.ObjectListView): - #queryset = models.Mapping.objects.all() queryset = models.Mapping.objects.annotate( - httpheader_count=count_related(models.HttpHeader, 'mapping') + httpheader_count=count_related(models.HttpHeader, "mapping") ) table = tables.MappingTable filterset = filtersets.MappingFilterSet @@ -39,9 +42,9 @@ class MappingBulkDeleteView(generic.BulkDeleteView): table = tables.MappingTable -@register_model_view(models.Mapping, 'httpheader') +@register_model_view(models.Mapping, "httpheader") class MappingHttpHeadersView(generic.ObjectChildrenView): - queryset = models.Mapping.objects.all().prefetch_related('http_headers') + queryset = models.Mapping.objects.all().prefetch_related("http_headers") child_model = models.HttpHeader table = tables.HttpHeaderTable filterset = filtersets.HttpHeaderFilterSet @@ -49,7 +52,7 @@ class MappingHttpHeadersView(generic.ObjectChildrenView): hide_if_empty = False tab = ViewTab( - label=_('HTTP Headers'), + label=_("HTTP Headers"), badge=lambda obj: obj.http_headers.count(), hide_if_empty=False, ) @@ -58,6 +61,18 @@ class MappingHttpHeadersView(generic.ObjectChildrenView): return parent.http_headers +@register_model_view(models.Mapping, "samlconfig") +class MappingSamlConfigView(generic.ObjectView): + base_template = "netbox_rps_plugin/mapping.html" + queryset = models.Mapping.objects.all().prefetch_related("saml_config") + template_name = "netbox_rps_plugin/saml_config.html" + + tab = ViewTab( + label=_("SAML Configuration"), + hide_if_empty=True, + ) + + class HttpHeaderView(generic.ObjectView): queryset = models.HttpHeader.objects.all() @@ -82,3 +97,12 @@ class HttpHeaderBulkDeleteView(generic.BulkDeleteView): queryset = models.HttpHeader.objects.all() filterset = filtersets.HttpHeaderFilterSet table = tables.HttpHeaderTable + + +class SamlConfigEditView(generic.ObjectEditView): + queryset = models.SamlConfig.objects.all() + form = forms.SamlConfigForm + + +class SamlConfigDeleteView(generic.ObjectDeleteView): + queryset = models.SamlConfig.objects.all() diff --git a/plugins/netbox-rps-plugin/setup.py b/plugins/netbox-rps-plugin/setup.py index ce13692..d7f062c 100644 --- a/plugins/netbox-rps-plugin/setup.py +++ b/plugins/netbox-rps-plugin/setup.py @@ -2,7 +2,7 @@ from setuptools import find_packages, setup setup( name='netbox_rps_plugin', - version='0.9.0', + version='0.10.0', description='A Netbox plugin to add RPS resources', install_requires=[], packages=find_packages(), -- GitLab