diff --git a/preferences/forms.py b/preferences/forms.py index d2bede7c..e216ea1f 100644 --- a/preferences/forms.py +++ b/preferences/forms.py @@ -44,7 +44,8 @@ from .models import ( SwitchManagementCred, RadiusOption, CotisationsOption, - DocumentTemplate + DocumentTemplate, + RadiusAttribute ) from topologie.models import Switch @@ -411,3 +412,33 @@ class DelDocumentTemplateForm(FormRevMixin, Form): self.fields['document_templates'].queryset = instances else: self.fields['document_templates'].queryset = Banque.objects.all() + + +class RadiusAttributeForm(ModelForm): + """Edit and add RADIUS attributes.""" + class Meta: + model = RadiusAttribute + fields = '__all__' + + def __init__(self, *args, **kwargs): + prefix = kwargs.pop('prefix', self.Meta.model.__name__) + super(RadiusAttributeForm, self).__init__(*args, prefix=prefix, **kwargs) + + +class DelRadiusAttributeForm(Form): + """Delete RADIUS attributes""" + attributes = forms.ModelMultipleChoiceField( + queryset=RadiusAttribute.objects.none(), + label=_("Current attributes"), + widget=forms.CheckboxSelectMultiple + ) + + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelServiceForm, self).__init__(*args, **kwargs) + if instances: + self.fields['attributes'].queryset = instances + else: + self.fields['attributes'].queryset = Attributes.objects.all() + + diff --git a/preferences/migrations/0062_radius_attributes.py b/preferences/migrations/0062_radius_attributes.py new file mode 100644 index 00000000..05a34493 --- /dev/null +++ b/preferences/migrations/0062_radius_attributes.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.23 on 2019-09-09 14:13 +from __future__ import unicode_literals + +from django.db import migrations, models +import re2o.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('preferences', '0061_optionaluser_allow_archived_connexion'), + ] + + operations = [ + migrations.CreateModel( + name='RadiusAttribute', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('attribute', models.CharField(help_text='See http://freeradius.org/rfc/attributes.html', max_length=255, verbose_name='Attribute')), + ('operator', models.CharField(choices=[('=', '='), (':=', ':='), ('==', '=='), ('+=', '+='), ('!=', '!='), ('>', '>'), ('>=', '>='), ('<', '<'), ('<=', '<='), ('=~', '=~'), ('!~', '!~'), ('=*', '=*'), ('!*', '!*')], default=':=', help_text='See https://wiki.freeradius.org/config/Operators', max_length=2, verbose_name='Operator')), + ('value', models.CharField(max_length=255, verbose_name='Value')), + ('comment', models.TextField(blank=True, default='', help_text='Use this field to document this attribute.', verbose_name='Comment')), + ], + options={ + 'verbose_name': 'RADIUS attribute', + 'verbose_name_plural': 'RADIUS attributes', + }, + bases=(re2o.mixins.RevMixin, re2o.mixins.AclMixin, models.Model), + ), + migrations.AddField( + model_name='radiusoption', + name='banned_attributes', + field=models.ManyToManyField(blank=True, help_text='Answer attributes for banned users.', related_name='banned_attribute', to='preferences.RadiusAttribute', verbose_name='Banned attributes.'), + ), + migrations.AddField( + model_name='radiusoption', + name='non_member_attributes', + field=models.ManyToManyField(blank=True, help_text='Answer attributes for non members.', related_name='non_member_attribute', to='preferences.RadiusAttribute', verbose_name='Non member attributes.'), + ), + migrations.AddField( + model_name='radiusoption', + name='ok_attributes', + field=models.ManyToManyField(blank=True, help_text='Answer attributes for accepted users.', related_name='ok_attribute', to='preferences.RadiusAttribute', verbose_name='Accepted users attributes.'), + ), + migrations.AddField( + model_name='radiusoption', + name='unknown_machine_attributes', + field=models.ManyToManyField(blank=True, help_text='Answer attributes for unknown machines.', related_name='unknown_machine_attribute', to='preferences.RadiusAttribute', verbose_name='Unknown machines attributes.'), + ), + migrations.AddField( + model_name='radiusoption', + name='unknown_port_attributes', + field=models.ManyToManyField(blank=True, help_text='Answer attributes for unknown ports.', related_name='unknown_port_attribute', to='preferences.RadiusAttribute', verbose_name='Unknown ports attributes.'), + ), + migrations.AddField( + model_name='radiusoption', + name='unknown_room_attributes', + field=models.ManyToManyField(blank=True, help_text='Answer attributes for unknown rooms.', related_name='unknown_room_attribute', to='preferences.RadiusAttribute', verbose_name='Unknown rooms attributes.'), + ), + ] diff --git a/preferences/models.py b/preferences/models.py index 98374e77..ea7e9dd5 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -591,6 +591,53 @@ class MailMessageOption(AclMixin, models.Model): verbose_name = _("email message options") +class RadiusAttribute(RevMixin, AclMixin, models.Model): + class Meta: + verbose_name = _("RADIUS attribute") + verbose_name_plural = _("RADIUS attributes") + + CHOICE_OPERATOR = ( + ('=' , '=' ), + (':=', ':='), + ('==', '=='), + ('+=', '+='), + ('!=', '!='), + ('>' , '>' ), + ('>=', '>='), + ('<' , '<' ), + ('<=', '<='), + ('=~', '=~'), + ('!~', '!~'), + ('=*', '=*'), + ('!*', '!*') + ) + attribute = models.CharField( + max_length=255, + verbose_name=_("Attribute"), + help_text=_("See http://freeradius.org/rfc/attributes.html"), + ) + operator = models.CharField( + max_length=2, + verbose_name=_("Operator"), + help_text=_("See https://wiki.freeradius.org/config/Operators"), + choices=CHOICE_OPERATOR, + default=':=' + ) + value = models.CharField( + max_length=255, + verbose_name=_("Value") + ) + comment = models.TextField( + verbose_name=_("Comment"), + help_text=_("Use this field to document this attribute."), + blank=True, + default="" + ) + + def __str__(self): + return ' '.join([self.attribute, self.operator, self.value]) + + class RadiusOption(AclMixin, PreferencesModel): class Meta: verbose_name = _("RADIUS policy") @@ -628,6 +675,13 @@ class RadiusOption(AclMixin, PreferencesModel): verbose_name=_("Unknown machines VLAN"), help_text=_("VLAN for unknown machines if not rejected") ) + unknown_machine_attributes = models.ManyToManyField( + RadiusAttribute, + related_name='unknown_machine_attribute', + blank=True, + verbose_name=_("Unknown machines attributes."), + help_text=_("Answer attributes for unknown machines."), + ) unknown_port = models.CharField( max_length=32, choices=CHOICE_POLICY, @@ -643,6 +697,13 @@ class RadiusOption(AclMixin, PreferencesModel): verbose_name=_("Unknown ports VLAN"), help_text=_("VLAN for unknown ports if not rejected") ) + unknown_port_attributes = models.ManyToManyField( + RadiusAttribute, + related_name='unknown_port_attribute', + blank=True, + verbose_name=_("Unknown ports attributes."), + help_text=_("Answer attributes for unknown ports."), + ) unknown_room = models.CharField( max_length=32, choices=CHOICE_POLICY, @@ -659,6 +720,13 @@ class RadiusOption(AclMixin, PreferencesModel): verbose_name=_("Unknown rooms VLAN"), help_text=_("VLAN for unknown rooms if not rejected") ) + unknown_room_attributes = models.ManyToManyField( + RadiusAttribute, + related_name='unknown_room_attribute', + blank=True, + verbose_name=_("Unknown rooms attributes."), + help_text=_("Answer attributes for unknown rooms."), + ) non_member = models.CharField( max_length=32, choices=CHOICE_POLICY, @@ -674,6 +742,13 @@ class RadiusOption(AclMixin, PreferencesModel): verbose_name=_("Non members VLAN"), help_text=_("VLAN for non members if not rejected") ) + non_member_attributes = models.ManyToManyField( + RadiusAttribute, + related_name='non_member_attribute', + blank=True, + verbose_name=_("Non member attributes."), + help_text=_("Answer attributes for non members."), + ) banned = models.CharField( max_length=32, choices=CHOICE_POLICY, @@ -689,6 +764,13 @@ class RadiusOption(AclMixin, PreferencesModel): verbose_name=_("Banned users VLAN"), help_text=_("VLAN for banned users if not rejected") ) + banned_attributes = models.ManyToManyField( + RadiusAttribute, + related_name='banned_attribute', + blank=True, + verbose_name=_("Banned attributes."), + help_text=_("Answer attributes for banned users."), + ) vlan_decision_ok = models.OneToOneField( 'machines.Vlan', on_delete=models.PROTECT, @@ -696,6 +778,13 @@ class RadiusOption(AclMixin, PreferencesModel): blank=True, null=True ) + ok_attributes = models.ManyToManyField( + RadiusAttribute, + related_name='ok_attribute', + blank=True, + verbose_name=_("Accepted users attributes."), + help_text=_("Answer attributes for accepted users."), + ) def default_invoice(): diff --git a/preferences/templates/preferences/aff_radiusattributes.html b/preferences/templates/preferences/aff_radiusattributes.html new file mode 100644 index 00000000..8edd8b93 --- /dev/null +++ b/preferences/templates/preferences/aff_radiusattributes.html @@ -0,0 +1,50 @@ +{% comment %} +Re2o est un logiciel d'administration développé initiallement au rezometz. Il +se veut agnostique au réseau considéré, de manière à être installable en +quelques clics. + +Copyright © 2018 Hugo Levy-Falk + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +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. +{% endcomment %} +{% load i18n %} +{% load acl %} +{% load logs_extra %} + + + + + + + + + + {% for attribute in radius_attributes %} + + + + + + {% endfor %} +
{% trans "Attribute" %}{% trans "Comment" %}
{{ attribute }}{{ attribute.comment }} + {% can_edit attribute%} + {% include 'buttons/edit.html' with href='preferences:edit-radiusattribute' id=attribute.id %} + {% acl_end %} + {% can_delete attribute %} + {% include 'buttons/suppr.html' with href='preferences:del-radiusattribute' id=attribute.id %} + {% acl_end %} + {% history_button attribute %} +
+ diff --git a/preferences/templates/preferences/aff_radiusoptions.html b/preferences/templates/preferences/aff_radiusoptions.html index 41cb1846..0e84b44c 100644 --- a/preferences/templates/preferences/aff_radiusoptions.html +++ b/preferences/templates/preferences/aff_radiusoptions.html @@ -32,6 +32,13 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans "VLAN for machines accepted by RADIUS" %} {% blocktrans with vlan_decision_ok=radiusoptions.vlan_decision_ok %}VLAN {{ vlan_decision_ok }}{% endblocktrans %} + {% trans "Attributes" %} + +
@@ -40,6 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans "Situation" %} {% trans "Behaviour" %} + {% trans "Attributes" %} @@ -51,6 +59,13 @@ with this program; if not, write to the Free Software Foundation, Inc., {% blocktrans with unknown_machine_vlan=radiusoptions.unknown_machine_vlan %}VLAN {{ unknown_machine_vlan }}{% endblocktrans %} {% endif %} + + + {% trans "Unknown port" %} @@ -61,6 +76,13 @@ with this program; if not, write to the Free Software Foundation, Inc., {% blocktrans with unknown_port_vlan=radiusoptions.unknown_port_vlan %}VLAN {{ unknown_port_vlan }}{% endblocktrans %} {% endif %} + + + {% trans "Unknown room" %} @@ -71,6 +93,13 @@ with this program; if not, write to the Free Software Foundation, Inc., {% blocktrans with unknown_room_vlan=radiusoptions.unknown_room_vlan %}VLAN {{ unknown_room_vlan }}{% endblocktrans %} {% endif %} + + + {% trans "Non member" %} @@ -81,16 +110,30 @@ with this program; if not, write to the Free Software Foundation, Inc., {% blocktrans with non_member_vlan=radiusoptions.non_member_vlan %}VLAN {{ non_member_vlan }}{% endblocktrans %} {% endif %} + + + {% trans "Banned user" %} - {% if radiusoptions.unknown_port == 'REJECT' %} + {% if radiusoptions.banned == 'REJECT' %} {% trans "Reject" %} {% else %} {% blocktrans with banned_vlan=radiusoptions.banned_vlan %}VLAN {{ banned_vlan }}{% endblocktrans %} {% endif %} + + + diff --git a/preferences/templates/preferences/display_preferences.html b/preferences/templates/preferences/display_preferences.html index 626cb1a3..99ee9be3 100644 --- a/preferences/templates/preferences/display_preferences.html +++ b/preferences/templates/preferences/display_preferences.html @@ -298,11 +298,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,

{% trans "RADIUS preferences" %}

+
{% trans "RADIUS policies" %}
{% trans "Edit" %} {% include 'preferences/aff_radiusoptions.html' %} +
{% trans "Available RADIUS attributes"%}
+ {% trans " Add an attribute" %} + {% include 'preferences/aff_radiusattributes.html' %}
diff --git a/preferences/urls.py b/preferences/urls.py index 9bfd67d3..d07aa0be 100644 --- a/preferences/urls.py +++ b/preferences/urls.py @@ -126,5 +126,12 @@ urlpatterns = [ views.del_document_template, name='del-document-template' ), + url(r'^add_radiusattribute/$', views.add_radiusattribute, name='add-radiusattribute'), + url( + r'^edit_radiusattribute/(?P[0-9]+)$', + views.edit_radiusattribute, + name='edit-radiusattribute' + ), + url(r'^del_radiusattribute/(?P[0-9]+)$', views.del_radiusattribute, name='del-radiusattribute'), url(r'^$', views.display_options, name='display-options'), ] diff --git a/preferences/views.py b/preferences/views.py index 90afa2e8..5ccd7cad 100644 --- a/preferences/views.py +++ b/preferences/views.py @@ -52,7 +52,9 @@ from .forms import ( RadiusKeyForm, SwitchManagementCredForm, DocumentTemplateForm, - DelDocumentTemplateForm + DelDocumentTemplateForm, + RadiusAttributeForm, + DelRadiusAttributeForm ) from .models import ( Service, @@ -69,7 +71,8 @@ from .models import ( SwitchManagementCred, RadiusOption, CotisationsOption, - DocumentTemplate + DocumentTemplate, + RadiusAttribute ) from . import models from . import forms @@ -94,6 +97,7 @@ def display_options(request): radiuskey_list = RadiusKey.objects.all() switchmanagementcred_list = SwitchManagementCred.objects.all() radiusoptions, _ = RadiusOption.objects.get_or_create() + radius_attributes = RadiusAttribute.objects.all() cotisationsoptions, _created = CotisationsOption.objects.get_or_create() document_template_list = DocumentTemplate.objects.order_by('name') @@ -114,6 +118,7 @@ def display_options(request): 'radiuskey_list' : radiuskey_list, 'switchmanagementcred_list': switchmanagementcred_list, 'radiusoptions' : radiusoptions, + 'radius_attributes' : radius_attributes, 'cotisationsoptions': cotisationsoptions, 'optionnal_templates_list': optionnal_templates_list, 'document_template_list': document_template_list, @@ -502,3 +507,54 @@ def del_document_template(request, instances): 'action_name': _("Delete"), 'title': _("Delete document template") }, 'preferences/preferences.html', request) + + +@login_required +@can_create(RadiusAttribute) +def add_radiusattribute(request): + """Create a RADIUS attribute.""" + attribute = RadiusAttributeForm(request.POST or None) + if attribute.is_valid(): + attribute.save() + messages.success(request, _("The attribute was added.")) + return redirect(reverse('preferences:display-options')) + return form( + {'preferenceform': attribute, 'action_name': _("Add a RADIUS attribute")}, + 'preferences/preferences.html', + request + ) + + +@login_required +@can_edit(RadiusAttribute) +def edit_radiusattribute(request, radiusattribute_instance, **_kwargs): + """Edit a RADIUS attribute.""" + attribute = RadiusAttributeForm( + request.POST or None, + instance=radiusattribute_instance + ) + if attribute.is_valid(): + attribute.save() + messages.success(request, _("The attribute was edited.")) + return redirect(reverse('preferences:display-options')) + return form( + {'preferenceform': attribute, 'action_name': _("Edit")}, + 'preferences/preferences.html', + request + ) + +@login_required +@can_delete(RadiusAttribute) +def del_radiusattribute(request, radiusattribute_instance, **_kwargs): + """Delete a RADIUS attribute.""" + if request.method == "POST": + radiusattribute_instance.delete() + messages.success(request, _("The attribute was deleted.")) + return redirect(reverse('preferences:display-options')) + return form( + {'objet': radiusattribute_instance, 'objet_name': 'attribute'}, + 'preferences/delete.html', + request + ) + +