diff --git a/cotisations/__init__.py b/cotisations/__init__.py index fc1be5d7..df6e4256 100644 --- a/cotisations/__init__.py +++ b/cotisations/__init__.py @@ -21,3 +21,4 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from .acl import * diff --git a/cotisations/acl.py b/cotisations/acl.py new file mode 100644 index 00000000..868e3411 --- /dev/null +++ b/cotisations/acl.py @@ -0,0 +1,40 @@ +# -*- mode: python; coding: utf-8 -*- +# 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 © 2017 Gabriel Détraz +# Copyright © 2017 Goulven Kermarec +# Copyright © 2017 Augustin Lemesle +# +# 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. + +"""cotisations.acl + +Here are defined some functions to check acl on the application. +""" + +def can_view(user): + """Check if an user can view the application. + + Args: + user: The user who wants to view the application. + + Returns: + A couple (allowed, msg) where allowed is a boolean which is True if + viewing is granted and msg is a message (can be None). + """ + can = user.has_module_perms('cotisations') + return can, None if can else "Vous ne pouvez pas voir cette application." diff --git a/cotisations/forms.py b/cotisations/forms.py index 7725016c..5845611a 100644 --- a/cotisations/forms.py +++ b/cotisations/forms.py @@ -43,6 +43,8 @@ from django.forms import ModelForm, Form from django.core.validators import MinValueValidator from .models import Article, Paiement, Facture, Banque +from re2o.field_permissions import FieldPermissionFormMixin + class NewFactureForm(ModelForm): """Creation d'une facture, moyen de paiement, banque et numero @@ -141,27 +143,18 @@ class NewFactureFormPdf(Form): ) -class EditFactureForm(NewFactureForm): +class EditFactureForm(FieldPermissionFormMixin, NewFactureForm): """Edition d'une facture : moyen de paiement, banque, user parent""" class Meta(NewFactureForm.Meta): - fields = ['paiement', 'banque', 'cheque', 'user'] + model = Facture + fields = '__all__' def __init__(self, *args, **kwargs): super(EditFactureForm, self).__init__(*args, **kwargs) self.fields['user'].label = 'Adherent' self.fields['user'].empty_label = "Séléctionner\ l'adhérent propriétaire" - - -class TrezEditFactureForm(EditFactureForm): - """Vue pour édition controle trésorier""" - class Meta(EditFactureForm.Meta): - fields = '__all__' - - def __init__(self, *args, **kwargs): - super(TrezEditFactureForm, self).__init__(*args, **kwargs) self.fields['valid'].label = 'Validité de la facture' - self.fields['control'].label = 'Contrôle de la facture' class ArticleForm(ModelForm): @@ -180,11 +173,19 @@ class DelArticleForm(Form): """Suppression d'un ou plusieurs articles en vente. Choix parmis les modèles""" articles = forms.ModelMultipleChoiceField( - queryset=Article.objects.all(), + queryset=Article.objects.none(), label="Articles actuels", widget=forms.CheckboxSelectMultiple ) + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelArticleForm, self).__init__(*args, **kwargs) + if instances: + self.fields['articles'].queryset = instances + else: + self.fields['articles'].queryset = Article.objects.all() + class PaiementForm(ModelForm): """Creation d'un moyen de paiement, champ text moyen et type @@ -204,11 +205,19 @@ class DelPaiementForm(Form): """Suppression d'un ou plusieurs moyens de paiements, selection parmis les models""" paiements = forms.ModelMultipleChoiceField( - queryset=Paiement.objects.all(), + queryset=Paiement.objects.none(), label="Moyens de paiement actuels", widget=forms.CheckboxSelectMultiple ) + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelPaiementForm, self).__init__(*args, **kwargs) + if instances: + self.fields['paiements'].queryset = instances + else: + self.fields['paiements'].queryset = Paiement.objects.all() + class BanqueForm(ModelForm): """Creation d'une banque, field name""" @@ -225,7 +234,15 @@ class BanqueForm(ModelForm): class DelBanqueForm(Form): """Selection d'une ou plusieurs banques, pour suppression""" banques = forms.ModelMultipleChoiceField( - queryset=Banque.objects.all(), + queryset=Banque.objects.none(), label="Banques actuelles", widget=forms.CheckboxSelectMultiple ) + + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelBanqueForm, self).__init__(*args, **kwargs) + if instances: + self.fields['banques'].queryset = instances + else: + self.fields['banques'].queryset = Banque.objects.all() diff --git a/cotisations/migrations/0028_auto_20171231_0007.py b/cotisations/migrations/0028_auto_20171231_0007.py new file mode 100644 index 00000000..0d492489 --- /dev/null +++ b/cotisations/migrations/0028_auto_20171231_0007.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-12-30 23:07 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('cotisations', '0027_auto_20171029_1156'), + ] + + operations = [ + migrations.AlterModelOptions( + name='article', + options={'permissions': (('view_article', 'Peut voir un objet article'),)}, + ), + migrations.AlterModelOptions( + name='banque', + options={'permissions': (('view_banque', 'Peut voir un objet banque'),)}, + ), + migrations.AlterModelOptions( + name='cotisation', + options={'permissions': (('view_cotisation', 'Peut voir un objet cotisation'), ('change_all_cotisation', 'Superdroit, peut modifier toutes les cotisations'))}, + ), + migrations.AlterModelOptions( + name='facture', + options={'permissions': (('change_facture_control', "Peut changer l'etat de controle"), ('change_facture_pdf', 'Peut éditer une facture pdf'), ('view_facture', 'Peut voir un objet facture'), ('change_all_facture', 'Superdroit, peut modifier toutes les factures'))}, + ), + migrations.AlterModelOptions( + name='paiement', + options={'permissions': (('view_paiement', 'Peut voir un objet paiement'),)}, + ), + migrations.AlterModelOptions( + name='vente', + options={'permissions': (('view_vente', 'Peut voir un objet vente'), ('change_all_vente', 'Superdroit, peut modifier toutes les ventes'))}, + ), + ] diff --git a/cotisations/models.py b/cotisations/models.py index 090636be..5ba43452 100644 --- a/cotisations/models.py +++ b/cotisations/models.py @@ -56,8 +56,10 @@ from django.db.models import Max from django.utils import timezone from machines.models import regen +from re2o.field_permissions import FieldPermissionModelMixin -class Facture(models.Model): + +class Facture(FieldPermissionModelMixin, models.Model): """ Définition du modèle des factures. Une facture regroupe une ou plusieurs ventes, rattachée à un user, et reliée à un moyen de paiement et si il y a lieu un numero pour les chèques. Possède les valeurs @@ -76,6 +78,15 @@ class Facture(models.Model): valid = models.BooleanField(default=True) control = models.BooleanField(default=False) + class Meta: + abstract = False + permissions = ( + ("change_facture_control", "Peut changer l'etat de controle"), + ("change_facture_pdf", "Peut éditer une facture pdf"), + ("view_facture", "Peut voir un objet facture"), + ("change_all_facture", "Superdroit, peut modifier toutes les factures"), + ) + def prix(self): """Renvoie le prix brut sans les quantités. Méthode dépréciée""" @@ -103,6 +114,59 @@ class Facture(models.Model): ).values_list('name', flat=True)) return name + def get_instance(factureid, *args, **kwargs): + return Facture.objects.get(pk=factureid) + + def can_create(user_request, *args, **kwargs): + return user_request.has_perm('cotisations.add_facture'), u"Vous n'avez pas le\ + droit de créer des factures" + + def can_edit(self, user_request, *args, **kwargs): + if not user_request.has_perm('cotisations.change_facture'): + return False, u"Vous n'avez pas le droit d'éditer les factures" + elif not user_request.has_perm('cotisations.change_all_facture') and\ + (self.control or not self.valid): + return False, u"Vous n'avez pas le droit d'éditer une facture\ + controlée ou invalidée par un trésorier" + else: + return True, None + + def can_delete(self, user_request, *args, **kwargs): + if not user_request.has_perm('cotisations.delete_facture'): + return False, u"Vous n'avez pas le droit de supprimer une facture" + if self.control or not self.valid: + return False, u"Vous ne pouvez pas supprimer une facture\ + contrôlée ou invalidée par un trésorier" + else: + return True, None + + def can_view_all(user_request, *args, **kwargs): + if not user_request.has_perm('cotisations.view_facture'): + return False, u"Vous n'avez pas le droit de voir les factures" + return True, None + + def can_view(self, user_request, *args, **kwargs): + if not user_request.has_perm('cotisations.view_facture') and\ + self.user != user_request: + return False, u"Vous ne pouvez pas afficher l'historique d'une\ + facture d'un autre user que vous sans droit cableur" + elif not self.valid: + return False, u"La facture est invalidée et ne peut être affichée" + else: + return True, None + + @staticmethod + def can_change_control(user_request, *args, **kwargs): + return user_request.has_perm('cotisations.change_facture_control'), "Vous ne pouvez pas éditer le controle sans droit trésorier" + + @staticmethod + def can_change_pdf(user_request, *args, **kwargs): + return user_request.has_perm('cotisations.change_facture_pdf'), "Vous ne pouvez pas éditer une facture sans droit trésorier" + + field_permissions = { + 'control': can_change_control, + } + def __str__(self): return str(self.user) + ' ' + str(self.date) @@ -149,6 +213,12 @@ class Vente(models.Model): max_length=255 ) + class Meta: + permissions = ( + ("view_vente", "Peut voir un objet vente"), + ("change_all_vente", "Superdroit, peut modifier toutes les ventes"), + ) + def prix_total(self): """Renvoie le prix_total de self (nombre*prix)""" return self.prix*self.number @@ -201,6 +271,46 @@ class Vente(models.Model): self.update_cotisation() super(Vente, self).save(*args, **kwargs) + def get_instance(venteid, *args, **kwargs): + return Vente.objects.get(pk=venteid) + + def can_create(user_request, *args, **kwargs): + return user_request.has_perm('cotisations.add_vente'), u"Vous n'avez pas le\ + droit de créer des ventes" + return True, None + + def can_edit(self, user_request, *args, **kwargs): + if not user_request.has_perm('cotisations.change_vente'): + return False, u"Vous n'avez pas le droit d'éditer les ventes" + elif not user_request.has_perm('cotisations.change_all_vente') and\ + (self.facture.control or not self.facture.valid): + return False, u"Vous n'avez pas le droit d'éditer une vente\ + controlée ou invalidée par un trésorier" + else: + return True, None + + def can_delete(self, user_request, *args, **kwargs): + if not user_request.has_perm('cotisations.delete_vente'): + return False, u"Vous n'avez pas le droit de supprimer une vente" + if self.facture.control or not self.facture.valid: + return False, u"Vous ne pouvez pas supprimer une vente\ + contrôlée ou invalidée par un trésorier" + else: + return True, None + + def can_view_all(user_request, *args, **kwargs): + if not user_request.has_perm('cotisations.view_vente'): + return False, u"Vous n'avez pas le droit de voir les ventes" + return True, None + + def can_view(self, user_request, *args, **kwargs): + if not user_request.has_perm('cotisations.view_vente') and\ + self.facture.user != user_request: + return False, u"Vous ne pouvez pas afficher l'historique d'une\ + facture d'un autre user que vous sans droit cableur" + else: + return True, None + def __str__(self): return str(self.name) + ' ' + str(self.facture) @@ -269,6 +379,11 @@ class Article(models.Model): unique_together = ('name', 'type_user') + class Meta: + permissions = ( + ("view_article", "Peut voir un objet article"), + ) + def clean(self): if self.name.lower() == "solde": raise ValidationError("Solde est un nom d'article invalide") @@ -277,6 +392,29 @@ class Article(models.Model): "La durée est obligatoire si il s'agit d'une cotisation" ) + def get_instance(articleid, *args, **kwargs): + return Article.objects.get(pk=articleid) + + def can_create(user_request, *args, **kwargs): + return user_request.has_perm('cotisations.add_article'), u"Vous n'avez pas le\ + droit d'ajouter des articles" + + def can_edit(self, user_request, *args, **kwargs): + return user_request.has_perm('cotisations.change_article'), u"Vous n'avez pas le\ + droit d'éditer des articles" + + def can_delete(self, user_request, *args, **kwargs): + return user_request.has_perm('cotisations.delete_article'), u"Vous n'avez pas le\ + droit de supprimer des articles" + + def can_view_all(user_request, *args, **kwargs): + return user_request.has_perm('cotisations.view_article'), u"Vous n'avez pas le\ + droit de voir des articles" + + def can_view(self, user_request, *args, **kwargs): + return user_request.has_perm('cotisations.view_article'), u"Vous n'avez pas le\ + droit de voir des articles" + def __str__(self): return self.name @@ -287,6 +425,34 @@ class Banque(models.Model): name = models.CharField(max_length=255) + class Meta: + permissions = ( + ("view_banque", "Peut voir un objet banque"), + ) + + def get_instance(banqueid, *args, **kwargs): + return Banque.objects.get(pk=banqueid) + + def can_create(user_request, *args, **kwargs): + return user_request.has_perm('cotisations.add_banque'), u"Vous n'avez pas le\ + droit d'ajouter des banques" + + def can_edit(self, user_request, *args, **kwargs): + return user_request.has_perm('cotisations.change_banque'), u"Vous n'avez pas le\ + droit d'éditer des banques" + + def can_delete(self, user_request, *args, **kwargs): + return user_request.has_perm('cotisations.delete_banque'), u"Vous n'avez pas le\ + droit de supprimer des banques" + + def can_view_all(user_request, *args, **kwargs): + return user_request.has_perm('cotisations.view_banque'), u"Vous n'avez pas le\ + droit de voir des banques" + + def can_view(self, user_request, *args, **kwargs): + return user_request.has_perm('cotisations.view_banque'), u"Vous n'avez pas le\ + droit de voir des banques" + def __str__(self): return self.name @@ -302,6 +468,34 @@ class Paiement(models.Model): moyen = models.CharField(max_length=255) type_paiement = models.IntegerField(choices=PAYMENT_TYPES, default=0) + class Meta: + permissions = ( + ("view_paiement", "Peut voir un objet paiement"), + ) + + def get_instance(paiementid, *args, **kwargs): + return Paiement.objects.get(pk=paiementid) + + def can_create(user_request, *args, **kwargs): + return user_request.has_perm('cotisations.add_paiement'), u"Vous n'avez pas le\ + droit d'ajouter des paiements" + + def can_edit(self, user_request, *args, **kwargs): + return user_request.has_perm('cotisations.change_paiement'), u"Vous n'avez pas le\ + droit d'éditer des paiements" + + def can_delete(self, user_request, *args, **kwargs): + return user_request.has_perm('cotisations.delete_paiement'), u"Vous n'avez pas le\ + droit de supprimer des paiements" + + def can_view_all(user_request, *args, **kwargs): + return user_request.has_perm('cotisations.view_paiement'), u"Vous n'avez pas le\ + droit de voir des paiements" + + def can_view(self, user_request, *args, **kwargs): + return user_request.has_perm('cotisations.view_paiement'), u"Vous n'avez pas le\ + droit de voir des paiements" + def __str__(self): return self.moyen @@ -334,6 +528,52 @@ class Cotisation(models.Model): date_start = models.DateTimeField() date_end = models.DateTimeField() + class Meta: + permissions = ( + ("view_cotisation", "Peut voir un objet cotisation"), + ("change_all_cotisation", "Superdroit, peut modifier toutes les cotisations"), + ) + + def get_instance(cotisationid, *args, **kwargs): + return Cotisations.objects.get(pk=cotisationid) + + def can_create(user_request, *args, **kwargs): + return user_request.has_perm('cotisations.add_cotisation'), u"Vous n'avez pas le\ + droit de créer des cotisations" + return True, None + + def can_edit(self, user_request, *args, **kwargs): + if not user_request.has_perm('cotisations.change_cotisation'): + return False, u"Vous n'avez pas le droit d'éditer les cotisations" + elif not user_request.has_perm('cotisations.change_all_cotisation') and\ + (self.vente.facture.control or not self.vente.facture.valid): + return False, u"Vous n'avez pas le droit d'éditer une cotisation\ + controlée ou invalidée par un trésorier" + else: + return True, None + + def can_delete(self, user_request, *args, **kwargs): + if not user_request.has_perm('cotisations.delete_cotisation'): + return False, u"Vous n'avez pas le droit de supprimer une cotisations" + if self.vente.facture.control or not self.vente.facture.valid: + return False, u"Vous ne pouvez pas supprimer une cotisations\ + contrôlée ou invalidée par un trésorier" + else: + return True, None + + def can_view_all(user_request, *args, **kwargs): + if not user_request.has_perm('cotisations.view_cotisation'): + return False, u"Vous n'avez pas le droit de voir les cotisations" + return True, None + + def can_view(self, user_request, *args, **kwargs): + if not user_request.has_perm('cotisations.view_cotisation') and\ + self.vente.facture.user != user_request: + return False, u"Vous ne pouvez pas afficher l'historique d'une\ + cotisation d'un autre user que vous sans droit cableur" + else: + return True, None + def __str__(self): return str(self.vente) diff --git a/cotisations/templates/cotisations/aff_article.html b/cotisations/templates/cotisations/aff_article.html index 3a0b21f6..f4880caf 100644 --- a/cotisations/templates/cotisations/aff_article.html +++ b/cotisations/templates/cotisations/aff_article.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} + @@ -41,11 +43,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{{ article.duration }} {{ article.type_user }} - {% if is_trez %} + {% can_edit article %} - {% endif %} + {% acl_end %} diff --git a/cotisations/templates/cotisations/aff_banque.html b/cotisations/templates/cotisations/aff_banque.html index fd962f1f..f31bf55a 100644 --- a/cotisations/templates/cotisations/aff_banque.html +++ b/cotisations/templates/cotisations/aff_banque.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} + @@ -33,11 +35,11 @@ with this program; if not, write to the Free Software Foundation, Inc., - {% if is_cableur %} - {% endif %}
{{ banque.name }} - {% if is_trez %} + {% can_edit banque %} - {% endif %} + {% acl_end %} diff --git a/cotisations/templates/cotisations/aff_cotisations.html b/cotisations/templates/cotisations/aff_cotisations.html index 34efd63d..2e6eea3f 100644 --- a/cotisations/templates/cotisations/aff_cotisations.html +++ b/cotisations/templates/cotisations/aff_cotisations.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} + {% if facture_list.paginator %} {% include "pagination.html" with list=facture_list %} {% endif %} @@ -47,7 +49,6 @@ with this program; if not, write to the Free Software Foundation, Inc., {{ facture.paiement }} {{ facture.date }} {{ facture.id }} {% if facture.valid %} diff --git a/cotisations/templates/cotisations/aff_paiement.html b/cotisations/templates/cotisations/aff_paiement.html index 5cf8d176..f2f9abe3 100644 --- a/cotisations/templates/cotisations/aff_paiement.html +++ b/cotisations/templates/cotisations/aff_paiement.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} + @@ -33,11 +35,11 @@ with this program; if not, write to the Free Software Foundation, Inc., +
{{ paiement.moyen }} - {% if is_trez %} - + {% can_edit paiement %} + - {% endif %} + {% acl_end %} diff --git a/cotisations/templates/cotisations/index_article.html b/cotisations/templates/cotisations/index_article.html index ee9f3db1..edc71c09 100644 --- a/cotisations/templates/cotisations/index_article.html +++ b/cotisations/templates/cotisations/index_article.html @@ -24,15 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load acl %} {% block title %}Articles{% endblock %} {% block content %}

Liste des types d'articles

- {% if is_trez %} + {% can_create Article %} Ajouter un type d'articles + {% acl_end %} Supprimer un ou plusieurs types d'articles - {% endif %} {% include "cotisations/aff_article.html" with article_list=article_list %}

diff --git a/cotisations/templates/cotisations/index_banque.html b/cotisations/templates/cotisations/index_banque.html index 142c163f..dda89843 100644 --- a/cotisations/templates/cotisations/index_banque.html +++ b/cotisations/templates/cotisations/index_banque.html @@ -24,15 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load acl %} {% block title %}Banques{% endblock %} {% block content %}

Liste des banques

+ {% can_create Banque %} Ajouter une banque - {% if is_trez %} + {% acl_end %} Supprimer une ou plusieurs banques - {% endif %} {% include "cotisations/aff_banque.html" with banque_list=banque_list %}

diff --git a/cotisations/templates/cotisations/index_paiement.html b/cotisations/templates/cotisations/index_paiement.html index d2734e46..a27cb824 100644 --- a/cotisations/templates/cotisations/index_paiement.html +++ b/cotisations/templates/cotisations/index_paiement.html @@ -24,15 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load acl %} {% block title %}Paiements{% endblock %} {% block content %}

Liste des types de paiements

- {% if is_trez %} + {% can_create Paiement %} Ajouter un type de paiement + {% acl_end %} Supprimer un ou plusieurs types de paiements - {% endif %} {% include "cotisations/aff_paiement.html" with paiement_list=paiement_list %}

diff --git a/cotisations/templates/cotisations/sidebar.html b/cotisations/templates/cotisations/sidebar.html index 1860b1ee..3506e5e8 100644 --- a/cotisations/templates/cotisations/sidebar.html +++ b/cotisations/templates/cotisations/sidebar.html @@ -23,9 +23,10 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} {% block sidebar %} - {% if is_trez %} + {% can_change Facture pdf %} Créer une facture @@ -34,21 +35,29 @@ with this program; if not, write to the Free Software Foundation, Inc., Contrôler les factures - {% endif %} + {% acl_end %} + {% can_view_all Facture %} Factures + {% acl_end %} + {% can_view_all Article %} Articles en vente + {% acl_end %} + {% can_view_all Banque %} Banques + {% acl_end %} + {% can_view_all Paiement %} Moyens de paiement + {% acl_end %} {% endblock %} diff --git a/cotisations/urls.py b/cotisations/urls.py index d3e56f36..2a0c0163 100644 --- a/cotisations/urls.py +++ b/cotisations/urls.py @@ -24,6 +24,7 @@ from __future__ import unicode_literals from django.conf.urls import url +import re2o from . import views urlpatterns = [ @@ -99,21 +100,12 @@ urlpatterns = [ views.index_paiement, name='index-paiement' ), - url(r'^history/(?Pfacture)/(?P[0-9]+)$', - views.history, - name='history' - ), - url(r'^history/(?Particle)/(?P[0-9]+)$', - views.history, - name='history' - ), - url(r'^history/(?Ppaiement)/(?P[0-9]+)$', - views.history, - name='history'), - url(r'^history/(?Pbanque)/(?P[0-9]+)$', - views.history, - name='history' - ), + url( + r'history/(?P\w+)/(?P[0-9]+)$', + re2o.views.history, + name='history', + kwargs={'application':'cotisations'}, + ), url(r'^control/$', views.control, name='control' diff --git a/cotisations/views.py b/cotisations/views.py index 1a9b8ed9..d7d953c9 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -44,11 +44,19 @@ from re2o.settings import LOGO_PATH from re2o import settings from re2o.views import form from re2o.utils import SortTable +from re2o.acl import ( + can_create, + can_edit, + can_delete, + can_view, + can_view_all, + can_delete_set, + can_change, +) from preferences.models import OptionalUser, AssoOption, GeneralOption from .models import Facture, Article, Vente, Paiement, Banque from .forms import ( NewFactureForm, - TrezEditFactureForm, EditFactureForm, ArticleForm, DelArticleForm, @@ -64,9 +72,11 @@ from .forms import ( from .tex import render_invoice + @login_required -@permission_required('cableur') -def new_facture(request, userid): +@can_create(Facture) +@can_edit(User) +def new_facture(request, user, userid): """Creation d'une facture pour un user. Renvoie la liste des articles et crée des factures dans un formset. Utilise un peu de js coté template pour ajouter des articles. @@ -74,11 +84,6 @@ def new_facture(request, userid): enfin sauve la facture parente. TODO : simplifier cette fonction, déplacer l'intelligence coté models Facture et Vente.""" - try: - user = User.objects.get(pk=userid) - except User.DoesNotExist: - messages.error(request, u"Utilisateur inexistant") - return redirect(reverse('cotisations:index')) facture = Facture(user=user) # Le template a besoin de connaitre les articles pour le js article_list = Article.objects.filter( @@ -163,7 +168,7 @@ def new_facture(request, userid): @login_required -@permission_required('tresorier') +@can_change(Facture, 'pdf') def new_facture_pdf(request): """Permet de générer un pdf d'une facture. Réservée au trésorier, permet d'emettre des factures sans objet @@ -203,31 +208,13 @@ def new_facture_pdf(request): @login_required -def facture_pdf(request, factureid): +@can_view(Facture) +def facture_pdf(request, facture, factureid): """Affiche en pdf une facture. Cree une ligne par Vente de la facture, et génére une facture avec le total, le moyen de paiement, l'adresse de l'adhérent, etc. Réservée à self pour un user sans droits, les droits cableurs permettent d'afficher toute facture""" - try: - facture = Facture.objects.get(pk=factureid) - except Facture.DoesNotExist: - messages.error(request, u"Facture inexistante") - return redirect(reverse('cotisations:index')) - if not request.user.has_perms(('cableur',))\ - and facture.user != request.user: - messages.error(request, "Vous ne pouvez pas afficher une facture ne vous\ - appartenant pas sans droit cableur") - return redirect(reverse( - 'users:profil', - kwargs={'userid': str(request.user.id)} - )) - if not facture.valid: - messages.error(request, "Vous ne pouvez pas afficher\ - une facture non valide") - return redirect(reverse( - 'users:profil', - kwargs={'userid': str(request.user.id)} - )) + ventes_objects = Vente.objects.all().filter(facture=facture) ventes = [] options, _created = AssoOption.objects.get_or_create() @@ -251,27 +238,12 @@ def facture_pdf(request, factureid): @login_required -@permission_required('cableur') -def edit_facture(request, factureid): +@can_edit(Facture) +def edit_facture(request, facture, factureid): """Permet l'édition d'une facture. On peut y éditer les ventes déjà effectuer, ou rendre une facture invalide (non payées, chèque en bois etc). Mets à jour les durée de cotisation attenantes""" - try: - facture = Facture.objects.get(pk=factureid) - except Facture.DoesNotExist: - messages.error(request, u"Facture inexistante") - return redirect(reverse('cotisations:index')) - if request.user.has_perms(['tresorier']): - facture_form = TrezEditFactureForm( - request.POST or None, - instance=facture - ) - elif facture.control or not facture.valid: - messages.error(request, "Vous ne pouvez pas editer une facture\ - controlée ou invalidée par le trésorier") - return redirect(reverse('cotisations:index')) - else: - facture_form = EditFactureForm(request.POST or None, instance=facture) + facture_form = EditFactureForm(request.POST or None, instance=facture, user=request.user) ventes_objects = Vente.objects.filter(facture=facture) vente_form_set = modelformset_factory( Vente, @@ -297,19 +269,10 @@ def edit_facture(request, factureid): @login_required -@permission_required('cableur') -def del_facture(request, factureid): +@can_delete(Facture) +def del_facture(request, facture, factureid): """Suppression d'une facture. Supprime en cascade les ventes et cotisations filles""" - try: - facture = Facture.objects.get(pk=factureid) - except Facture.DoesNotExist: - messages.error(request, u"Facture inexistante") - return redirect(reverse('cotisations:index')) - if facture.control or not facture.valid: - messages.error(request, "Vous ne pouvez pas editer une facture\ - controlée ou invalidée par le trésorier") - return redirect(reverse('cotisations:index')) if request.method == "POST": with transaction.atomic(), reversion.create_revision(): facture.delete() @@ -323,14 +286,10 @@ def del_facture(request, factureid): @login_required -@permission_required('cableur') -def credit_solde(request, userid): +@can_create(Facture) +@can_edit(User) +def credit_solde(request, user, userid): """ Credit ou débit de solde """ - try: - user = User.objects.get(pk=userid) - except User.DoesNotExist: - messages.error(request, u"Utilisateur inexistant") - return redirect(reverse('cotisations:index')) facture = CreditSoldeForm(request.POST or None) if facture.is_valid(): facture_instance = facture.save(commit=False) @@ -355,7 +314,7 @@ def credit_solde(request, userid): @login_required -@permission_required('tresorier') +@can_create(Article) def add_article(request): """Ajoute un article. Champs : désignation, prix, est-ce une cotisation et si oui sa durée @@ -376,15 +335,10 @@ def add_article(request): @login_required -@permission_required('tresorier') -def edit_article(request, articleid): +@can_edit(Article) +def edit_article(request, article_instance, articleid): """Edition d'un article (designation, prix, etc) Réservé au trésorier""" - try: - article_instance = Article.objects.get(pk=articleid) - except Article.DoesNotExist: - messages.error(request, u"Entrée inexistante") - return redirect(reverse('cotisations:index-article')) article = ArticleForm(request.POST or None, instance=article_instance) if article.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -401,10 +355,10 @@ def edit_article(request, articleid): @login_required -@permission_required('tresorier') -def del_article(request): +@can_delete_set(Article) +def del_article(request, instances): """Suppression d'un article en vente""" - article = DelArticleForm(request.POST or None) + article = DelArticleForm(request.POST or None, instances=instances) if article.is_valid(): article_del = article.cleaned_data['articles'] with transaction.atomic(), reversion.create_revision(): @@ -416,7 +370,7 @@ def del_article(request): @login_required -@permission_required('tresorier') +@can_create(Paiement) def add_paiement(request): """Ajoute un moyen de paiement. Relié aux factures via foreign key""" @@ -432,14 +386,9 @@ def add_paiement(request): @login_required -@permission_required('tresorier') -def edit_paiement(request, paiementid): +@can_edit(Paiement) +def edit_paiement(request, paiement_instance, paiementid): """Edition d'un moyen de paiement""" - try: - paiement_instance = Paiement.objects.get(pk=paiementid) - except Paiement.DoesNotExist: - messages.error(request, u"Entrée inexistante") - return redirect(reverse('cotisations:index-paiement')) paiement = PaiementForm(request.POST or None, instance=paiement_instance) if paiement.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -456,10 +405,10 @@ def edit_paiement(request, paiementid): @login_required -@permission_required('tresorier') -def del_paiement(request): +@can_delete_set(Paiement) +def del_paiement(request, instances): """Suppression d'un moyen de paiement""" - paiement = DelPaiementForm(request.POST or None) + paiement = DelPaiementForm(request.POST or None, instances=instances) if paiement.is_valid(): paiement_dels = paiement.cleaned_data['paiements'] for paiement_del in paiement_dels: @@ -483,7 +432,7 @@ def del_paiement(request): @login_required -@permission_required('cableur') +@can_create(Banque) def add_banque(request): """Ajoute une banque à la liste des banques""" banque = BanqueForm(request.POST or None) @@ -498,14 +447,9 @@ def add_banque(request): @login_required -@permission_required('tresorier') -def edit_banque(request, banqueid): +@can_edit(Banque) +def edit_banque(request, banque_instance, banqueid): """Edite le nom d'une banque""" - try: - banque_instance = Banque.objects.get(pk=banqueid) - except Banque.DoesNotExist: - messages.error(request, u"Entrée inexistante") - return redirect(reverse('cotisations:index-banque')) banque = BanqueForm(request.POST or None, instance=banque_instance) if banque.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -522,10 +466,10 @@ def edit_banque(request, banqueid): @login_required -@permission_required('tresorier') -def del_banque(request): +@can_delete_set(Banque) +def del_banque(request, instances): """Supprime une banque""" - banque = DelBanqueForm(request.POST or None) + banque = DelBanqueForm(request.POST or None, instances=instances) if banque.is_valid(): banque_dels = banque.cleaned_data['banques'] for banque_del in banque_dels: @@ -543,7 +487,8 @@ def del_banque(request): @login_required -@permission_required('tresorier') +@can_view_all(Facture) +@can_change(Facture, 'control') def control(request): """Pour le trésorier, vue pour controler en masse les factures.Case à cocher, pratique""" @@ -583,7 +528,7 @@ def control(request): @login_required -@permission_required('cableur') +@can_view_all(Article) def index_article(request): """Affiche l'ensemble des articles en vente""" article_list = Article.objects.order_by('name') @@ -593,7 +538,7 @@ def index_article(request): @login_required -@permission_required('cableur') +@can_view_all(Paiement) def index_paiement(request): """Affiche l'ensemble des moyens de paiement en vente""" paiement_list = Paiement.objects.order_by('moyen') @@ -603,7 +548,7 @@ def index_paiement(request): @login_required -@permission_required('cableur') +@can_view_all(Banque) def index_banque(request): """Affiche l'ensemble des banques""" banque_list = Banque.objects.order_by('name') @@ -613,7 +558,7 @@ def index_banque(request): @login_required -@permission_required('cableur') +@can_view_all(Facture) def index(request): """Affiche l'ensemble des factures, pour les cableurs et +""" options, _created = GeneralOption.objects.get_or_create() @@ -639,60 +584,3 @@ def index(request): return render(request, 'cotisations/index.html', { 'facture_list': facture_list }) - - -@login_required -def history(request, object_name, object_id): - """Affiche l'historique de chaque objet""" - if object_name == 'facture': - try: - object_instance = Facture.objects.get(pk=object_id) - except Facture.DoesNotExist: - messages.error(request, "Facture inexistante") - return redirect(reverse('cotisations:index')) - if not request.user.has_perms(('cableur',))\ - and object_instance.user != request.user: - messages.error(request, "Vous ne pouvez pas afficher l'historique\ - d'une facture d'un autre user que vous sans droit cableur") - return redirect(reverse( - 'users:profil', - kwargs={'userid':str(request.user.id)} - )) - elif object_name == 'paiement' and request.user.has_perms(('cableur',)): - try: - object_instance = Paiement.objects.get(pk=object_id) - except Paiement.DoesNotExist: - messages.error(request, "Paiement inexistant") - return redirect(reverse('cotisations:index')) - elif object_name == 'article' and request.user.has_perms(('cableur',)): - try: - object_instance = Article.objects.get(pk=object_id) - except Article.DoesNotExist: - messages.error(request, "Article inexistante") - return redirect(reverse('cotisations:index')) - elif object_name == 'banque' and request.user.has_perms(('cableur',)): - try: - object_instance = Banque.objects.get(pk=object_id) - except Banque.DoesNotExist: - messages.error(request, "Banque inexistante") - return redirect(reverse('cotisations:index')) - else: - messages.error(request, "Objet inconnu") - return redirect(reverse('cotisations:index')) - options, _created = GeneralOption.objects.get_or_create() - pagination_number = options.pagination_number - reversions = Version.objects.get_for_object(object_instance) - paginator = Paginator(reversions, pagination_number) - page = request.GET.get('page') - try: - reversions = paginator.page(page) - except PageNotAnInteger: - # If page is not an integer, deliver first page. - reversions = paginator.page(1) - except EmptyPage: - # If page is out of range (e.g. 9999), deliver last page of results. - reversions = paginator.page(paginator.num_pages) - return render(request, 're2o/history.html', { - 'reversions': reversions, - 'object': object_instance - }) diff --git a/logs/__init__.py b/logs/__init__.py index fc1be5d7..df6e4256 100644 --- a/logs/__init__.py +++ b/logs/__init__.py @@ -21,3 +21,4 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from .acl import * diff --git a/logs/acl.py b/logs/acl.py new file mode 100644 index 00000000..4a1417f6 --- /dev/null +++ b/logs/acl.py @@ -0,0 +1,40 @@ +# -*- mode: python; coding: utf-8 -*- +# 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 © 2017 Gabriel Détraz +# Copyright © 2017 Goulven Kermarec +# Copyright © 2017 Augustin Lemesle +# +# 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. + +"""logs.acl + +Here are defined some functions to check acl on the application. +""" + +def can_view(user): + """Check if an user can view the application. + + Args: + user: The user who wants to view the application. + + Returns: + A couple (allowed, msg) where allowed is a boolean which is True if + viewing is granted and msg is a message (can be None). + """ + can = user.has_module_perms('admin') + return can, None if can else "Vous ne pouvez pas voir cette application." diff --git a/logs/templates/logs/aff_stats_logs.html b/logs/templates/logs/aff_stats_logs.html index 08178a38..be0b5a41 100644 --- a/logs/templates/logs/aff_stats_logs.html +++ b/logs/templates/logs/aff_stats_logs.html @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %} {% load logs_extra %} +{% load acl %} @@ -47,19 +48,19 @@ with this program; if not, write to the Free Software Foundation, Inc., - {% if is_bureau %} + {% can_edit_history %} - {% endif %} + {% acl_end %} {% endfor %} {% endfor %}
{{ revision.user }} {{ revision.date_created }} {{ revision.comment }} Annuler
- + {% if revisions_list.paginator %} {% include "pagination.html" with list=revisions_list %} {% endif %} diff --git a/logs/templates/logs/aff_summary.html b/logs/templates/logs/aff_summary.html index 65f71aca..c6eca27b 100644 --- a/logs/templates/logs/aff_summary.html +++ b/logs/templates/logs/aff_summary.html @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %} {% load logs_extra %} - +{% load acl %} @@ -51,14 +51,14 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %} ) - {% if is_bureau %} + {% can_edit_history %} - {% endif %} + {% acl_end %} {% elif v.version.content_type.model == 'whitelist' %} @@ -74,14 +74,14 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %} ) - {% if is_bureau %} + {% can_edit_history%} - {% endif %} + {% acl_end %} {% elif v.version.content_type.model == 'user' %} @@ -93,14 +93,14 @@ with this program; if not, write to the Free Software Foundation, Inc., ({{ v.comment }}) {% endif %} - {% if is_bureau %} + {% can_edit_history %} - {% endif %} + {% acl_end %} {% elif v.version.content_type.model == 'vente' %} @@ -112,14 +112,14 @@ with this program; if not, write to the Free Software Foundation, Inc., (+{{ v.version.object.duration }} mois) {% endif %} - {% if is_bureau %} + {% can_edit_history %} - {% endif %} + {% acl_end %} {% elif v.version.content_type.model == 'interface' %} @@ -131,14 +131,14 @@ with this program; if not, write to the Free Software Foundation, Inc., ({{ v.comment }}) {% endif %} - {% if is_bureau %} + {% can_edit_history %} - {% endif %} + {% acl_end %} {% endif %} {% endfor %} diff --git a/logs/templates/logs/sidebar.html b/logs/templates/logs/sidebar.html index 4137741f..fe7e63e7 100644 --- a/logs/templates/logs/sidebar.html +++ b/logs/templates/logs/sidebar.html @@ -23,9 +23,10 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} {% block sidebar %} - {% if is_cableur %} + {% can_view_app logs %} Résumé @@ -50,5 +51,5 @@ with this program; if not, write to the Free Software Foundation, Inc., Utilisateurs - {% endif %} + {% acl_end %} {% endblock %} diff --git a/logs/views.py b/logs/views.py index 1bd91a97..d899d2f6 100644 --- a/logs/views.py +++ b/logs/views.py @@ -41,7 +41,7 @@ from django.urls import reverse from django.shortcuts import render, redirect from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.contrib import messages -from django.contrib.auth.decorators import login_required, permission_required +from django.contrib.auth.decorators import login_required from django.db.models import Count from reversion.models import Revision @@ -50,7 +50,6 @@ from reversion.models import Version, ContentType from users.models import ( User, ServiceUser, - Right, School, ListRight, ListShell, @@ -93,7 +92,17 @@ from topologie.models import ( ) from preferences.models import GeneralOption from re2o.views import form -from re2o.utils import all_whitelisted, all_baned, all_has_access, all_adherent +from re2o.utils import ( + all_whitelisted, + all_baned, + all_has_access, + all_adherent, +) +from re2o.acl import ( + can_view_all, + can_view_app, + can_edit_history, +) from re2o.utils import all_active_assigned_interfaces_count from re2o.utils import all_active_interfaces_count, SortTable @@ -108,7 +117,7 @@ STATS_DICT = { @login_required -@permission_required('cableur') +@can_view_app('logs') def index(request): """Affiche les logs affinés, date reformatées, selectionne les event importants (ajout de droits, ajout de ban/whitelist)""" @@ -167,7 +176,7 @@ def index(request): @login_required -@permission_required('cableur') +@can_view_all(GeneralOption) def stats_logs(request): """Affiche l'ensemble des logs et des modifications sur les objets, classés par date croissante, en vrac""" @@ -197,7 +206,7 @@ def stats_logs(request): @login_required -@permission_required('bureau') +@can_edit_history def revert_action(request, revision_id): """ Annule l'action en question """ try: @@ -215,7 +224,9 @@ def revert_action(request, revision_id): @login_required -@permission_required('cableur') +@can_view_all(IpList) +@can_view_all(Interface) +@can_view_all(User) def stats_general(request): """Statistiques générales affinées sur les ip, activées, utilisées par range, et les statistiques générales sur les users : users actifs, @@ -298,7 +309,10 @@ def stats_general(request): @login_required -@permission_required('cableur') +@can_view_app('users') +@can_view_app('cotisations') +@can_view_app('machines') +@can_view_app('topologie') def stats_models(request): """Statistiques générales, affiche les comptages par models: nombre d'users, d'écoles, de droits, de bannissements, @@ -310,7 +324,6 @@ def stats_models(request): 'clubs': [Club.PRETTY_NAME, Club.objects.count()], 'serviceuser': [ServiceUser.PRETTY_NAME, ServiceUser.objects.count()], - 'right': [Right.PRETTY_NAME, Right.objects.count()], 'school': [School.PRETTY_NAME, School.objects.count()], 'listright': [ListRight.PRETTY_NAME, ListRight.objects.count()], 'listshell': [ListShell.PRETTY_NAME, ListShell.objects.count()], @@ -340,7 +353,7 @@ def stats_models(request): OuverturePortList.objects.count() ], 'vlan': [Vlan.PRETTY_NAME, Vlan.objects.count()], - 'SOA': [Mx.PRETTY_NAME, Mx.objects.count()], + 'SOA': [SOA.PRETTY_NAME, SOA.objects.count()], 'Mx': [Mx.PRETTY_NAME, Mx.objects.count()], 'Ns': [Ns.PRETTY_NAME, Ns.objects.count()], 'nas': [Nas.PRETTY_NAME, Nas.objects.count()], @@ -368,7 +381,7 @@ def stats_models(request): @login_required -@permission_required('cableur') +@can_view_app('users') def stats_users(request): """Affiche les statistiques base de données aggrégées par user : nombre de machines par user, d'etablissements par user, @@ -395,7 +408,7 @@ def stats_users(request): num=Count('whitelist') ).order_by('-num')[:10], 'Droits': User.objects.annotate( - num=Count('right') + num=Count('groups') ).order_by('-num')[:10], }, 'Etablissement': { @@ -422,7 +435,7 @@ def stats_users(request): @login_required -@permission_required('cableur') +@can_view_app('users') def stats_actions(request): """Vue qui affiche les statistiques de modifications d'objets par utilisateurs. diff --git a/machines/__init__.py b/machines/__init__.py index fc1be5d7..df6e4256 100644 --- a/machines/__init__.py +++ b/machines/__init__.py @@ -21,3 +21,4 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from .acl import * diff --git a/machines/acl.py b/machines/acl.py new file mode 100644 index 00000000..f77a93c7 --- /dev/null +++ b/machines/acl.py @@ -0,0 +1,40 @@ +# -*- mode: python; coding: utf-8 -*- +# 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 © 2017 Gabriel Détraz +# Copyright © 2017 Goulven Kermarec +# Copyright © 2017 Augustin Lemesle +# +# 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. + +"""machines.acl + +Here are defined some functions to check acl on the application. +""" + +def can_view(user): + """Check if an user can view the application. + + Args: + user: The user who wants to view the application. + + Returns: + A couple (allowed, msg) where allowed is a boolean which is True if + viewing is granted and msg is a message (can be None). + """ + can = user.has_module_perms('machines') + return can, None if can else "Vous ne pouvez pas voir cette application." diff --git a/machines/forms.py b/machines/forms.py index d6aa5e3e..f23f1d7d 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -38,6 +38,8 @@ from __future__ import unicode_literals from django.forms import ModelForm, Form from django import forms +from re2o.field_permissions import FieldPermissionFormMixin + from .models import ( Domain, Machine, @@ -58,7 +60,7 @@ from .models import ( ) -class EditMachineForm(ModelForm): +class EditMachineForm(FieldPermissionFormMixin, ModelForm): """Formulaire d'édition d'une machine""" class Meta: model = Machine @@ -117,10 +119,10 @@ class AddInterfaceForm(EditInterfaceForm): fields = ['type', 'ipv4', 'mac_address', 'details'] def __init__(self, *args, **kwargs): - infra = kwargs.pop('infra') + user = kwargs.pop('user') super(AddInterfaceForm, self).__init__(*args, **kwargs) self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" - if not infra: + if not IpType.can_use_all(user): self.fields['type'].queryset = MachineType.objects.filter( ip_type__in=IpType.objects.filter(need_infra=False) ) @@ -146,13 +148,14 @@ class BaseEditInterfaceForm(EditInterfaceForm): fields = ['type', 'ipv4', 'mac_address', 'details'] def __init__(self, *args, **kwargs): - infra = kwargs.pop('infra') + user = kwargs.pop('user') super(BaseEditInterfaceForm, self).__init__(*args, **kwargs) self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" - if not infra: + if not MachineType.can_use_all(user): self.fields['type'].queryset = MachineType.objects.filter( ip_type__in=IpType.objects.filter(need_infra=False) ) + if not IpType.can_use_all(user): self.fields['ipv4'].queryset = IpList.objects.filter( interface__isnull=True ).filter(ip_type__in=IpType.objects.filter(need_infra=False)) @@ -177,9 +180,10 @@ class AliasForm(ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) - infra = kwargs.pop('infra') + user = kwargs.pop('user') super(AliasForm, self).__init__(*args, prefix=prefix, **kwargs) - if not infra: + can_use_all, reason = Extension.can_use_all(user) + if not can_use_all: self.fields['extension'].queryset = Extension.objects.filter( need_infra=False ) @@ -233,11 +237,19 @@ class MachineTypeForm(ModelForm): class DelMachineTypeForm(Form): """Suppression d'un ou plusieurs machinetype""" machinetypes = forms.ModelMultipleChoiceField( - queryset=MachineType.objects.all(), + queryset=MachineType.objects.none(), label="Types de machines actuelles", widget=forms.CheckboxSelectMultiple ) + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelMachineTypeForm, self).__init__(*args, **kwargs) + if instances: + self.fields['machinetypes'].queryset = instances + else: + self.fields['machinetypes'].queryset = MachineType.objects.all() + class IpTypeForm(ModelForm): """Formulaire d'ajout d'un iptype. Pas d'edition de l'ip de start et de @@ -264,11 +276,19 @@ class EditIpTypeForm(IpTypeForm): class DelIpTypeForm(Form): """Suppression d'un ou plusieurs iptype""" iptypes = forms.ModelMultipleChoiceField( - queryset=IpType.objects.all(), + queryset=IpType.objects.none(), label="Types d'ip actuelles", widget=forms.CheckboxSelectMultiple ) + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelIpTypeForm, self).__init__(*args, **kwargs) + if instances: + self.fields['iptypes'].queryset = instances + else: + self.fields['iptypes'].queryset = IpType.objects.all() + class ExtensionForm(ModelForm): """Formulaire d'ajout et edition d'une extension""" @@ -288,11 +308,19 @@ class ExtensionForm(ModelForm): class DelExtensionForm(Form): """Suppression d'une ou plusieurs extensions""" extensions = forms.ModelMultipleChoiceField( - queryset=Extension.objects.all(), + queryset=Extension.objects.none(), label="Extensions actuelles", widget=forms.CheckboxSelectMultiple ) + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelExtensionForm, self).__init__(*args, **kwargs) + if instances: + self.fields['extensions'].queryset = instances + else: + self.fields['extensions'].queryset = Extension.objects.all() + class SOAForm(ModelForm): """Ajout et edition d'un SOA""" @@ -308,11 +336,19 @@ class SOAForm(ModelForm): class DelSOAForm(Form): """Suppression d'un ou plusieurs SOA""" soa = forms.ModelMultipleChoiceField( - queryset=SOA.objects.all(), + queryset=SOA.objects.none(), label="SOA actuels", widget=forms.CheckboxSelectMultiple ) + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelSOAForm, self).__init__(*args, **kwargs) + if instances: + self.fields['soa'].queryset = instances + else: + self.fields['soa'].queryset = SOA.objects.all() + class MxForm(ModelForm): """Ajout et edition d'un MX""" @@ -327,15 +363,22 @@ class MxForm(ModelForm): interface_parent=None ).select_related('extension') - class DelMxForm(Form): """Suppression d'un ou plusieurs MX""" mx = forms.ModelMultipleChoiceField( - queryset=Mx.objects.all(), + queryset=Mx.objects.none(), label="MX actuels", widget=forms.CheckboxSelectMultiple ) + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelMxForm, self).__init__(*args, **kwargs) + if instances: + self.fields['mx'].queryset = instances + else: + self.fields['mx'].queryset = Mx.objects.all() + class NsForm(ModelForm): """Ajout d'un NS pour une zone @@ -356,11 +399,19 @@ class NsForm(ModelForm): class DelNsForm(Form): """Suppresion d'un ou plusieurs NS""" ns = forms.ModelMultipleChoiceField( - queryset=Ns.objects.all(), + queryset=Ns.objects.none(), label="Enregistrements NS actuels", widget=forms.CheckboxSelectMultiple ) + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelNsForm, self).__init__(*args, **kwargs) + if instances: + self.fields['ns'].queryset = instances + else: + self.fields['ns'].queryset = Ns.objects.all() + class TxtForm(ModelForm): """Ajout d'un txt pour une zone""" @@ -376,12 +427,20 @@ class TxtForm(ModelForm): class DelTxtForm(Form): """Suppression d'un ou plusieurs TXT""" txt = forms.ModelMultipleChoiceField( - queryset=Txt.objects.all(), + queryset=Txt.objects.none(), label="Enregistrements Txt actuels", widget=forms.CheckboxSelectMultiple ) - + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelTxtForm, self).__init__(*args, **kwargs) + if instances: + self.fields['txt'].queryset = instances + else: + self.fields['txt'].queryset = Txt.objects.all() + + class SrvForm(ModelForm): """Ajout d'un srv pour une zone""" class Meta: @@ -396,11 +455,19 @@ class SrvForm(ModelForm): class DelSrvForm(Form): """Suppression d'un ou plusieurs Srv""" srv = forms.ModelMultipleChoiceField( - queryset=Srv.objects.all(), + queryset=Srv.objects.none(), label="Enregistrements Srv actuels", widget=forms.CheckboxSelectMultiple ) + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelSrvForm, self).__init__(*args, **kwargs) + if instances: + self.fields['srv'].queryset = instances + else: + self.fields['srv'].queryset = Srv.objects.all() + class NasForm(ModelForm): """Ajout d'un type de nas (machine d'authentification, @@ -417,11 +484,19 @@ class NasForm(ModelForm): class DelNasForm(Form): """Suppression d'un ou plusieurs nas""" nas = forms.ModelMultipleChoiceField( - queryset=Nas.objects.all(), + queryset=Nas.objects.none(), label="Enregistrements Nas actuels", widget=forms.CheckboxSelectMultiple ) + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelNasForm, self).__init__(*args, **kwargs) + if instances: + self.fields['nas'].queryset = instances + else: + self.fields['nas'].queryset = Nas.objects.all() + class ServiceForm(ModelForm): """Ajout et edition d'une classe de service : dns, dhcp, etc""" @@ -446,11 +521,19 @@ class ServiceForm(ModelForm): class DelServiceForm(Form): """Suppression d'un ou plusieurs service""" service = forms.ModelMultipleChoiceField( - queryset=Service.objects.all(), + queryset=Service.objects.none(), label="Services actuels", widget=forms.CheckboxSelectMultiple ) + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelServiceForm, self).__init__(*args, **kwargs) + if instances: + self.fields['service'].queryset = instances + else: + self.fields['service'].queryset = Service.objects.all() + class VlanForm(ModelForm): """Ajout d'un vlan : id, nom""" @@ -466,11 +549,19 @@ class VlanForm(ModelForm): class DelVlanForm(Form): """Suppression d'un ou plusieurs vlans""" vlan = forms.ModelMultipleChoiceField( - queryset=Vlan.objects.all(), + queryset=Vlan.objects.none(), label="Vlan actuels", widget=forms.CheckboxSelectMultiple ) + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelVlanForm, self).__init__(*args, **kwargs) + if instances: + self.fields['vlan'].queryset = instances + else: + self.fields['vlan'].queryset = Vlan.objects.all() + class EditOuverturePortConfigForm(ModelForm): """Edition de la liste des profils d'ouverture de ports diff --git a/machines/migrations/0070_auto_20171231_1947.py b/machines/migrations/0070_auto_20171231_1947.py new file mode 100644 index 00000000..ef441d01 --- /dev/null +++ b/machines/migrations/0070_auto_20171231_1947.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-12-31 18:47 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0069_auto_20171116_0822'), + ] + + operations = [ + migrations.AlterModelOptions( + name='domain', + options={'permissions': (('view_domain', 'Peut voir un objet domain'),)}, + ), + migrations.AlterModelOptions( + name='extension', + options={'permissions': (('view_extension', 'Peut voir un objet extension'), ('use_all_extension', 'Peut utiliser toutes les extension'))}, + ), + migrations.AlterModelOptions( + name='interface', + options={'permissions': (('view_interface', 'Peut voir un objet interface'),)}, + ), + migrations.AlterModelOptions( + name='iplist', + options={'permissions': (('view_iplist', 'Peut voir un objet iplist'),)}, + ), + migrations.AlterModelOptions( + name='iptype', + options={'permissions': (('view_iptype', 'Peut voir un objet iptype'), ('use_all_iptype', 'Peut utiliser tous les iptype'))}, + ), + migrations.AlterModelOptions( + name='machine', + options={'permissions': (('view_machine', 'Peut voir un objet machine quelquonque'), ('change_machine_user', "Peut changer le propriétaire d'une machine"))}, + ), + migrations.AlterModelOptions( + name='machinetype', + options={'permissions': (('view_machinetype', 'Peut voir un objet machinetype'), ('use_all_machinetype', "Peut utiliser n'importe quel type de machine"))}, + ), + migrations.AlterModelOptions( + name='mx', + options={'permissions': (('view_mx', 'Peut voir un objet mx'),)}, + ), + migrations.AlterModelOptions( + name='nas', + options={'permissions': (('view_nas', 'Peut voir un objet Nas'),)}, + ), + migrations.AlterModelOptions( + name='ns', + options={'permissions': (('view_nx', 'Peut voir un objet nx'),)}, + ), + migrations.AlterModelOptions( + name='ouvertureportlist', + options={'permissions': (('view_ouvertureportlist', 'Peut voir un objet ouvertureport'),)}, + ), + migrations.AlterModelOptions( + name='service', + options={'permissions': (('view_service', 'Peut voir un objet service'),)}, + ), + migrations.AlterModelOptions( + name='soa', + options={'permissions': (('view_soa', 'Peut voir un objet soa'),)}, + ), + migrations.AlterModelOptions( + name='srv', + options={'permissions': (('view_soa', 'Peut voir un objet soa'),)}, + ), + migrations.AlterModelOptions( + name='txt', + options={'permissions': (('view_txt', 'Peut voir un objet txt'),)}, + ), + migrations.AlterModelOptions( + name='vlan', + options={'permissions': (('view_vlan', 'Peut voir un objet vlan'),)}, + ), + ] diff --git a/machines/migrations/0071_auto_20171231_2100.py b/machines/migrations/0071_auto_20171231_2100.py new file mode 100644 index 00000000..776edd82 --- /dev/null +++ b/machines/migrations/0071_auto_20171231_2100.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-12-31 20:00 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0070_auto_20171231_1947'), + ] + + operations = [ + migrations.AlterModelOptions( + name='ns', + options={'permissions': (('view_ns', 'Peut voir un objet ns'),)}, + ), + ] diff --git a/machines/models.py b/machines/models.py index 9bfb4b55..f2dedee9 100644 --- a/machines/models.py +++ b/machines/models.py @@ -37,8 +37,13 @@ from django.core.validators import MaxValueValidator from macaddress.fields import MACAddressField +from re2o.field_permissions import FieldPermissionModelMixin -class Machine(models.Model): +import users.models +import preferences.models + + +class Machine(FieldPermissionModelMixin, models.Model): """ Class définissant une machine, object parent user, objets fils interfaces""" PRETTY_NAME = "Machine" @@ -52,6 +57,97 @@ class Machine(models.Model): ) active = models.BooleanField(default=True) + class Meta: + permissions = ( + ("view_machine", "Peut voir un objet machine quelquonque"), + ("change_machine_user", "Peut changer le propriétaire d'une machine"), + ) + + def get_instance(machineid, *args, **kwargs): + """Récupère une instance + :param machineid: Instance id à trouver + :return: Une instance machine évidemment""" + return Machine.objects.get(pk=machineid) + + @staticmethod + def can_change_user(user_request, *args, **kwargs): + """Checks if an user is allowed to change the user who owns a + Machine. + + Args: + user_request: The user requesting to change owner. + + Returns: + A tuple with a boolean stating if edition is allowed and an + explanation message. + """ + return user_request.has_perm('machines.change_machine_user'), "Vous ne pouvez pas \ + modifier l'utilisateur de la machine." + + def can_create(user_request, userid, *args, **kwargs): + """Vérifie qu'un user qui fait la requète peut bien créer la machine + et n'a pas atteint son quota, et crée bien une machine à lui + :param user_request: Utilisateur qui fait la requête + :param userid: id de l'user dont on va créer une machine + :return: soit True, soit False avec la raison de l'échec""" + try: + user = users.models.User.objects.get(pk=userid) + except users.models.User.DoesNotExist: + return False, u"Utilisateur inexistant" + options, created = preferences.models.OptionalMachine.objects.get_or_create() + max_lambdauser_interfaces = options.max_lambdauser_interfaces + if not user_request.has_perm('machines.add_machine'): + if user != user_request: + return False, u"Vous ne pouvez pas ajouter une machine à un\ + autre user que vous sans droit" + if user.user_interfaces().count() >= max_lambdauser_interfaces: + return False, u"Vous avez atteint le maximum d'interfaces\ + autorisées que vous pouvez créer vous même (%s) "\ + % max_lambdauser_interfaces + return True, None + + def can_edit(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien éditer cette instance particulière (soit + machine de soi, soit droit particulier + :param self: instance machine à éditer + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison le cas échéant""" + if not user_request.has_perm('machines.change_machine') and self.user != user_request: + return False, u"Vous ne pouvez pas éditer une machine d'un autre user\ + que vous sans droit" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien supprimer cette instance particulière (soit + machine de soi, soit droit particulier + :param self: instance machine à supprimer + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + if not user_request.has_perm('machines.delete_machine') and self.user != user_request: + return False, u"Vous ne pouvez pas éditer une machine d'un autre user\ + que vous sans droit" + return True, None + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des machines, + droit particulier correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + if not user_request.has_perm('machines.view_machine'): + return False, u"Vous ne pouvez pas afficher l'ensemble des machines sans permission" + return True, None + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière (soit + machine de soi, soit droit particulier + :param self: instance machine à éditer + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + if not user_request.has_perm('machines.view_machine') and self.user != user_request: + return False, u"Vous n'avez pas droit de voir les machines autre\ + que les vôtres" + return True, None + def __str__(self): return str(self.user) + ' - ' + str(self.id) + ' - ' + str(self.name) @@ -68,11 +164,81 @@ class MachineType(models.Model): null=True ) + class Meta: + permissions = ( + ("view_machinetype", "Peut voir un objet machinetype"), + ("use_all_machinetype", "Peut utiliser n'importe quel type de machine"), + ) + def all_interfaces(self): """ Renvoie toutes les interfaces (cartes réseaux) de type machinetype""" return Interface.objects.filter(type=self) + def get_instance(machinetypeid, *args, **kwargs): + """Récupère une instance + :param machinetypeid: Instance id à trouver + :return: Une instance machinetype évidemment""" + return MachineType.objects.get(pk=machinetypeid) + + def can_create(user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour créer + un type de machine + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.add_machinetype'), u"Vous n'avez pas le droit\ + de créer un type de machine" + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour editer + cette instance type de machine + :param self: Instance machinetype à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_machinetype'): + return False, u"Vous n'avez pas le droit d'éditer des types de machine" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien supprimer cette instance particulière (soit + machinetype de soi, soit droit particulier + :param self: instance machinetype à supprimer + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + if not user_request.has_perm('machines.delete_machinetype'): + return False, u"Vous n'avez pas le droit de supprimer des types de machines" + return True, None + + def can_use_all(user_request, *args, **kwargs): + """Check if an user can use every MachineType. + + Args: + user_request: The user requesting edition. + Returns: + A tuple with a boolean stating if user can acces and an explanation + message is acces is not allowed. + """ + if not user_request.has_perm('machines.use_all_machinetype'): + return False, u"Vous n'avez pas le droit d'utiliser tout types de machines" + return True, None + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des machinetype, + droit particulier correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_machinetype'), u"Vous n'avez pas le droit\ + de voir les types de machines" + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit view objet + :param self: instance machinetype à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_machinetype'), u"Vous n'avez pas le droit\ + de voir les types de machines" + def __str__(self): return self.type @@ -103,6 +269,12 @@ class IpType(models.Model): null=True ) + class Meta: + permissions = ( + ("view_iptype", "Peut voir un objet iptype"), + ("use_all_iptype", "Peut utiliser tous les iptype"), + ) + @cached_property def ip_range(self): """ Renvoie un objet IPRange à partir de l'objet IpType""" @@ -183,6 +355,63 @@ class IpType(models.Model): self.clean() super(IpType, self).save(*args, **kwargs) + def get_instance(iptypeid, *args, **kwargs): + """Récupère une instance + :param iptypeid: Instance id à trouver + :return: Une instance iptype évidemment""" + return IpType.objects.get(pk=iptypeid) + + def can_use_all(user_request, *args, **kwargs): + """Superdroit qui permet d'utiliser toutes les extensions sans restrictions + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.use_all_iptype'), None + + + def can_create(user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour créer + un type d'ip + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.add_iptype'), u"Vous n'avez pas le droit\ + de créer un type d'ip" + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour editer + cette instance iptype + :param self: Instance iptype à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_iptype'): + return False, u"Vous n'avez pas le droit d'éditer des types d'ip" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour supprimer + cette instance iptype + :param self: Instance iptype à delete + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.delete_iptype'), u"Vous n'avez pas le droit\ + de supprimer un type d'ip" + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des iptype, + droit particulier view objet correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_iptype'), u"Vous n'avez pas le droit\ + de voir les types d'ip" + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit view objet + :param self: instance iptype à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_iptype'), u"Vous n'avez pas le droit\ + de voir les types d'ip" + def __str__(self): return self.type @@ -196,6 +425,61 @@ class Vlan(models.Model): name = models.CharField(max_length=256) comment = models.CharField(max_length=256, blank=True) + class Meta: + permissions = ( + ("view_vlan", "Peut voir un objet vlan"), + ) + + def get_instance(vlanid, *args, **kwargs): + """Récupère une instance + :param vlanid: Instance id à trouver + :return: Une instance vlan évidemment""" + return Vlan.objects.get(pk=vlanid) + + def can_create(user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour créer + un vlan + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.add_vlan'), u"Vous n'avez pas le droit\ + de créer un vlan" + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour editer + cette instance vlan + :param self: Instance vlan à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_vlan'): + return False, u"Vous n'avez pas le droit d'éditer des vlans" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour supprimer + cette instance vlan + :param self: Instance vlan à delete + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.delete_vlan'), u"Vous n'avez pas le droit\ + de suprimer un vlan" + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des vlan, + droit particulier view objet correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_vlan'), u"Vous n'avez pas le droit\ + de voir les vlans" + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit view objet + :param self: instance vlan à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_vlan'), u"Vous n'avez pas le droit\ + de voir les vlans" + def __str__(self): return self.name @@ -230,6 +514,62 @@ class Nas(models.Model): ) autocapture_mac = models.BooleanField(default=False) + class Meta: + permissions = ( + ("view_nas", "Peut voir un objet Nas"), + ) + + def get_instance(nasid, *args, **kwargs): + """Récupère une instance + :param nasid: Instance id à trouver + :return: Une instance nas évidemment""" + return Nas.objects.get(pk=nasid) + + def can_create(user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour créer + un nas + :param user_request: instance utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.add_nas'), u"Vous n'avez pas le droit\ + de créer un nas" + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour editer + cette instance nas + :param self: Instance nas à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_nas'): + return False, u"Vous n'avez pas le droit d'éditer des nas" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour supprimer + cette instance nas + :param self: Instance nas à delete + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.delete_nas'), u"Vous n'avez pas le droit\ + de supprimer un nas" + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des nas, + droit particulier view objet correspondant + + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_nas'), u"Vous n'avez pas le droit\ + de voir les nas" + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit view objet + :param self: instance nas à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_nas'), u"Vous n'avez pas le droit\ + de voir les nas" + def __str__(self): return self.name @@ -266,6 +606,61 @@ class SOA(models.Model): help_text='Time To Live' ) + class Meta: + permissions = ( + ("view_soa", "Peut voir un objet soa"), + ) + + def get_instance(soaid, *args, **kwargs): + """Récupère une instance + :param soaid: Instance id à trouver + :return: Une instance soa évidemment""" + return SOA.objects.get(pk=soaid) + + def can_create(user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour créer + un soa + :param user_request: instance utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.add_soa'), u"Vous n'avez pas le droit\ + de créer un enregistrement SOA" + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour editer + cette instance soa + :param self: Instance soa à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_soa'): + return False, u"Vous n'avez pas le droit d'éditer des enregistrements SOA" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour supprimer + cette instance soa + :param self: Instance soa à delete + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.delete_soa'), u"Vous n'avez pas le droit\ + de supprimer des enregistrements SOA" + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des soa, + droit particulier view objet correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_soa'), u"Vous n'avez pas le droit\ + de voir les enreistrement SOA" + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit view objet + :param self: instance soa à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_soa'), u"Vous n'avez pas le droit\ + de voir les enreistrement SOA" + def __str__(self): return str(self.name) @@ -336,6 +731,12 @@ class Extension(models.Model): default=SOA.new_default_soa ) + class Meta: + permissions = ( + ("view_extension", "Peut voir un objet extension"), + ("use_all_extension", "Peut utiliser toutes les extension"), + ) + @cached_property def dns_entry(self): """ Une entrée DNS A et AAAA sur origin (zone self)""" @@ -348,6 +749,62 @@ class Extension(models.Model): entry += "@ IN AAAA " + str(self.origin_v6) return entry + def get_instance(extensionid, *args, **kwargs): + """Récupère une instance + :param extensionid: Instance id à trouver + :return: Une instance extension évidemment""" + return Extension.objects.get(pk=extensionid) + + def can_create(user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour créer + une extension + :param user_request: instance utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.add_extension'), u"Vous n'avez pas le droit\ + de créer une extension" + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour editer + cette instance extension + :param self: Instance extension à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_extension'): + return False, u"Vous n'avez pas le droit d'éditer des extensions" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour supprimer + cette instance extension + :param self: Instance extension à delete + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.delete_extension'), u"Vous n'avez pas le droit\ + de supprimer des extension" + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des extension, + droit particulier view objet correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_extension'), u"Vous n'avez pas le droit\ + de voir les extensions" + + def can_use_all(user_request, *args, **kwargs): + """Superdroit qui permet d'utiliser toutes les extensions sans restrictions + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.use_all_extension'), None + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit view objet + :param self: instance extension à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_extension'), u"Vous n'avez pas le droit\ + de voir les extensions" + def __str__(self): return self.name @@ -367,12 +824,67 @@ class Mx(models.Model): priority = models.PositiveIntegerField(unique=True) name = models.OneToOneField('Domain', on_delete=models.PROTECT) + class Meta: + permissions = ( + ("view_mx", "Peut voir un objet mx"), + ) + @cached_property def dns_entry(self): """Renvoie l'entrée DNS complète pour un MX à mettre dans les fichiers de zones""" return "@ IN MX " + str(self.priority).ljust(3) + " " + str(self.name) + def get_instance(mxid, *args, **kwargs): + """Récupère une instance + :param mxid: Instance id à trouver + :return: Une instance mx évidemment""" + return Mx.objects.get(pk=mxid) + + def can_create(user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour créer + un mx + :param user_request: instance utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.add_mx'), u"Vous n'avez pas le droit\ + de créer un enregistrement MX" + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour editer + cette instance mx + :param self: Instance mx à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_mx'): + return False, u"Vous n'avez pas le droit d'éditer des enregstrements MX" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour del + cette instance mx + :param self: Instance mx à delete + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.delete_mx'), u"Vous n'avez pas le droit\ + de supprimer un enregistrement MX" + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des mx, + droit particulier view objet correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_mx'), u"Vous n'avez pas le droit\ + de voir les enregistrements MX" + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit view objet + :param self: instance mx à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_mx'), u"Vous n'avez pas le droit\ + de voir les enregistrements MX" + def __str__(self): return str(self.zone) + ' ' + str(self.priority) + ' ' + str(self.name) @@ -384,11 +896,66 @@ class Ns(models.Model): zone = models.ForeignKey('Extension', on_delete=models.PROTECT) ns = models.OneToOneField('Domain', on_delete=models.PROTECT) + class Meta: + permissions = ( + ("view_ns", "Peut voir un objet ns"), + ) + @cached_property def dns_entry(self): """Renvoie un enregistrement NS complet pour les filezones""" return "@ IN NS " + str(self.ns) + def get_instance(nsid, *args, **kwargs): + """Récupère une instance + :param nsid: Instance id à trouver + :return: Une instance ns évidemment""" + return Ns.objects.get(pk=nsid) + + def can_create(user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour créer + un ns + :param user_request: instance utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.add_ns'), u"Vous n'avez pas le droit\ + de créer un enregistrement NS" + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour editer + cette instance ns + :param self: Instance ns à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_ns'): + return False, u"Vous n'avez pas le droit d'éditer des enregistrements NS" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour del + cette instance ns + :param self: Instance ns à delete + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.del_ns'), u"Vous n'avez pas le droit\ + de supprimer un enregistrement NS" + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des ns, + droit particulier view objet correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_ns'), u"Vous n'avez pas le droit\ + de voir les enregistrements NS" + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit view objet + :param self: instance ns à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_ns'), u"Vous n'avez pas le droit\ + de voir les enregistrements NS" + def __str__(self): return str(self.zone) + ' ' + str(self.ns) @@ -401,6 +968,61 @@ class Txt(models.Model): field1 = models.CharField(max_length=255) field2 = models.TextField(max_length=2047) + class Meta: + permissions = ( + ("view_txt", "Peut voir un objet txt"), + ) + + def get_instance(txtid, *args, **kwargs): + """Récupère une instance + :param txtid: Instance id à trouver + :return: Une instance txt évidemment""" + return Txt.objects.get(pk=txtid) + + def can_create(user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour créer + un txt + :param user_request: instance utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.add_txt'), u"Vous n'avez pas le droit\ + de créer un enregistrement TXT" + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour editer + cette instance txt + :param self: Instance txt à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_txt'): + return False, u"Vous n'avez pas le droit d'éditer des enregistrement TXT" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour del + cette instance txt + :param self: Instance txt à delete + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.delete_txt'), u"Vous n'avez pas le droit\ + de supprimer des enregistrements TXT" + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des txt, + droit particulier view objet correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_txt'), u"Vous n'avez pas le droit\ + de voir les enregistrements TXT" + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit view objet + :param self: instance txt à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_txt'), u"Vous n'avez pas le droit\ + de voir les enregistrements TXT" + def __str__(self): return str(self.zone) + " : " + str(self.field1) + " " +\ str(self.field2) @@ -454,6 +1076,61 @@ class Srv(models.Model): help_text="Serveur cible" ) + class Meta: + permissions = ( + ("view_soa", "Peut voir un objet soa"), + ) + + def get_instance(srvid, *args, **kwargs): + """Récupère une instance + :param srvid: Instance id à trouver + :return: Une instance srv évidemment""" + return Srv.objects.get(pk=srvid) + + def can_create(user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour créer + un srv + :param user_request: instance utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.add_soa'), u"Vous n'avez pas le droit\ + de créer un enregistrement SRV" + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour editer + cette instance srv + :param self: Instance srv à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_soa'): + return False, u"Vous n'avez pas le droit d'éditer des enregistrements SRV" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour del + cette instance srv + :param self: Instance srv à delete + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.delete_soa'), u"Vous n'avez pas le droit\ + de supprimer un enregistrement SRV" + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des srv, + droit particulier view objet correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_soa'), u"Vous n'avez pas le droit\ + de voir les enregistrements SRV" + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit view objet + :param self: instance srv à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_soa'), u"Vous n'avez pas le droit\ + de voir les enregistrements SRV" + def __str__(self): return str(self.service) + ' ' + str(self.protocole) + ' ' +\ str(self.extension) + ' ' + str(self.priority) +\ @@ -490,6 +1167,11 @@ class Interface(models.Model): details = models.CharField(max_length=255, blank=True) port_lists = models.ManyToManyField('OuverturePortList', blank=True) + class Meta: + permissions = ( + ("view_interface", "Peut voir un objet interface"), + ) + @cached_property def is_active(self): """ Renvoie si une interface doit avoir accès ou non """ @@ -571,6 +1253,77 @@ class Interface(models.Model): correspondent pas") super(Interface, self).save(*args, **kwargs) + def get_instance(interfaceid, *args, **kwargs): + """Récupère une instance + :param interfaceid: Instance id à trouver + :return: Une instance interface évidemment""" + return Interface.objects.get(pk=interfaceid) + + def can_create(user_request, machineid, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour créer + une interface, ou bien que la machine appartient bien à l'user + :param macineid: Id de la machine parente de l'interface + :param user_request: instance utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + try: + machine = Machine.objects.get(pk=machineid) + except Machine.DoesNotExist: + return False, u"Machine inexistante" + if not user_request.has_perm('machines.add_interface'): + options, created = preferences.models.OptionalMachine.objects.get_or_create() + max_lambdauser_interfaces = options.max_lambdauser_interfaces + if machine.user != user_request: + return False, u"Vous ne pouvez pas ajouter une interface à une\ + machine d'un autre user que vous sans droit" + if machine.user.user_interfaces().count() >= max_lambdauser_interfaces: + return False, u"Vous avez atteint le maximum d'interfaces\ + autorisées que vous pouvez créer vous même (%s) "\ + % max_lambdauser_interfaces + return True, None + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour editer + cette instance interface, ou qu'elle lui appartient + :param self: Instance interface à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_interface') and self.machine.user != user_request: + return False, u"Vous ne pouvez pas éditer une machine\ + d'un autre user que vous sans droit" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits delete object pour del + cette instance interface, ou qu'elle lui appartient + :param self: Instance interface à del + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.delete_interface') and self.machine.user != user_request: + return False, u"Vous ne pouvez pas éditer une machine d'un autre\ + user que vous sans droit" + return True, None + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des interfaces, + droit particulier view objet correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + if not user_request.has_perm('machines.view_interface'): + return False, u"Vous n'avez pas le droit de voir des machines autre\ + que les vôtres" + return True, None + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit view objet ou qu'elle appartient à l'user + :param self: instance interface à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + if not user_request.has_perm('machines.view_interface') and self.machine.user != user_request: + return False, u"Vous n'avez pas le droit de voir des machines autre\ + que les vôtres" + return True, None + def __str__(self): try: domain = self.domain @@ -618,6 +1371,9 @@ class Domain(models.Model): class Meta: unique_together = (("name", "extension"),) + permissions = ( + ("view_domain", "Peut voir un objet domain"), + ) def get_extension(self): """ Retourne l'extension de l'interface parente si c'est un A @@ -670,6 +1426,96 @@ class Domain(models.Model): self.full_clean() super(Domain, self).save(*args, **kwargs) + @cached_property + def get_source_interface(self): + """Renvoie l'interface source : + - l'interface reliée si c'est un A + - si c'est un cname, suit le cname jusqu'à atteindre le A + et renvoie l'interface parente + Fonction récursive""" + if self.interface_parent: + return self.interface_parent + else: + return self.cname.get_parent_interface() + + def get_instance(domainid, *args, **kwargs): + """Récupère une instance + :param domainid: Instance id à trouver + :return: Une instance domain évidemment""" + return Domain.objects.get(pk=domainid) + + def can_create(user_request, interfaceid, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour créer + un domain, ou possède l'interface associée + :param interfaceid: Id de l'interface associée à cet objet domain + :param user_request: instance utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + try: + interface = Interface.objects.get(pk=interfaceid) + except Interface.DoesNotExist: + return False, u"Interface inexistante" + if not user_request.has_perm('machines.add_domain'): + options, created = preferences.models.OptionalMachine.objects.get_or_create() + max_lambdauser_aliases = options.max_lambdauser_aliases + if interface.machine.user != user_request: + return False, u"Vous ne pouvez pas ajouter un alias à une\ + machine d'un autre user que vous sans droit" + if Domain.objects.filter( + cname__in=Domain.objects.filter( + interface_parent__in=interface.machine.user.user_interfaces() + ) + ).count() >= max_lambdauser_aliases: + return False, u"Vous avez atteint le maximum d'alias\ + autorisés que vous pouvez créer vous même (%s) "\ + % max_lambdauser_aliases + return True, None + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits pour editer + cette instance domain + :param self: Instance domain à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_domain') and\ + self.get_source_interface.machine.user != user_request: + return False, u"Vous ne pouvez pas editer un alias à une machine\ + d'un autre user que vous sans droit" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits delete object pour del + cette instance domain, ou qu'elle lui appartient + :param self: Instance domain à del + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.delete_domain') and\ + self.get_source_interface.machine.user != user_request: + return False, u"Vous ne pouvez pas supprimer un alias à une machine\ + d'un autre user que vous sans droit" + return True, None + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des domain, + droit particulier view objet correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + if not user_request.has_perm('machines.view_domain'): + return False, u"Vous ne pouvez pas supprimer un alias à une machine\ + d'un autre user que vous sans droit" + return True, None + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit view objet ou qu'elle appartient à l'user + :param self: instance domain à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + if not user_request.has_perm('machines.view_domain') and\ + self.get_source_interface.machine.user != user_request: + return False, u"Vous n'avez pas le droit de voir des machines autre\ + que les vôtres" + return True, None + def __str__(self): return str(self.name) + str(self.extension) @@ -680,6 +1526,11 @@ class IpList(models.Model): ipv4 = models.GenericIPAddressField(protocol='IPv4', unique=True) ip_type = models.ForeignKey('IpType', on_delete=models.CASCADE) + class Meta: + permissions = ( + ("view_iplist", "Peut voir un objet iplist"), + ) + @cached_property def need_infra(self): """ Permet de savoir si un user basique peut assigner cette ip ou @@ -697,6 +1548,59 @@ class IpList(models.Model): self.clean() super(IpList, self).save(*args, **kwargs) + def get_instance(iplistid, *args, **kwargs): + """Récupère une instance + :param iplistid: Instance id à trouver + :return: Une instance iplist évidemment""" + return IpList.objects.get(pk=iplistid) + + def can_create(user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour créer + une ip + :param user_request: instance utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.add_iplist'), u"Vous n'avez pas le droit\ + de créer une ip" + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour editer + cette instance ip + :param self: Instance ip à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_iplist'): + return False, u"Vous n'avez pas le droit d'éditer des enregistrements ip" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour delete + cette instance ip + :param self: Instance ip à delete + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.delete_iplist'): + return False, u"Vous n'avez pas le droit d'éditer des enregistrements ip" + return True, None + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des ip, + droit particulier view objet correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + if not user_request.has_perm('machines.view_iplist'): + return False, u"Vous n'avez pas le droit de voir des enregistrements ip" + return True, None + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit infra + :param self: instance iplist à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + if not user_request.has_perm('machines.view_iplist'): + return False, u"Vous n'avez pas le droit de voir des enregistrements ip" + return True, None + def __str__(self): return self.ipv4 @@ -716,6 +1620,11 @@ class Service(models.Model): ) servers = models.ManyToManyField('Interface', through='Service_link') + class Meta: + permissions = ( + ("view_service", "Peut voir un objet service"), + ) + def ask_regen(self): """ Marque à True la demande de régénération pour un service x """ Service_link.objects.filter(service=self).exclude(asked_regen=True)\ @@ -737,6 +1646,56 @@ class Service(models.Model): def save(self, *args, **kwargs): super(Service, self).save(*args, **kwargs) + def get_instance(serviceid, *args, **kwargs): + """Récupère une instance + :param serviceid: Instance id à trouver + :return: Une instance service évidemment""" + return Service.objects.get(pk=serviceid) + + def can_create(user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour créer + un service + :param user_request: instance utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.add_service'), u"Vous n'avez pas le droit\ + de créer un service" + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour editer + cette instance service + :param self: Instance service à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_service'): + return False, u"Vous n'avez pas le droit d'éditer des services" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour delete + cette instance service + :param self: Instance service à delete + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.delete_service'), u"Vous n'avez pas le droit\ + de supprimer un service" + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des services, + droit particulier view objet correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_service'), u"Vous n'avez pas le droit\ + de voir des services" + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit view objet + :param self: instance service à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_service'), u"Vous n'avez pas le droit\ + de voir des services" + def __str__(self): return str(self.service_type) @@ -777,6 +1736,57 @@ class Service_link(models.Model): ) < timezone.now() ) + def get_instance(servicelinkid, *args, **kwargs): + """Récupère une instance + :param servicelinkid: Instance id à trouver + :return: Une instance servicelink évidemment""" + return ServiceLink.objects.get(pk=servicelinkid) + + def can_create(user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour créer + un servicelink + :param user_request: instance utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.add_service'), u"Vous n'avez pas le droit\ + de créer un service" + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour editer + cette instance servicelink + :param self: Instance servicelink à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_service'): + return False, u"Vous n'avez pas le droit d'éditer des services" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits infra pour delete + cette instance servicelink + :param self: Instance servicelink à delete + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.delete_service'): + return False, u"Vous n'avez pas le droit d'éditer des services" + return True, None + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des services, + droit particulier view objet correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_service'), u"Vous n'avez pas le droit\ + de voir des liens de services" + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit view objet + :param self: instance service à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_service'), u"Vous n'avez pas le droit\ + de voir des liens de services" + def __str__(self): return str(self.server) + " " + str(self.service) @@ -790,6 +1800,65 @@ class OuverturePortList(models.Model): max_length=255 ) + class Meta: + permissions = ( + ("view_ouvertureportlist", "Peut voir un objet ouvertureport"), + ) + + def get_instance(ouvertureportlistid, *args, **kwargs): + """Récupère une instance + :param ouvertureportlistid: Instance id à trouver + :return: Une instance ouvertureportlist évidemment""" + return OuverturePortList.objects.get(pk=ouvertureportlistid) + + def can_create(user_request, *args, **kwargs): + """Verifie que l'user a les bons droits bureau pour créer + une ouverture de port + :param user_request: instance utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.add_ouvertureportlist') , u"Vous n'avez pas le droit\ + d'ouvrir un port" + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits bureau pour editer + cette instance ouvertureportlist + :param self: Instance ouvertureportlist à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_ouvertureportlist'): + return False, u"Vous n'avez pas le droit d'éditer des ouvertures de port" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits bureau pour delete + cette instance ouvertureportlist + :param self: Instance ouvertureportlist à delete + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.delete_ouvertureportlist'): + return False, u"Vous n'avez pas le droit de supprimer une ouverture\ + de port" + if self.interface_set.all(): + return False, u"Cette liste de ports est utilisée" + return True, None + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des ouvertureport, + droit particulier view objet correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_ouvertureportlist'), u"Vous n'avez pas le droit\ + de voir des ouverture de ports" + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit view objet + :param self: instance ouvertureport à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + return user_request.has_perm('machines.view_ouvertureportlist'), u"Vous n'avez pas le droit\ + de voir des ouverture de ports" + def __str__(self): return self.name @@ -860,6 +1929,60 @@ class OuverturePort(models.Model): default=OUT, ) + def get_instance(ouvertureportid, *args, **kwargs): + """Récupère une instance + :param ouvertureportid: Instance id à trouver + :return: Une instance ouvertureport évidemment""" + return OuverturePort.objects.get(pk=ouvertureportid) + + def can_create(user_request, *args, **kwargs): + """Verifie que l'user a les bons droits bureau pour créer + une ouverture de port + :param user_request: instance utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + return user_request.has_perm('machines.add_ouvertureportlist') , u"Vous n'avez pas le droit\ + d'ouvrir un port" + + def can_edit(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits bureau pour editer + cette instance ouvertureport + :param self: Instance ouvertureport à editer + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.change_ouvertureportlist'): + return False, u"Vous n'avez pas le droit d'éditer des ouvertures de port" + return True, None + + def can_delete(self, user_request, *args, **kwargs): + """Verifie que l'user a les bons droits bureau pour delete + cette instance ouvertureport + :param self: Instance ouvertureport à delete + :param user_request: Utilisateur qui fait la requête + :return: soit True, soit False avec la raison de l'échec""" + if not user_request.has_perm('machines.delete_ouvertureportlist'): + return False, u"Vous n'avez pas le droit d'éditer des ouvertures de port" + return True, None + + def can_view_all(user_request, *args, **kwargs): + """Vérifie qu'on peut bien afficher l'ensemble des ouvertureport, + droit particulier view objet correspondant + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + if not user_request.has_perm('machines.view_ouvertureportlist'): + return False, u"Vous n'avez pas le droit d'éditer des ouvertures de port" + return True, None + + def can_view(self, user_request, *args, **kwargs): + """Vérifie qu'on peut bien voir cette instance particulière avec + droit view objet + :param self: instance ouvertureport à voir + :param user_request: instance user qui fait l'edition + :return: True ou False avec la raison de l'échec le cas échéant""" + if not user_request.has_perm('machines.view_ouvertureportlist'): + return False, u"Vous n'avez pas le droit d'éditer des ouvertures de port" + return True, None + + def __str__(self): if self.begin == self.end: return str(self.begin) diff --git a/machines/templates/machines/aff_alias.html b/machines/templates/machines/aff_alias.html index bd64c737..fb3f0486 100644 --- a/machines/templates/machines/aff_alias.html +++ b/machines/templates/machines/aff_alias.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} +
Annuler
Annuler
Annuler
Annuler
Annuler
@@ -33,7 +35,9 @@ with this program; if not, write to the Free Software Foundation, Inc., diff --git a/machines/templates/machines/aff_extension.html b/machines/templates/machines/aff_extension.html index 15a4c637..0da5a08e 100644 --- a/machines/templates/machines/aff_extension.html +++ b/machines/templates/machines/aff_extension.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} +
{{ alias }} + {% can_edit alias %} {% include 'buttons/edit.html' with href='machines:edit-alias' id=alias.id %} + {% acl_end %} {% include 'buttons/history.html' with href='machines:history' name='alias' id=alias.id %}
@@ -45,9 +47,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %} diff --git a/machines/templates/machines/aff_iptype.html b/machines/templates/machines/aff_iptype.html index 454b169d..c5ed4c10 100644 --- a/machines/templates/machines/aff_iptype.html +++ b/machines/templates/machines/aff_iptype.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} +
{{ extension.origin_v6 }} - {% if is_infra %} + {% can_create Extension %} {% include 'buttons/edit.html' with href='machines:edit-extension' id=extension.id %} - {% endif %} + {% acl_end %} {% include 'buttons/history.html' with href='machines:history' name='extension' id=extension.id %}
@@ -48,9 +50,9 @@ with this program; if not, write to the Free Software Foundation, Inc., diff --git a/machines/templates/machines/aff_machines.html b/machines/templates/machines/aff_machines.html index 63d35241..124aaf4d 100644 --- a/machines/templates/machines/aff_machines.html +++ b/machines/templates/machines/aff_machines.html @@ -22,11 +22,13 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} + {% if machines_list.paginator %} {% include "pagination.html" with list=machines_list %} {% endif %} -
{{ type.vlan }} {{ type.ouverture_ports }} - {% if is_infra %} + {% can_edit type %} {% include 'buttons/edit.html' with href='machines:edit-iptype' id=type.id %} - {% endif %} + {% acl_end %} {% include 'buttons/history.html' with href='machines:history' name='iptype' id=type.id %}
+
@@ -50,30 +52,23 @@ with this program; if not, write to the Free Software Foundation, Inc., {% for interface in machine.interface_set.all %} + {% if interface.domain.related_domain.all %} + + + + {% endif %} {% endfor %} @@ -126,6 +147,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
+ {% can_create Interface machine.id %} {% include 'buttons/add.html' with href='machines:new-interface' id=machine.id desc='Ajouter une interface' %} + {% acl_end %} {% include 'buttons/history.html' with href='machines:history' name='machine' id=machine.id %} + {% can_delete machine %} {% include 'buttons/suppr.html' with href='machines:del-machine' id=machine.id %} + {% acl_end %}
{% if interface.domain.related_domain.all %} - + {{ interface.domain }} + {% else %} {{ interface.domain }} {% endif %} @@ -97,27 +92,53 @@ with this program; if not, write to the Free Software Foundation, Inc., {% include 'buttons/history.html' with href='machines:history' name='interface' id=interface.id %} + {% can_delete interface %} {% include 'buttons/suppr.html' with href='machines:del-interface' id=interface.id %} -
+
+
    + {% for al in interface.domain.related_domain.all %} +
  • + + {{ al }} + + +
  • + {% endfor %} +
+
+
+ + {% if machines_list.paginator %} {% include "pagination.html" with list=machines_list %} {% endif %} diff --git a/machines/templates/machines/aff_machinetype.html b/machines/templates/machines/aff_machinetype.html index 7fdcbdb4..facad203 100644 --- a/machines/templates/machines/aff_machinetype.html +++ b/machines/templates/machines/aff_machinetype.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} + @@ -35,9 +37,9 @@ with this program; if not, write to the Free Software Foundation, Inc., diff --git a/machines/templates/machines/aff_mx.html b/machines/templates/machines/aff_mx.html index 176ab061..4478cdab 100644 --- a/machines/templates/machines/aff_mx.html +++ b/machines/templates/machines/aff_mx.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} +
{{ type.type }} {{ type.ip_type }} - {% if is_infra %} + {% can_edit type %} {% include 'buttons/edit.html' with href='machines:edit-machinetype' id=type.id %} - {% endif %} + {% acl_end %} {% include 'buttons/history.html' with href='machines:history' name='machinetype' id=type.id %}
@@ -38,9 +40,9 @@ with this program; if not, write to the Free Software Foundation, Inc., diff --git a/machines/templates/machines/aff_nas.html b/machines/templates/machines/aff_nas.html index dc8fd079..735a4ca8 100644 --- a/machines/templates/machines/aff_nas.html +++ b/machines/templates/machines/aff_nas.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} +
{{ mx.priority }} {{ mx.name }} - {% if is_infra %} + {% can_edit mx %} {% include 'buttons/edit.html' with href='machines:edit-mx' id=mx.id %} - {% endif %} + {% acl_end %} {% include 'buttons/history.html' with href='machines:history' name='mx' id=mx.id %}
@@ -41,9 +43,9 @@ with this program; if not, write to the Free Software Foundation, Inc., diff --git a/machines/templates/machines/aff_ns.html b/machines/templates/machines/aff_ns.html index 67a0dd81..5ee87304 100644 --- a/machines/templates/machines/aff_ns.html +++ b/machines/templates/machines/aff_ns.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} +
{{ nas.port_access_mode }} {{ nas.autocapture_mac }} - {% if is_infra %} + {% can_edit nas %} {% include 'buttons/edit.html' with href='machines:edit-nas' id=nas.id %} - {% endif %} + {% acl_end %} {% include 'buttons/history.html' with href='machines:history' name='nas' id=nas.id %}
@@ -36,9 +38,9 @@ with this program; if not, write to the Free Software Foundation, Inc., diff --git a/machines/templates/machines/aff_service.html b/machines/templates/machines/aff_service.html index 47bfee25..da80b4da 100644 --- a/machines/templates/machines/aff_service.html +++ b/machines/templates/machines/aff_service.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} +
{{ ns.zone }} {{ ns.ns }} - {% if is_infra %} + {% can_edit ns %} {% include 'buttons/edit.html' with href='machines:edit-ns' id=ns.id %} - {% endif %} + {% acl_end %} {% include 'buttons/history.html' with href='machines:history' name='ns' id=ns.id %}
@@ -40,9 +42,9 @@ with this program; if not, write to the Free Software Foundation, Inc., diff --git a/machines/templates/machines/aff_soa.html b/machines/templates/machines/aff_soa.html index 3dad11c7..5352a739 100644 --- a/machines/templates/machines/aff_soa.html +++ b/machines/templates/machines/aff_soa.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} +
{{ service.regular_time_regen }} {% for serv in service.servers.all %}{{ serv }}, {% endfor %} - {% if is_infra %} + {% can_edit service %} {% include 'buttons/edit.html' with href='machines:edit-service' id=service.id %} - {% endif %} + {% acl_end %} {% include 'buttons/history.html' with href='machines:history' name='service' id=service.id %}
@@ -44,9 +46,9 @@ with this program; if not, write to the Free Software Foundation, Inc., diff --git a/machines/templates/machines/aff_srv.html b/machines/templates/machines/aff_srv.html index 773815d9..e7886cf1 100644 --- a/machines/templates/machines/aff_srv.html +++ b/machines/templates/machines/aff_srv.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} +
{{ soa.expire }} {{ soa.ttl }} - {% if is_infra %} + {% can_edit soa %} {% include 'buttons/edit.html' with href='machines:edit-soa' id=soa.id %} - {% endif %} + {% acl_end %} {% include 'buttons/history.html' with href='machines:history' name='soa' id=soa.id %}
@@ -48,9 +50,9 @@ with this program; if not, write to the Free Software Foundation, Inc., diff --git a/machines/templates/machines/aff_txt.html b/machines/templates/machines/aff_txt.html index fd7c5ee6..973fd6d9 100644 --- a/machines/templates/machines/aff_txt.html +++ b/machines/templates/machines/aff_txt.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} +
{{ srv.port }} {{ srv.target }} - {% if is_infra %} + {% can_edit srv %} {% include 'buttons/edit.html' with href='machines:edit-srv' id=srv.id %} - {% endif %} + {% acl_end %} {% include 'buttons/history.html' with href='machines:history' name='srv' id=srv.id %}
@@ -36,9 +38,9 @@ with this program; if not, write to the Free Software Foundation, Inc., diff --git a/machines/templates/machines/aff_vlan.html b/machines/templates/machines/aff_vlan.html index eaa1c82c..deb8cb11 100644 --- a/machines/templates/machines/aff_vlan.html +++ b/machines/templates/machines/aff_vlan.html @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} +
{{ txt.zone }} {{ txt.dns_entry }} - {% if is_infra %} + {% can_edit txt %} {% include 'buttons/edit.html' with href='machines:edit-txt' id=txt.id %} - {% endif %} + {% acl_end %} {% include 'buttons/history.html' with href='machines:history' name='txt' id=txt.id %}
@@ -39,9 +41,9 @@ with this program; if not, write to the Free Software Foundation, Inc., diff --git a/machines/templates/machines/index_extension.html b/machines/templates/machines/index_extension.html index 3169b4c9..a0159974 100644 --- a/machines/templates/machines/index_extension.html +++ b/machines/templates/machines/index_extension.html @@ -25,45 +25,47 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} +{% load acl %} + {% block title %}Machines{% endblock %} {% block content %}

Liste des extensions

- {% if is_infra %} + {% can_create Extension %} Ajouter une extension + {% acl_end %} Supprimer une ou plusieurs extensions - {% endif %} {% include "machines/aff_extension.html" with extension_list=extension_list %}

Liste des enregistrements SOA

- {% if is_infra %} + {% can_create SOA %} Ajouter un enregistrement SOA + {% acl_end %} Supprimer un enregistrement SOA - {% endif %} {% include "machines/aff_soa.html" with soa_list=soa_list %}

Liste des enregistrements MX

- {% if is_infra %} + {% can_create Mx %} Ajouter un enregistrement MX + {% acl_end %} Supprimer un enregistrement MX - {% endif %} {% include "machines/aff_mx.html" with mx_list=mx_list %}

Liste des enregistrements NS

- {% if is_infra %} + {% can_create Ns %} Ajouter un enregistrement NS + {% acl_end %} Supprimer un enregistrement NS - {% endif %} {% include "machines/aff_ns.html" with ns_list=ns_list %}

Liste des enregistrements TXT

- {% if is_infra %} + {% can_create Txt %} Ajouter un enregistrement TXT + {% acl_end %} Supprimer un enregistrement TXT - {% endif %} {% include "machines/aff_txt.html" with txt_list=txt_list %}

Liste des enregistrements SRV

- {% if is_infra %} + {% can_create Srv %} Ajouter un enregistrement SRV + {% acl_end %} Supprimer un enregistrement SRV - {% endif %} {% include "machines/aff_srv.html" with srv_list=srv_list %}

diff --git a/machines/templates/machines/index_iptype.html b/machines/templates/machines/index_iptype.html index 8f85130b..cd582183 100644 --- a/machines/templates/machines/index_iptype.html +++ b/machines/templates/machines/index_iptype.html @@ -25,14 +25,16 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} +{% load acl %} + {% block title %}Ip{% endblock %} {% block content %}

Liste des types d'ip

- {% if is_infra %} + {% can_create IpType %} Ajouter un type d'ip + {% acl_end %} Supprimer un ou plusieurs types d'ip - {% endif %} {% include "machines/aff_iptype.html" with iptype_list=iptype_list %}

diff --git a/machines/templates/machines/index_machinetype.html b/machines/templates/machines/index_machinetype.html index 1e99fd2d..96fa2d7e 100644 --- a/machines/templates/machines/index_machinetype.html +++ b/machines/templates/machines/index_machinetype.html @@ -25,14 +25,16 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} +{% load acl %} + {% block title %}Machines{% endblock %} {% block content %}

Liste des types de machines

- {% if is_infra %} + {% can_create MachineType %} Ajouter un type de machine + {% acl_end %} Supprimer un ou plusieurs types de machines - {% endif %} {% include "machines/aff_machinetype.html" with machinetype_list=machinetype_list %}

diff --git a/machines/templates/machines/index_nas.html b/machines/templates/machines/index_nas.html index ac1fe8b4..b4f99ac4 100644 --- a/machines/templates/machines/index_nas.html +++ b/machines/templates/machines/index_nas.html @@ -25,16 +25,18 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} +{% load acl %} + {% block title %}Machines{% endblock %} {% block content %}

Liste des nas

La correpondance nas-machinetype relie le type de nas à un type de machine. Elle est utile pour l'autoenregistrement des macs par radius, et permet de choisir le type de machine à affecter aux machines en fonction du type de nas
- {% if is_infra %} + {% can_create Nas %} Ajouter un type de nas + {% acl_end %} Supprimer un ou plusieurs types nas - {% endif %} {% include "machines/aff_nas.html" with nas_list=nas_list %}

diff --git a/machines/templates/machines/index_portlist.html b/machines/templates/machines/index_portlist.html index 04ac66d4..5c0c148a 100644 --- a/machines/templates/machines/index_portlist.html +++ b/machines/templates/machines/index_portlist.html @@ -2,11 +2,15 @@ {% load bootstrap3 %} +{% load acl %} + {% block title %}Configuration de ports{% endblock %} {% block content %}

Liste des configurations de ports

+ {% can_create OuverturePortList %} Ajouter une configuration + {% acl_end %}
{{ vlan.comment }} {% for range in vlan.iptype_set.all %}{{ range }}, {% endfor%} - {% if is_infra %} + {% can_create Vlan %} {% include 'buttons/edit.html' with href='machines:edit-vlan' id=vlan.id %} - {% endif %} + {% acl_end %} {% include 'buttons/history.html' with href='machines:history' name='vlan' id=vlan.id %}
@@ -44,8 +48,12 @@ {% endif %} {%endfor%} diff --git a/machines/templates/machines/index_service.html b/machines/templates/machines/index_service.html index b07f994c..4a06761d 100644 --- a/machines/templates/machines/index_service.html +++ b/machines/templates/machines/index_service.html @@ -24,15 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load acl %} {% block title %}Machines{% endblock %} {% block content %}

Liste des services

- {% if is_infra %} + {% can_create Service %} Ajouter un service + {% acl_end %} Supprimer un ou plusieurs service - {% endif %} {% include "machines/aff_service.html" with service_list=service_list %}

Etat des serveurs

{% include "machines/aff_servers.html" with servers_list=servers_list %} diff --git a/machines/templates/machines/index_vlan.html b/machines/templates/machines/index_vlan.html index ec00b0bf..d9a4f8f9 100644 --- a/machines/templates/machines/index_vlan.html +++ b/machines/templates/machines/index_vlan.html @@ -25,14 +25,16 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} +{% load acl %} + {% block title %}Machines{% endblock %} {% block content %}

Liste des vlans

- {% if is_infra %} + {% can_create Vlan %} Ajouter un vlan + {% acl_end %} Supprimer un ou plusieurs vlan - {% endif %} {% include "machines/aff_vlan.html" with vlan_list=vlan_list %}

diff --git a/machines/templates/machines/sidebar.html b/machines/templates/machines/sidebar.html index 6ca3a07f..355d5147 100644 --- a/machines/templates/machines/sidebar.html +++ b/machines/templates/machines/sidebar.html @@ -23,42 +23,55 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. {% endcomment %} +{% load acl %} {% block sidebar %} - {% if is_cableur %} + {% can_view_all Machine %} Machines + {% acl_end %} + {% can_view_all MachineType %} Types de machines + {% acl_end %} + {% can_view_all Extension %} Extensions et zones + {% acl_end %} + {% can_view_all IpType %} Plages d'IP + {% acl_end %} + {% can_view_all Vlan %} Vlans + {% acl_end %} + {% can_view_all Nas %} Gestion des nas + {% acl_end %} + {% can_view_all Service %} Services (dhcp, dns...) - - {% endif %} - {% if is_cableur %} + + {% acl_end %} + {% can_view_all OuverturePortList %} Ouverture de ports - {%endif%} + {% acl_end %} {% endblock %} diff --git a/machines/urls.py b/machines/urls.py index c024cf56..41f443e9 100644 --- a/machines/urls.py +++ b/machines/urls.py @@ -24,7 +24,7 @@ from __future__ import unicode_literals from django.conf.urls import url - +import re2o from . import views urlpatterns = [ @@ -61,7 +61,7 @@ urlpatterns = [ url(r'^del_srv/$', views.del_srv, name='del-srv'), url(r'^index_extension/$', views.index_extension, name='index-extension'), url(r'^add_alias/(?P[0-9]+)$', views.add_alias, name='add-alias'), - url(r'^edit_alias/(?P[0-9]+)$', views.edit_alias, name='edit-alias'), + url(r'^edit_alias/(?P[0-9]+)$', views.edit_alias, name='edit-alias'), url(r'^del_alias/(?P[0-9]+)$', views.del_alias, name='del-alias'), url(r'^index_alias/(?P[0-9]+)$', views.index_alias, name='index-alias'), url(r'^add_service/$', views.add_service, name='add-service'), @@ -76,20 +76,12 @@ urlpatterns = [ url(r'^edit_nas/(?P[0-9]+)$', views.edit_nas, name='edit-nas'), url(r'^del_nas/$', views.del_nas, name='del-nas'), url(r'^index_nas/$', views.index_nas, name='index-nas'), - url(r'^history/(?Pmachine)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Pinterface)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Pmachinetype)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Pextension)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Psoa)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Pmx)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Pns)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Ptxt)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Psrv)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Piptype)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Palias)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Pvlan)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Pnas)/(?P[0-9]+)$', views.history, name='history'), - url(r'^history/(?Pservice)/(?P[0-9]+)$', views.history, name='history'), + url( + r'history/(?P\w+)/(?P[0-9]+)$', + re2o.views.history, + name='history', + kwargs={'application':'machines'}, + ), url(r'^$', views.index, name='index'), url(r'^rest/mac-ip/$', views.mac_ip, name='mac-ip'), url(r'^rest/regen-achieved/$', views.regen_achieved, name='regen-achieved'), @@ -104,9 +96,9 @@ urlpatterns = [ url(r'^rest/service_servers/$', views.service_servers, name='service-servers'), url(r'^rest/ouverture_ports/$', views.ouverture_ports, name='ouverture-ports'), url(r'index_portlist/$', views.index_portlist, name='index-portlist'), - url(r'^edit_portlist/(?P[0-9]+)$', views.edit_portlist, name='edit-portlist'), - url(r'^del_portlist/(?P[0-9]+)$', views.del_portlist, name='del-portlist'), + url(r'^edit_portlist/(?P[0-9]+)$', views.edit_portlist, name='edit-portlist'), + url(r'^del_portlist/(?P[0-9]+)$', views.del_portlist, name='del-portlist'), url(r'^add_portlist/$', views.add_portlist, name='add-portlist'), - url(r'^port_config/(?P[0-9]+)$', views.configure_ports, name='port-config'), + url(r'^port_config/(?P[0-9]+)$', views.configure_ports, name='port-config'), ] diff --git a/machines/views.py b/machines/views.py index a59e493c..0975e58c 100644 --- a/machines/views.py +++ b/machines/views.py @@ -123,7 +123,15 @@ from re2o.utils import ( all_active_assigned_interfaces, all_has_access, filter_active_interfaces, - SortTable + SortTable, +) +from re2o.acl import ( + can_create, + can_edit, + can_delete, + can_view, + can_view_all, + can_delete_set, ) from re2o.views import form @@ -210,34 +218,18 @@ def generate_ipv4_mbf_param( form, is_type_tt ): return i_mbf_param @login_required -def new_machine(request, userid): - """ Fonction de creation d'une machine. Cree l'objet machine, +@can_create(Machine) +@can_edit(User) +def new_machine(request, user, userid): + """ Fonction de creation d'une machine. Cree l'objet machine, le sous objet interface et l'objet domain à partir de model forms. Trop complexe, devrait être simplifié""" - try: - user = User.objects.get(pk=userid) - except User.DoesNotExist: - messages.error(request, u"Utilisateur inexistant" ) - return redirect(reverse('machines:index')) - options, created = OptionalMachine.objects.get_or_create() - max_lambdauser_interfaces = options.max_lambdauser_interfaces - if not request.user.has_perms(('cableur',)): - if user != request.user: - messages.error( - request, - "Vous ne pouvez pas ajouter une machine à un autre user que vous sans droit") - return redirect(reverse( - 'users:profil', - kwargs={'userid':str(request.user.id)} - )) - if user.user_interfaces().count() >= max_lambdauser_interfaces: - messages.error(request, "Vous avez atteint le maximum d'interfaces autorisées que vous pouvez créer vous même (%s) " % max_lambdauser_interfaces) - return redirect(reverse( - 'users:profil', - kwargs={'userid':str(request.user.id)} - )) - machine = NewMachineForm(request.POST or None) - interface = AddInterfaceForm(request.POST or None, infra=request.user.has_perms(('infra',))) + + machine = NewMachineForm(request.POST or None, user=user) + interface = AddInterfaceForm( + request.POST or None, + user=request.user + ) domain = DomainForm(request.POST or None, user=user) if machine.is_valid() and interface.is_valid(): new_machine = machine.save(commit=False) @@ -264,32 +256,32 @@ def new_machine(request, userid): return redirect(reverse( 'users:profil', kwargs={'userid':str(user.id)} - )) - i_mbf_param = generate_ipv4_mbf_param( interface, False ) - return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain, 'i_mbf_param': i_mbf_param}, 'machines/machine.html', request) + )) + i_mbf_param = generate_ipv4_mbf_param(interface, False) + return form( + { + 'machineform': machine, + 'interfaceform': interface, + 'domainform': domain, + 'i_mbf_param': i_mbf_param + }, + 'machines/machine.html', + request + ) @login_required -def edit_interface(request, interfaceid): +@can_edit(Interface) +def edit_interface(request, interface_instance, interfaceid): """ Edition d'une interface. Distingue suivant les droits les valeurs de interfaces et machines que l'user peut modifier infra permet de modifier le propriétaire""" - try: - interface = Interface.objects.get(pk=interfaceid) - except Interface.DoesNotExist: - messages.error(request, u"Interface inexistante" ) - return redirect(reverse('machines:index')) - if not request.user.has_perms(('infra',)): - if not request.user.has_perms(('cableur',)) and interface.machine.user != request.user: - messages.error(request, "Vous ne pouvez pas éditer une machine d'un autre user que vous sans droit") - return redirect(reverse( - 'users:profil', - kwargs={'userid':str(request.user.id)} - )) - machine_form = BaseEditMachineForm(request.POST or None, instance=interface.machine) - interface_form = BaseEditInterfaceForm(request.POST or None, instance=interface, infra=False) - else: - machine_form = EditMachineForm(request.POST or None, instance=interface.machine) - interface_form = EditInterfaceForm(request.POST or None, instance=interface) - domain_form = DomainForm(request.POST or None, instance=interface.domain) + + machine_form = EditMachineForm( + request.POST or None, + instance=interface_instance.machine, + user=request.user + ) + interface_form = BaseEditInterfaceForm(request.POST or None, instance=interface_instance, user=request.user) + domain_form = DomainForm(request.POST or None, instance=interface_instance.domain) if machine_form.is_valid() and interface_form.is_valid() and domain_form.is_valid(): new_machine = machine_form.save(commit=False) new_interface = interface_form.save(commit=False) @@ -309,26 +301,15 @@ def edit_interface(request, interfaceid): messages.success(request, "La machine a été modifiée") return redirect(reverse( 'users:profil', - kwargs={'userid':str(interface.machine.user.id)} + kwargs={'userid':str(interface_instance.machine.user.id)} )) i_mbf_param = generate_ipv4_mbf_param( interface_form, False ) return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form, 'i_mbf_param': i_mbf_param}, 'machines/machine.html', request) @login_required -def del_machine(request, machineid): +@can_delete(Machine) +def del_machine(request, machine, machineid): """ Supprime une machine, interfaces en mode cascade""" - try: - machine = Machine.objects.get(pk=machineid) - except Machine.DoesNotExist: - messages.error(request, u"Machine inexistante" ) - return redirect(reverse('machines:index')) - if not request.user.has_perms(('cableur',)): - if machine.user != request.user: - messages.error(request, "Vous ne pouvez pas éditer une machine d'un autre user que vous sans droit") - return redirect(reverse( - 'users:profil', - kwargs={'userid':str(machine.user.id)} - )) if request.method == "POST": with transaction.atomic(), reversion.create_revision(): machine.delete() @@ -341,29 +322,12 @@ def del_machine(request, machineid): return form({'objet': machine, 'objet_name': 'machine'}, 'machines/delete.html', request) @login_required -def new_interface(request, machineid): +@can_create(Interface) +@can_edit(Machine) +def new_interface(request, machine, machineid): """ Ajoute une interface et son domain associé à une machine existante""" - try: - machine = Machine.objects.get(pk=machineid) - except Machine.DoesNotExist: - messages.error(request, u"Machine inexistante" ) - return redirect(reverse('machines:index')) - if not request.user.has_perms(('cableur',)): - options, created = OptionalMachine.objects.get_or_create() - max_lambdauser_interfaces = options.max_lambdauser_interfaces - if machine.user != request.user: - messages.error(request, "Vous ne pouvez pas ajouter une interface à une machine d'un autre user que vous sans droit") - return redirect(reverse( - 'users:profil', - kwargs={'userid':str(request.user.id)} - )) - if machine.user.user_interfaces().count() >= max_lambdauser_interfaces: - messages.error(request, "Vous avez atteint le maximum d'interfaces autorisées que vous pouvez créer vous même (%s) " % max_lambdauser_interfaces) - return redirect(reverse( - 'users:profil', - kwargs={'userid':str(request.user.id)} - )) - interface_form = AddInterfaceForm(request.POST or None, infra=request.user.has_perms(('infra',))) + + interface_form = AddInterfaceForm(request.POST or None, user=user) domain_form = DomainForm(request.POST or None) if interface_form.is_valid(): new_interface = interface_form.save(commit=False) @@ -389,20 +353,9 @@ def new_interface(request, machineid): return form({'interfaceform': interface_form, 'domainform': domain_form, 'i_mbf_param': i_mbf_param}, 'machines/machine.html', request) @login_required -def del_interface(request, interfaceid): +@can_delete(Interface) +def del_interface(request, interface, interfaceid): """ Supprime une interface. Domain objet en mode cascade""" - try: - interface = Interface.objects.get(pk=interfaceid) - except Interface.DoesNotExist: - messages.error(request, u"Interface inexistante" ) - return redirect(reverse('machines:index')) - if not request.user.has_perms(('cableur',)): - if interface.machine.user != request.user: - messages.error(request, "Vous ne pouvez pas éditer une machine d'un autre user que vous sans droit") - return redirect(reverse( - 'users:profil', - kwargs={'userid':str(request.user.id)} - )) if request.method == "POST": machine = interface.machine with transaction.atomic(), reversion.create_revision(): @@ -418,9 +371,10 @@ def del_interface(request, interfaceid): return form({'objet': interface, 'objet_name': 'interface'}, 'machines/delete.html', request) @login_required -@permission_required('infra') +@can_create(IpType) def add_iptype(request): """ Ajoute un range d'ip. Intelligence dans le models, fonction views minimaliste""" + iptype = IpTypeForm(request.POST or None) if iptype.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -432,14 +386,10 @@ def add_iptype(request): return form({'iptypeform': iptype}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def edit_iptype(request, iptypeid): +@can_edit(IpType) +def edit_iptype(request, iptype_instance, iptypeid): """ Edition d'un range. Ne permet pas de le redimensionner pour éviter l'incohérence""" - try: - iptype_instance = IpType.objects.get(pk=iptypeid) - except IpType.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) - return redirect(reverse('machines:index-iptype')) + iptype = EditIpTypeForm(request.POST or None, instance=iptype_instance) if iptype.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -451,10 +401,10 @@ def edit_iptype(request, iptypeid): return form({'iptypeform': iptype}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def del_iptype(request): +@can_delete_set(IpType) +def del_iptype(request, instances): """ Suppression d'un range ip. Supprime les objets ip associés""" - iptype = DelIpTypeForm(request.POST or None) + iptype = DelIpTypeForm(request.POST or None, instances=instances) if iptype.is_valid(): iptype_dels = iptype.cleaned_data['iptypes'] for iptype_del in iptype_dels: @@ -469,8 +419,9 @@ def del_iptype(request): return form({'iptypeform': iptype}, 'machines/machine.html', request) @login_required -@permission_required('infra') +@can_create(MachineType) def add_machinetype(request): + machinetype = MachineTypeForm(request.POST or None) if machinetype.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -482,13 +433,9 @@ def add_machinetype(request): return form({'machinetypeform': machinetype}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def edit_machinetype(request, machinetypeid): - try: - machinetype_instance = MachineType.objects.get(pk=machinetypeid) - except MachineType.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) - return redirect(reverse('machines:index-machinetype')) +@can_edit(MachineType) +def edit_machinetype(request, machinetype_instance, machinetypeid): + machinetype = MachineTypeForm(request.POST or None, instance=machinetype_instance) if machinetype.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -500,9 +447,9 @@ def edit_machinetype(request, machinetypeid): return form({'machinetypeform': machinetype}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def del_machinetype(request): - machinetype = DelMachineTypeForm(request.POST or None) +@can_delete_set(MachineType) +def del_machinetype(request, instances): + machinetype = DelMachineTypeForm(request.POST or None, instances=instances) if machinetype.is_valid(): machinetype_dels = machinetype.cleaned_data['machinetypes'] for machinetype_del in machinetype_dels: @@ -517,8 +464,9 @@ def del_machinetype(request): return form({'machinetypeform': machinetype}, 'machines/machine.html', request) @login_required -@permission_required('infra') +@can_create(Extension) def add_extension(request): + extension = ExtensionForm(request.POST or None) if extension.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -530,27 +478,23 @@ def add_extension(request): return form({'extensionform': extension}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def edit_extension(request, extensionid): - try: - extension_instance = Extension.objects.get(pk=extensionid) - except Extension.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) - return redirect(reverse('machines:index-extension')) +@can_edit(Extension) +def edit_extension(request, extension_instance, extensionid): + extension = ExtensionForm(request.POST or None, instance=extension_instance) if extension.is_valid(): with transaction.atomic(), reversion.create_revision(): extension.save() reversion.set_user(request.user) reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in extension.changed_data)) - messages.success(request, "Extension modifiée") + mssages.success(request, "Extension modifiée") return redirect(reverse('machines:index-extension')) return form({'extensionform': extension}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def del_extension(request): - extension = DelExtensionForm(request.POST or None) +@can_delete_set(Extension) +def del_extension(request, instances): + extension = DelExtensionForm(request.POST or None, instances=instances) if extension.is_valid(): extension_dels = extension.cleaned_data['extensions'] for extension_del in extension_dels: @@ -565,8 +509,9 @@ def del_extension(request): return form({'extensionform': extension}, 'machines/machine.html', request) @login_required -@permission_required('infra') +@can_create(SOA) def add_soa(request): + soa = SOAForm(request.POST or None) if soa.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -578,13 +523,9 @@ def add_soa(request): return form({'soaform': soa}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def edit_soa(request, soaid): - try: - soa_instance = SOA.objects.get(pk=soaid) - except SOA.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) - return redirect(reverse('machines:index-extension')) +@can_edit(SOA) +def edit_soa(request, soa_instance, soaid): + soa = SOAForm(request.POST or None, instance=soa_instance) if soa.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -596,9 +537,9 @@ def edit_soa(request, soaid): return form({'soaform': soa}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def del_soa(request): - soa = DelSOAForm(request.POST or None) +@can_delete_set(SOA) +def del_soa(request, instances): + soa = DelSOAForm(request.POST or None, instances=instances) if soa.is_valid(): soa_dels = soa.cleaned_data['soa'] for soa_del in soa_dels: @@ -613,8 +554,9 @@ def del_soa(request): return form({'soaform': soa}, 'machines/machine.html', request) @login_required -@permission_required('infra') +@can_create(Mx) def add_mx(request): + mx = MxForm(request.POST or None) if mx.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -626,13 +568,9 @@ def add_mx(request): return form({'mxform': mx}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def edit_mx(request, mxid): - try: - mx_instance = Mx.objects.get(pk=mxid) - except Mx.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) - return redirect(reverse('machines:index-extension')) +@can_edit(Mx) +def edit_mx(request, mx_instance, mxid): + mx = MxForm(request.POST or None, instance=mx_instance) if mx.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -644,9 +582,9 @@ def edit_mx(request, mxid): return form({'mxform': mx}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def del_mx(request): - mx = DelMxForm(request.POST or None) +@can_delete_set(Mx) +def del_mx(request, instances): + mx = DelMxForm(request.POST or None, instances=instances) if mx.is_valid(): mx_dels = mx.cleaned_data['mx'] for mx_del in mx_dels: @@ -661,8 +599,9 @@ def del_mx(request): return form({'mxform': mx}, 'machines/machine.html', request) @login_required -@permission_required('infra') +@can_create(Ns) def add_ns(request): + ns = NsForm(request.POST or None) if ns.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -674,13 +613,9 @@ def add_ns(request): return form({'nsform': ns}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def edit_ns(request, nsid): - try: - ns_instance = Ns.objects.get(pk=nsid) - except Ns.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) - return redirect(reverse('machines:index-extension')) +@can_edit(Ns) +def edit_ns(request, ns_instance, nsid): + ns = NsForm(request.POST or None, instance=ns_instance) if ns.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -692,9 +627,9 @@ def edit_ns(request, nsid): return form({'nsform': ns}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def del_ns(request): - ns = DelNsForm(request.POST or None) +@can_delete_set(Ns) +def del_ns(request, instances): + ns = DelNsForm(request.POST or None, instances=instances) if ns.is_valid(): ns_dels = ns.cleaned_data['ns'] for ns_del in ns_dels: @@ -709,8 +644,9 @@ def del_ns(request): return form({'nsform': ns}, 'machines/machine.html', request) @login_required -@permission_required('infra') +@can_create(Txt) def add_txt(request): + txt = TxtForm(request.POST or None) if txt.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -722,13 +658,9 @@ def add_txt(request): return form({'txtform': txt}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def edit_txt(request, txtid): - try: - txt_instance = Txt.objects.get(pk=txtid) - except Txt.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) - return redirect(reverse('machines:index-extension')) +@can_edit(Txt) +def edit_txt(request, txt_instance, txtid): + txt = TxtForm(request.POST or None, instance=txt_instance) if txt.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -740,9 +672,9 @@ def edit_txt(request, txtid): return form({'txtform': txt}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def del_txt(request): - txt = DelTxtForm(request.POST or None) +@can_delete_set(Txt) +def del_txt(request, instances): + txt = DelTxtForm(request.POST or None, instances=instances) if txt.is_valid(): txt_dels = txt.cleaned_data['txt'] for txt_del in txt_dels: @@ -757,8 +689,9 @@ def del_txt(request): return form({'txtform': txt}, 'machines/machine.html', request) @login_required -@permission_required('infra') +@can_create(Srv) def add_srv(request): + srv = SrvForm(request.POST or None) if srv.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -770,13 +703,9 @@ def add_srv(request): return form({'srvform': srv}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def edit_srv(request, srvid): - try: - srv_instance = Srv.objects.get(pk=srvid) - except Srv.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) - return redirect(reverse('machines:index-extension')) +@can_edit(Srv) +def edit_srv(request, srv_instance, srvid): + srv = SrvForm(request.POST or None, instance=srv_instance) if srv.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -788,9 +717,9 @@ def edit_srv(request, srvid): return form({'srvform': srv}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def del_srv(request): - srv = DelSrvForm(request.POST or None) +@can_delete_set(Srv) +def del_srv(request, instances): + srv = DelSrvForm(request.POST or None, instances=instances) if srv.is_valid(): srv_dels = srv.cleaned_data['srv'] for srv_del in srv_dels: @@ -805,28 +734,11 @@ def del_srv(request): return form({'srvform': srv}, 'machines/machine.html', request) @login_required -def add_alias(request, interfaceid): - try: - interface = Interface.objects.get(pk=interfaceid) - except Interface.DoesNotExist: - messages.error(request, u"Interface inexistante" ) - return redirect(reverse('machines:index')) - if not request.user.has_perms(('cableur',)): - options, created = OptionalMachine.objects.get_or_create() - max_lambdauser_aliases = options.max_lambdauser_aliases - if interface.machine.user != request.user: - messages.error(request, "Vous ne pouvez pas ajouter un alias à une machine d'un autre user que vous sans droit") - return redirect(reverse( - 'users:profil', - kwargs={'userid':str(request.user.id)} - )) - if Domain.objects.filter(cname__in=Domain.objects.filter(interface_parent__in=interface.machine.user.user_interfaces())).count() >= max_lambdauser_aliases: - messages.error(request, "Vous avez atteint le maximum d'alias autorisées que vous pouvez créer vous même (%s) " % max_lambdauser_aliases) - return redirect(reverse( - 'users:profil', - kwargs={'userid':str(request.user.id)} - )) - alias = AliasForm(request.POST or None, infra=request.user.has_perms(('infra',))) +@can_create(Domain) +@can_edit(Interface) +def add_alias(request, interface, interfaceid): + + alias = AliasForm(request.POST or None, user=request.user) if alias.is_valid(): alias = alias.save(commit=False) alias.cname = interface.domain @@ -836,50 +748,31 @@ def add_alias(request, interfaceid): reversion.set_comment("Création") messages.success(request, "Cet alias a été ajouté") return redirect(reverse( - 'machines:index-alias', + 'machines:index-alias', kwargs={'interfaceid':str(interfaceid)} )) return form({'aliasform': alias}, 'machines/machine.html', request) @login_required -def edit_alias(request, aliasid): - try: - alias_instance = Domain.objects.get(pk=aliasid) - except Domain.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) - return redirect(reverse('machines:index-extension')) - if not request.user.has_perms(('cableur',)) and alias_instance.cname.interface_parent.machine.user != request.user: - messages.error(request, "Vous ne pouvez pas ajouter un alias à une machine d'un autre user que vous sans droit") - return redirect(reverse( - 'users:profil', - kwargs={'userid':str(request.user.id)} - )) - alias = AliasForm(request.POST or None, instance=alias_instance, infra=request.user.has_perms(('infra',))) +@can_edit(Domain) +def edit_alias(request, domain_instance, domainid): + + alias = AliasForm(request.POST or None, instance=domain_instance, user=request.user) if alias.is_valid(): with transaction.atomic(), reversion.create_revision(): - alias_instance = alias.save() + domain_instance = alias.save() reversion.set_user(request.user) reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in alias.changed_data)) messages.success(request, "Alias modifié") return redirect(reverse( - 'machines:index-alias', - kwargs={'interfaceid':str(alias_instance.cname.interface_parent.id)} + 'machines:index-alias', + kwargs={'interfaceid':str(domain_instance.cname.interface_parent.id)} )) return form({'aliasform': alias}, 'machines/machine.html', request) @login_required -def del_alias(request, interfaceid): - try: - interface = Interface.objects.get(pk=interfaceid) - except Interface.DoesNotExist: - messages.error(request, u"Interface inexistante" ) - return redirect(reverse('machines:index')) - if not request.user.has_perms(('cableur',)) and interface.machine.user != request.user: - messages.error(request, "Vous ne pouvez pas ajouter un alias à une machine d'un autre user que vous sans droit") - return redirect(reverse( - 'users:profil', - kwargs={'userid':str(request.user.id)} - )) +@can_edit(Interface) +def del_alias(request, interface, interfaceid): alias = DelAliasForm(request.POST or None, interface=interface) if alias.is_valid(): alias_dels = alias.cleaned_data['alias'] @@ -892,15 +785,16 @@ def del_alias(request, interfaceid): except ProtectedError: messages.error(request, "Erreur l'alias suivant %s ne peut être supprimé" % alias_del) return redirect(reverse( - 'machines:index-alias', + 'machines:index-alias', kwargs={'interfaceid':str(interfaceid)} )) return form({'aliasform': alias}, 'machines/machine.html', request) @login_required -@permission_required('infra') +@can_create(Service) def add_service(request): + service = ServiceForm(request.POST or None) if service.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -912,13 +806,9 @@ def add_service(request): return form({'serviceform': service}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def edit_service(request, serviceid): - try: - service_instance = Service.objects.get(pk=serviceid) - except Ns.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) - return redirect(reverse('machines:index-extension')) +@can_edit(Service) +def edit_service(request, service_instance, serviceid): + service = ServiceForm(request.POST or None, instance=service_instance) if service.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -930,9 +820,9 @@ def edit_service(request, serviceid): return form({'serviceform': service}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def del_service(request): - service = DelServiceForm(request.POST or None) +@can_delete_set(Service) +def del_service(request, instances): + service = DelServiceForm(request.POST or None, instances=instances) if service.is_valid(): service_dels = service.cleaned_data['service'] for service_del in service_dels: @@ -947,8 +837,9 @@ def del_service(request): return form({'serviceform': service}, 'machines/machine.html', request) @login_required -@permission_required('infra') +@can_create(Vlan) def add_vlan(request): + vlan = VlanForm(request.POST or None) if vlan.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -960,13 +851,9 @@ def add_vlan(request): return form({'vlanform': vlan}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def edit_vlan(request, vlanid): - try: - vlan_instance = Vlan.objects.get(pk=vlanid) - except Vlan.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) - return redirect(reverse('machines:index-vlan')) +@can_edit(Vlan) +def edit_vlan(request, vlan_instance, vlanid): + vlan = VlanForm(request.POST or None, instance=vlan_instance) if vlan.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -978,9 +865,9 @@ def edit_vlan(request, vlanid): return form({'vlanform': vlan}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def del_vlan(request): - vlan = DelVlanForm(request.POST or None) +@can_delete_set(Vlan) +def del_vlan(request, instances): + vlan = DelVlanForm(request.POST or None, instances=instances) if vlan.is_valid(): vlan_dels = vlan.cleaned_data['vlan'] for vlan_del in vlan_dels: @@ -995,8 +882,9 @@ def del_vlan(request): return form({'vlanform': vlan}, 'machines/machine.html', request) @login_required -@permission_required('infra') +@can_create(Nas) def add_nas(request): + nas = NasForm(request.POST or None) if nas.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -1008,13 +896,9 @@ def add_nas(request): return form({'nasform': nas}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def edit_nas(request, nasid): - try: - nas_instance = Nas.objects.get(pk=nasid) - except Nas.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) - return redirect(reverse('machines:index-nas')) +@can_edit(Nas) +def edit_nas(request, nas_instance, nasid): + nas = NasForm(request.POST or None, instance=nas_instance) if nas.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -1026,9 +910,9 @@ def edit_nas(request, nasid): return form({'nasform': nas}, 'machines/machine.html', request) @login_required -@permission_required('infra') -def del_nas(request): - nas = DelNasForm(request.POST or None) +@can_delete_set(Nas) +def del_nas(request, instances): + nas = DelNasForm(request.POST or None, instances=instances) if nas.is_valid(): nas_dels = nas.cleaned_data['nas'] for nas_del in nas_dels: @@ -1043,7 +927,7 @@ def del_nas(request): return form({'nasform': nas}, 'machines/machine.html', request) @login_required -@permission_required('cableur') +@can_view_all(Machine) def index(request): options, created = GeneralOption.objects.get_or_create() pagination_large_number = options.pagination_large_number @@ -1067,31 +951,36 @@ def index(request): return render(request, 'machines/index.html', {'machines_list': machines_list}) @login_required -@permission_required('cableur') +@can_view_all(IpType) def index_iptype(request): iptype_list = IpType.objects.select_related('extension').select_related('vlan').order_by('type') return render(request, 'machines/index_iptype.html', {'iptype_list':iptype_list}) @login_required -@permission_required('cableur') +@can_view_all(Vlan) def index_vlan(request): vlan_list = Vlan.objects.prefetch_related('iptype_set').order_by('vlan_id') return render(request, 'machines/index_vlan.html', {'vlan_list':vlan_list}) @login_required -@permission_required('cableur') +@can_view_all(MachineType) def index_machinetype(request): machinetype_list = MachineType.objects.select_related('ip_type').order_by('type') return render(request, 'machines/index_machinetype.html', {'machinetype_list':machinetype_list}) @login_required -@permission_required('cableur') +@can_view_all(Nas) def index_nas(request): nas_list = Nas.objects.select_related('machine_type').select_related('nas_type').order_by('name') return render(request, 'machines/index_nas.html', {'nas_list':nas_list}) @login_required -@permission_required('cableur') +@can_view_all(SOA) +@can_view_all(Mx) +@can_view_all(Ns) +@can_view_all(Txt) +@can_view_all(Srv) +@can_view_all(Extension) def index_extension(request): extension_list = Extension.objects.select_related('origin').select_related('soa').order_by('name') soa_list = SOA.objects.order_by('name') @@ -1102,153 +991,21 @@ def index_extension(request): return render(request, 'machines/index_extension.html', {'extension_list':extension_list, 'soa_list': soa_list, 'mx_list': mx_list, 'ns_list': ns_list, 'txt_list' : txt_list, 'srv_list': srv_list}) @login_required -def index_alias(request, interfaceid): - try: - interface = Interface.objects.get(pk=interfaceid) - except Interface.DoesNotExist: - messages.error(request, u"Interface inexistante" ) - return redirect(reverse('machines:index')) - if not request.user.has_perms(('cableur',)) and interface.machine.user != request.user: - messages.error(request, "Vous ne pouvez pas éditer une machine d'un autre user que vous sans droit") - return redirect(reverse( - 'users:profil', - kwargs={'userid':str(request.user.id)} - )) +@can_edit(Interface) +def index_alias(request, interface, interfaceid): alias_list = Domain.objects.filter(cname=Domain.objects.filter(interface_parent=interface)).order_by('name') return render(request, 'machines/index_alias.html', {'alias_list':alias_list, 'interface_id': interfaceid}) @login_required -@permission_required('cableur') +@can_view_all(Service) def index_service(request): service_list = Service.objects.prefetch_related('service_link_set__server__domain__extension').all() servers_list = Service_link.objects.select_related('server__domain__extension').select_related('service').all() return render(request, 'machines/index_service.html', {'service_list':service_list, 'servers_list':servers_list}) -@login_required -def history(request, object, id): - if object == 'machine': - try: - object_instance = Machine.objects.get(pk=id) - except Machine.DoesNotExist: - messages.error(request, "Machine inexistante") - return redirect(reverse('machines:index')) - if not request.user.has_perms(('cableur',)) and object_instance.user != request.user: - messages.error(request, "Vous ne pouvez pas afficher l'historique d'une machine d'un autre user que vous sans droit cableur") - return redirect(reverse( - 'users:profil', - kwargs={'userid':str(request.user.id)} - )) - elif object == 'interface': - try: - object_instance = Interface.objects.get(pk=id) - except Interface.DoesNotExist: - messages.error(request, "Interface inexistante") - return redirect(reverse('machines:index')) - if not request.user.has_perms(('cableur',)) and object_instance.machine.user != request.user: - messages.error(request, "Vous ne pouvez pas afficher l'historique d'une interface d'un autre user que vous sans droit cableur") - return redirect(reverse( - 'users:profil', - kwargs={'userid':str(request.user.id)} - )) - elif object == 'alias': - try: - object_instance = Domain.objects.get(pk=id) - except Domain.DoesNotExist: - messages.error(request, "Alias inexistant") - return redirect(reverse('machines:index')) - if not request.user.has_perms(('cableur',)) and object_instance.cname.interface_parent.machine.user != request.user: - messages.error(request, "Vous ne pouvez pas afficher l'historique d'un alias d'un autre user que vous sans droit cableur") - return redirect(reverse( - 'users:profil', - kwargs={'userid':str(request.user.id)} - )) - elif object == 'machinetype' and request.user.has_perms(('cableur',)): - try: - object_instance = MachineType.objects.get(pk=id) - except MachineType.DoesNotExist: - messages.error(request, "Type de machine inexistant") - return redirect(reverse('machines:index')) - elif object == 'iptype' and request.user.has_perms(('cableur',)): - try: - object_instance = IpType.objects.get(pk=id) - except IpType.DoesNotExist: - messages.error(request, "Type d'ip inexistant") - return redirect(reverse('machines:index')) - elif object == 'extension' and request.user.has_perms(('cableur',)): - try: - object_instance = Extension.objects.get(pk=id) - except Extension.DoesNotExist: - messages.error(request, "Extension inexistante") - return redirect(reverse('machines:index')) - elif object == 'soa' and request.user.has_perms(('cableur',)): - try: - object_instance = SOA.objects.get(pk=id) - except SOA.DoesNotExist: - messages.error(request, "SOA inexistant") - return redirect(reverse('machines:index')) - elif object == 'mx' and request.user.has_perms(('cableur',)): - try: - object_instance = Mx.objects.get(pk=id) - except Mx.DoesNotExist: - messages.error(request, "Mx inexistant") - return redirect(reverse('machines:index')) - elif object == 'txt' and request.user.has_perms(('cableur',)): - try: - object_instance = Txt.objects.get(pk=id) - except Txt.DoesNotExist: - messages.error(request, "Txt inexistant") - return redirect(reverse('machines:index')) - elif object == 'srv' and request.user.has_perms(('cableur',)): - try: - object_instance = Srv.objects.get(pk=id) - except Srv.DoesNotExist: - messages.error(request, "Srv inexistant") - return redirect(reverse('machines:index')) - elif object == 'ns' and request.user.has_perms(('cableur',)): - try: - object_instance = Ns.objects.get(pk=id) - except Ns.DoesNotExist: - messages.error(request, "Ns inexistant") - return redirect(reverse('machines:index')) - elif object == 'service' and request.user.has_perms(('cableur',)): - try: - object_instance = Service.objects.get(pk=id) - except Service.DoesNotExist: - messages.error(request, "Service inexistant") - return redirect(reverse('machines:index')) - elif object == 'vlan' and request.user.has_perms(('cableur',)): - try: - object_instance = Vlan.objects.get(pk=id) - except Vlan.DoesNotExist: - messages.error(request, "Vlan inexistant") - return redirect(reverse('machines:index')) - elif object == 'nas' and request.user.has_perms(('cableur',)): - try: - object_instance = Nas.objects.get(pk=id) - except Nas.DoesNotExist: - messages.error(request, "Nas inexistant") - return redirect(reverse('machines:index')) - else: - messages.error(request, "Objet inconnu") - return redirect(reverse('machines:index')) - options, created = GeneralOption.objects.get_or_create() - pagination_number = options.pagination_number - reversions = Version.objects.get_for_object(object_instance) - paginator = Paginator(reversions, pagination_number) - page = request.GET.get('page') - try: - reversions = paginator.page(page) - except PageNotAnInteger: - # If page is not an integer, deliver first page. - reversions = paginator.page(1) - except EmptyPage: - # If page is out of range (e.g. 9999), deliver last page of results. - reversions = paginator.page(paginator.num_pages) - return render(request, 're2o/history.html', {'reversions': reversions, 'object': object_instance}) - @login_required -@permission_required('cableur') +@can_view_all(OuverturePortList) def index_portlist(request): port_list = OuverturePortList.objects.prefetch_related('ouvertureport_set')\ .prefetch_related('interface_set__domain__extension')\ @@ -1256,14 +1013,10 @@ def index_portlist(request): return render(request, "machines/index_portlist.html", {'port_list':port_list}) @login_required -@permission_required('bureau') -def edit_portlist(request, pk): - try: - port_list_instance = OuverturePortList.objects.get(pk=pk) - except OuverturePortList.DoesNotExist: - messages.error(request, "Liste de ports inexistante") - return redirect(reverse('machines:index-portlist')) - port_list = EditOuverturePortListForm(request.POST or None, instance=port_list_instance) +@can_edit(OuverturePortList) +def edit_portlist(request, ouvertureportlist_instance, ouvertureportlistid): + + port_list = EditOuverturePortListForm(request.POST or None, instance=ouvertureportlist_instance) port_formset = modelformset_factory( OuverturePort, fields=('begin','end','protocole','io'), @@ -1271,7 +1024,7 @@ def edit_portlist(request, pk): can_delete=True, min_num=1, validate_min=True, - )(request.POST or None, queryset=port_list_instance.ouvertureport_set.all()) + )(request.POST or None, queryset=ouvertureportlist_instance.ouvertureport_set.all()) if port_list.is_valid() and port_formset.is_valid(): pl = port_list.save() instances = port_formset.save(commit=False) @@ -1285,23 +1038,16 @@ def edit_portlist(request, pk): return form({'port_list' : port_list, 'ports' : port_formset}, 'machines/edit_portlist.html', request) @login_required -@permission_required('bureau') -def del_portlist(request, pk): - try: - port_list_instance = OuverturePortList.objects.get(pk=pk) - except OuverturePortList.DoesNotExist: - messages.error(request, "Liste de ports inexistante") - return redirect(reverse('machines:index-portlist')) - if port_list_instance.interface_set.all(): - messages.error(request, "Cette liste de ports est utilisée") - return redirect(reverse('machines:index-portlist')) +@can_delete(OuverturePortList) +def del_portlist(request, port_list_instance, ouvertureportlistid): port_list_instance.delete() messages.success(request, "La liste de ports a été supprimée") return redirect(reverse('machines:index-portlist')) @login_required -@permission_required('bureau') +@can_create(OuverturePortList) def add_portlist(request): + port_list = EditOuverturePortListForm(request.POST or None) port_formset = modelformset_factory( OuverturePort, @@ -1330,13 +1076,9 @@ def add_portlist(request): return form({'machineform' : port_list}, 'machines/machine.html', request) @login_required -@permission_required('cableur') -def configure_ports(request, pk): - try: - interface_instance = Interface.objects.get(pk=pk) - except Interface.DoesNotExist: - messages.error(request, u"Interface inexistante" ) - return redirect(reverse('machines:index')) +@can_create(OuverturePort) +@can_edit(Interface) +def configure_ports(request, interface_instance, interfaceid): if not interface_instance.may_have_port_open(): messages.error(request, "Attention, l'ipv4 n'est pas publique, l'ouverture n'aura pas d'effet en v4") interface = EditOuverturePortConfigForm(request.POST or None, instance=interface_instance) @@ -1356,7 +1098,7 @@ class JSONResponse(HttpResponse): @csrf_exempt @login_required -@permission_required('serveur') +@permission_required('machines.serveur') def mac_ip_list(request): interfaces = all_active_assigned_interfaces() seria = InterfaceSerializer(interfaces, many=True) @@ -1364,7 +1106,7 @@ def mac_ip_list(request): @csrf_exempt @login_required -@permission_required('serveur') +@permission_required('machines.serveur') def full_mac_ip_list(request): interfaces = all_active_assigned_interfaces() seria = FullInterfaceSerializer(interfaces, many=True) @@ -1372,7 +1114,7 @@ def full_mac_ip_list(request): @csrf_exempt @login_required -@permission_required('serveur') +@permission_required('machines.serveur') def alias(request): alias = Domain.objects.filter(interface_parent=None).filter(cname__in=Domain.objects.filter(interface_parent__in=Interface.objects.exclude(ipv4=None))).select_related('extension').select_related('cname__extension') seria = DomainSerializer(alias, many=True) @@ -1380,7 +1122,7 @@ def alias(request): @csrf_exempt @login_required -@permission_required('serveur') +@permission_required('machines.serveur') def corresp(request): type = IpType.objects.all().select_related('extension') seria = TypeSerializer(type, many=True) @@ -1388,7 +1130,7 @@ def corresp(request): @csrf_exempt @login_required -@permission_required('serveur') +@permission_required('machines.serveur') def mx(request): mx = Mx.objects.all().select_related('zone').select_related('name__extension') seria = MxSerializer(mx, many=True) @@ -1396,7 +1138,7 @@ def mx(request): @csrf_exempt @login_required -@permission_required('serveur') +@permission_required('machines.serveur') def txt(request): txt = Txt.objects.all().select_related('zone') seria = TxtSerializer(txt, many=True) @@ -1404,7 +1146,7 @@ def txt(request): @csrf_exempt @login_required -@permission_required('serveur') +@permission_required('machines.serveur') def srv(request): srv = Srv.objects.all().select_related('extension').select_related('target__extension') seria = SrvSerializer(srv, many=True) @@ -1412,7 +1154,7 @@ def srv(request): @csrf_exempt @login_required -@permission_required('serveur') +@permission_required('machines.serveur') def ns(request): ns = Ns.objects.exclude(ns__in=Domain.objects.filter(interface_parent__in=Interface.objects.filter(ipv4=None))).select_related('zone').select_related('ns__extension') seria = NsSerializer(ns, many=True) @@ -1420,7 +1162,7 @@ def ns(request): @csrf_exempt @login_required -@permission_required('serveur') +@permission_required('machines.serveur') def zones(request): zones = Extension.objects.all().select_related('origin') seria = ExtensionSerializer(zones, many=True) @@ -1428,21 +1170,21 @@ def zones(request): @csrf_exempt @login_required -@permission_required('serveur') +@permission_required('machines.serveur') def mac_ip(request): seria = mac_ip_list(request) return JSONResponse(seria) @csrf_exempt @login_required -@permission_required('serveur') +@permission_required('machines.serveur') def mac_ip_dns(request): seria = full_mac_ip_list(request) return JSONResponse(seria) @csrf_exempt @login_required -@permission_required('serveur') +@permission_required('machines.serveur') def service_servers(request): service_link = Service_link.objects.all().select_related('server__domain').select_related('service') seria = ServiceServersSerializer(service_link, many=True) @@ -1450,7 +1192,7 @@ def service_servers(request): @csrf_exempt @login_required -@permission_required('serveur') +@permission_required('machines.serveur') def ouverture_ports(request): r = {'ipv4':{}, 'ipv6':{}} for o in OuverturePortList.objects.all().prefetch_related('ouvertureport_set').prefetch_related('interface_set', 'interface_set__ipv4'): @@ -1478,7 +1220,7 @@ def ouverture_ports(request): return JSONResponse(r) @csrf_exempt @login_required -@permission_required('serveur') +@permission_required('machines.serveur') def regen_achieved(request): obj = Service_link.objects.filter(service__in=Service.objects.filter(service_type=request.POST['service']), server__in=Interface.objects.filter(domain__in=Domain.objects.filter(name=request.POST['server']))) if obj: diff --git a/preferences/__init__.py b/preferences/__init__.py index e69de29b..e895e295 100644 --- a/preferences/__init__.py +++ b/preferences/__init__.py @@ -0,0 +1,2 @@ + +from .acl import * diff --git a/preferences/acl.py b/preferences/acl.py new file mode 100644 index 00000000..8ffb4c9b --- /dev/null +++ b/preferences/acl.py @@ -0,0 +1,40 @@ +# -*- mode: python; coding: utf-8 -*- +# 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 © 2017 Gabriel Détraz +# Copyright © 2017 Goulven Kermarec +# Copyright © 2017 Augustin Lemesle +# +# 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. + +"""preferences.acl + +Here are defined some functions to check acl on the application. +""" + +def can_view(user): + """Check if an user can view the application. + + Args: + user: The user who wants to view the application. + + Returns: + A couple (allowed, msg) where allowed is a boolean which is True if + viewing is granted and msg is a message (can be None). + """ + can = user.has_module_perms('preferences') + return can, None if can else "Vous ne pouvez pas voir cette application." diff --git a/preferences/forms.py b/preferences/forms.py index 51cbb885..7dda8620 100644 --- a/preferences/forms.py +++ b/preferences/forms.py @@ -173,7 +173,15 @@ class ServiceForm(ModelForm): class DelServiceForm(Form): """Suppression de services sur la page d'accueil""" services = forms.ModelMultipleChoiceField( - queryset=Service.objects.all(), + queryset=Service.objects.none(), label="Enregistrements service actuels", widget=forms.CheckboxSelectMultiple ) + + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelServiceForm, self).__init__(*args, **kwargs) + if instances: + self.fields['services'].queryset = instances + else: + self.fields['services'].queryset = Service.objects.all() diff --git a/preferences/migrations/0025_auto_20171231_2142.py b/preferences/migrations/0025_auto_20171231_2142.py new file mode 100644 index 00000000..d54b8215 --- /dev/null +++ b/preferences/migrations/0025_auto_20171231_2142.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-12-31 20:42 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('preferences', '0024_optionaluser_all_can_create'), + ] + + operations = [ + migrations.AlterModelOptions( + name='assooption', + options={'permissions': (('view_assooption', "Peut voir les options de l'asso"),)}, + ), + migrations.AlterModelOptions( + name='generaloption', + options={'permissions': (('view_generaloption', 'Peut voir les options générales'),)}, + ), + migrations.AlterModelOptions( + name='mailmessageoption', + options={'permissions': (('view_mailmessageoption', 'Peut voir les options de mail'),)}, + ), + migrations.AlterModelOptions( + name='optionalmachine', + options={'permissions': (('view_optionalmachine', 'Peut voir les options de machine'),)}, + ), + migrations.AlterModelOptions( + name='optionaltopologie', + options={'permissions': (('view_optionaltopologie', 'Peut voir les options de topologie'),)}, + ), + migrations.AlterModelOptions( + name='optionaluser', + options={'permissions': (('view_optionaluser', "Peut voir les options de l'user"),)}, + ), + migrations.AlterModelOptions( + name='service', + options={'permissions': (('view_service', 'Peut voir les options de service'),)}, + ), + ] diff --git a/preferences/migrations/0027_merge_20180106_2019.py b/preferences/migrations/0027_merge_20180106_2019.py new file mode 100644 index 00000000..68ce34fb --- /dev/null +++ b/preferences/migrations/0027_merge_20180106_2019.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-01-06 19:19 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('preferences', '0025_auto_20171231_2142'), + ('preferences', '0026_auto_20171216_0401'), + ] + + operations = [ + ] diff --git a/preferences/models.py b/preferences/models.py index 2e803b66..8dfc4260 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -26,7 +26,7 @@ Reglages généraux, machines, utilisateurs, mail, general pour l'application. from __future__ import unicode_literals from django.db import models -from cotisations.models import Paiement +import cotisations.models class OptionalUser(models.Model): @@ -47,10 +47,67 @@ class OptionalUser(models.Model): help_text="Tous les users peuvent en créer d'autres", ) + class Meta: + permissions = ( + ("view_optionaluser", "Peut voir les options de l'user"), + ) + + def get_instance(*args, **kwargs): + return OptionalUser.objects.get_or_create() + + def can_create(user_request, *args, **kwargs): + """Check if an user can create a OptionalUser object. + + :param user_request: The user who wants to create a user object. + :return: a message and a boolean which is True if the user can create. + """ + return user_request.has_perm('preferences.add_optionaluser'), u"Vous n'avez pas le droit\ + de créer les préférences concernant les users" + + def can_edit(self, user_request, *args, **kwargs): + """Check if an user can edit a OptionalUser object. + + :param self: The OptionalUser which is to be edited. + :param user_request: The user who requests to edit self. + :return: a message and a boolean which is True if edition is granted. + """ + return user_request.has_perm('preferences.change_optionaluser'), u"Vous n'avez pas le droit\ + d'éditer les préférences concernant les users" + + def can_delete(self, user_request, *args, **kwargs): + """Check if an user can delete a OptionalUser object. + + :param self: The OptionalUser which is to be deleted. + :param user_request: The user who requests deletion. + :return: True if deletion is granted, and a message. + """ + return user_request.has_perm('preferences.delete_optionaluser'), u"Vous n'avez pas le droit\ + de supprimer les préférences concernant les users" + + def can_view_all(user_request, *args, **kwargs): + """Check if an user can access to the list of every OptionalUser objects + + :param user_request: The user who wants to view the list. + :return: True if the user can view the list and an explanation message. + """ + return user_request.has_perm('preferences.view_optionaluser'), u"Vous n'avez pas le droit\ + de voir les préférences concernant les utilisateurs" + + def can_view(self, user_request, *args, **kwargs): + """Check if an user can view a OptionalUser object. + + :param self: The targeted OptionalUser. + :param user_request: The user who ask for viewing the target. + :return: A boolean telling if the acces is granted and an explanation + text + """ + return user_request.has_perm('preferences.view_optionaluser'), u"Vous n'avez pas le droit\ + de voir les préférences concernant les utilisateurs" + def clean(self): """Creation du mode de paiement par solde""" if self.user_solde: - Paiement.objects.get_or_create(moyen="Solde") + cotisations.models.Paiement.objects.get_or_create(moyen="Solde") class OptionalMachine(models.Model): @@ -63,6 +120,64 @@ class OptionalMachine(models.Model): max_lambdauser_aliases = models.IntegerField(default=10) ipv6 = models.BooleanField(default=False) + class Meta: + permissions = ( + ("view_optionalmachine", "Peut voir les options de machine"), + ) + + def get_instance(*args, **kwargs): + return OptionalMachine.objects.get_or_create() + + def can_create(user_request, *args, **kwargs): + """Check if an user can create a OptionalMachine object. + + :param user_request: The user who wants to create an object. + :return: a message and a boolean which is True if the user can create. + """ + return user_request.has_perm('preferences.add_optionalmachine'), u"Vous n'avez pas le droit\ + de créer les préférences concernant les machines" + + def can_edit(self, user_request, *args, **kwargs): + """Check if an user can edit a OptionalMachine object. + + :param self: The OptionalMachine which is to be edited. + :param user_request: The user who requests to edit self. + :return: a message and a boolean which is True if edition is granted. + """ + return user_request.has_perm('preferences.change_optionalmachine'), u"Vous n'avez pas le droit\ + d'éditer les préférences concernant les machines" + + def can_delete(self, user_request, *args, **kwargs): + """Check if an user can delete a OptionalMachine object. + + :param self: The OptionalMachine which is to be deleted. + :param user_request: The user who requests deletion. + :return: True if deletion is granted, and a message. + """ + + return user_request.has_perm('preferences.delete_optionalmachine'), u"Vous n'avez pas le droit\ + de supprimer les préférences concernant les machines" + + def can_view_all(user_request, *args, **kwargs): + """Check if an user can access to the list of every OptionalMachine objects + + :param user_request: The user who wants to view the list. + :return: True if the user can view the list and an explanation message. + """ + return user_request.has_perm('preferences.view_optionalmachine'), u"Vous n'avez pas le droit\ + de voir les préférences concernant les machines" + + def can_view(self, user_request, *args, **kwargs): + """Check if an user can view a OptionalMachine object. + + :param self: The targeted OptionalMachine. + :param user_request: The user who ask for viewing the target. + :return: A boolean telling if the acces is granted and an explanation + text + """ + return user_request.has_perm('preferences.view_optionalmachine'), u"Vous n'avez pas le droit\ + de voir les préférences concernant les machines" + class OptionalTopologie(models.Model): """Reglages pour la topologie : mode d'accès radius, vlan où placer @@ -96,6 +211,63 @@ class OptionalTopologie(models.Model): null=True ) + class Meta: + permissions = ( + ("view_optionaltopologie", "Peut voir les options de topologie"), + ) + + def get_instance(*args, **kwargs): + return OptionalTopologie.objects.get_or_create() + + def can_create(user_request, *args, **kwargs): + """Check if an user can create a OptionalTopologie object. + + :param user_request: The user who wants to create an object. + :return: a message and a boolean which is True if the user can create. + """ + return user_request.has_perm('preferences.add_optionaltopologie'), u"Vous n'avez pas le droit\ + de créer les préférences concernant la topologie" + + def can_edit(self, user_request, *args, **kwargs): + """Check if an user can edit a OptionalTopologie object. + + :param self: The OptionalTopologie which is to be edited. + :param user_request: The user who requests to edit self. + :return: a message and a boolean which is True if edition is granted. + """ + return user_request.has_perm('preferences.change_optionaltopologie'), u"Vous n'avez pas le droit\ + d'éditer les préférences concernant la topologie" + + def can_delete(self, user_request, *args, **kwargs): + """Check if an user can delete a OptionalTopologie object. + + :param self: The OptionalTopologie which is to be deleted. + :param user_request: The user who requests deletion. + :return: True if deletion is granted, and a message. + """ + return user_request.has_perm('preferences.delete_optionaltoplogie'), u"Vous n'avez pas le droit\ + d'éditer les préférences concernant la topologie" + + def can_view_all(user_request, *args, **kwargs): + """Check if an user can access to the list of every OptionalTopologie objects + + :param user_request: The user who wants to view the list. + :return: True if the user can view the list and an explanation message. + """ + return user_request.has_perm('preferences.view_optionaltopologie'), u"Vous n'avez pas le droit\ + de voir les préférences concernant la topologie" + + def can_view(self, user_request, *args, **kwargs): + """Check if an user can view a OptionalTopologie object. + + :param self: The targeted OptionalTopologie. + :param user_request: The user who ask for viewing the target. + :return: A boolean telling if the acces is granted and an explanation + text + """ + return user_request.has_perm('preferences.view_optionaltopologie'), u"Vous n'avez pas le droit\ + de voir les préférences concernant la topologie" + class GeneralOption(models.Model): """Options générales : nombre de resultats par page, nom du site, @@ -114,6 +286,64 @@ class GeneralOption(models.Model): site_name = models.CharField(max_length=32, default="Re2o") email_from = models.EmailField(default="www-data@serveur.net") + class Meta: + permissions = ( + ("view_generaloption", "Peut voir les options générales"), + ) + + def get_instance(*args, **kwargs): + return GeneralOption.objects.get_or_create() + + def can_create(user_request, *args, **kwargs): + """Check if an user can create a GeneralOption object. + + :param user_request: The user who wants to create an object. + :return: a message and a boolean which is True if the user can create. + """ + return user_request.has_perm('preferences.add_generaloption'), u"Vous n'avez pas le droit\ + de créer les préférences générales" + + def can_edit(self, user_request, *args, **kwargs): + """Check if an user can edit a GeneralOption object. + + :param self: The GeneralOption which is to be edited. + :param user_request: The user who requests to edit self. + :return: a message and a boolean which is True if edition is granted. + """ + return user_request.has_perm('preferences.change_generaloption'), u"Vous n'avez pas le droit\ + d'éditer les préférences générales" + + def can_delete(self, user_request, *args, **kwargs): + """Check if an user can delete a GeneralOption object. + + :param self: The GeneralOption which is to be deleted. + :param user_request: The user who requests deletion. + :return: True if deletion is granted, and a message. + """ + return user_request.has_perm('preferences.delete_generaloption'), u"Vous n'avez pas le droit\ + d'éditer les préférences générales" + + def can_view_all(user_request, *args, **kwargs): + """Check if an user can access to the list of every GeneralOption objects + + :param user_request: The user who wants to view the list. + :return: True if the user can view the list and an explanation message. + """ + + return user_request.has_perm('preferences.view_generaloption'), u"Vous n'avez pas le droit\ + de voir les préférences générales" + + def can_view(self, user_request, *args, **kwargs): + """Check if an user can view a GeneralOption object. + + :param self: The targeted GeneralOption. + :param user_request: The user who ask for viewing the target. + :return: A boolean telling if the acces is granted and an explanation + text + """ + return user_request.has_perm('preferences.view_generaloption'), u"Vous n'avez pas le droit\ + de voir les préférences générales" + class Service(models.Model): """Liste des services affichés sur la page d'accueil : url, description, @@ -123,6 +353,65 @@ class Service(models.Model): description = models.TextField() image = models.ImageField(upload_to='logo', blank=True) + class Meta: + permissions = ( + ("view_service", "Peut voir les options de service"), + ) + + def get_instance(serviceid, *args, **kwargs): + return Service.objects.get(pk=serviceid) + + def can_create(user_request, *args, **kwargs): + """Check if an user can create a Service object. + + :param user_request: The user who wants to create an object. + :return: a message and a boolean which is True if the user can create. + """ + + return user_request.has_perm('preferences.add_service'), u"Vous n'avez pas le droit\ + de créer un service pour la page d'accueil" + + def can_edit(self, user_request, *args, **kwargs): + """Check if an user can edit a Service object. + + :param self: The Service which is to be edited. + :param user_request: The user who requests to edit self. + :return: a message and a boolean which is True if edition is granted. + """ + return user_request.has_perm('preferences.change_service'), u"Vous n'avez pas le droit\ + d'éditer les services pour la page d'accueil" + + def can_delete(self, user_request, *args, **kwargs): + """Check if an user can delete a Service object. + + :param self: The Right which is to be deleted. + :param user_request: The user who requests deletion. + :return: True if deletion is granted, and a message. + """ + return user_request.has_perm('preferences.delete_service'), u"Vous n'avez pas le droit\ + de supprimer les services pour la page d'accueil" + + def can_view_all(user_request, *args, **kwargs): + """Check if an user can access to the list of every Service objects + + :param user_request: The user who wants to view the list. + :return: True if the user can view the list and an explanation message. + """ + + return user_request.has_perm('preferences.view_service'), u"Vous n'avez pas le droit\ + de voir les services pour la page d'accueil" + + def can_view(self, user_request, *args, **kwargs): + """Check if an user can view a Service object. + + :param self: The targeted Service. + :param user_request: The user who ask for viewing the target. + :return: A boolean telling if the acces is granted and an explanation + text + """ + return user_request.has_perm('preferences.view_service'), u"Vous n'avez pas le droit\ + de voir les services pour la page d'accueil" + def __str__(self): return str(self.name) @@ -148,6 +437,63 @@ class AssoOption(models.Model): null=True ) + class Meta: + permissions = ( + ("view_assooption", "Peut voir les options de l'asso"), + ) + + def get_instance(*args, **kwargs): + return AssoOption.objects.get_or_create() + + def can_create(user_request, *args, **kwargs): + """Check if an user can create a AssoOption object. + + :param user_request: The user who wants to create an object. + :return: a message and a boolean which is True if the user can create. + """ + return user_request.has_perm('preferences.add_assooption'), u"Vous n'avez pas le droit\ + d'éditer les préférences concernant l'association" + + def can_edit(self, user_request, *args, **kwargs): + """Check if an user can edit a AssoOption object. + + :param self: The AssoOption which is to be edited. + :param user_request: The user who requests to edit self. + :return: a message and a boolean which is True if edition is granted. + """ + return user_request.has_perm('preferences.change_assooption'), u"Vous n'avez pas le droit\ + d'éditer les préférences concernant l'association" + + def can_delete(self, user_request, *args, **kwargs): + """Check if an user can delete a AssoOption object. + + :param self: The AssoOption which is to be deleted. + :param user_request: The user who requests deletion. + :return: True if deletion is granted, and a message. + """ + return user_request.has_perm('preferences.delete_assooption'), u"Vous n'avez pas le droit\ + d'éditer les préférences concernant l'association" + + def can_view_all(user_request, *args, **kwargs): + """Check if an user can access to the list of every AssoOption objects + + :param user_request: The user who wants to view the list. + :return: True if the user can view the list and an explanation message. + """ + return user_request.has_perm('preferences.view_assooption'), u"Vous n'avez pas le droit\ + de voir les préférences concernant l'association" + + def can_view(self, user_request, *args, **kwargs): + """Check if an user can view a AssoOption object. + + :param self: The targeted AssoOption. + :param user_request: The user who ask for viewing the target. + :return: A boolean telling if the acces is granted and an explanation + text + """ + return user_request.has_perm('preferences.view_assooption'), u"Vous n'avez pas le droit\ + de voir les préférences concernant l'association" + class MailMessageOption(models.Model): """Reglages, mail de bienvenue et autre""" @@ -155,3 +501,61 @@ class MailMessageOption(models.Model): welcome_mail_fr = models.TextField(default="") welcome_mail_en = models.TextField(default="") + + class Meta: + permissions = ( + ("view_mailmessageoption", "Peut voir les options de mail"), + ) + + def get_instance(*args, **kwargs): + return MailMessageOption.objects.get_or_create() + + def can_create(user_request, *args, **kwargs): + """Check if an user can create a MailMessageOption object. + + :param user_request: The user who wants to create an object. + :return: a message and a boolean which is True if the user can create. + """ + return user_request.has_perm('preferences.add_mailmessageoption'), u"Vous n'avez pas le droit\ + d'éditer les préférences concernant les mails" + + def can_edit(self, user_request, *args, **kwargs): + """Check if an user can edit a MailMessageOption object. + + :param self: The MailMessageOption which is to be edited. + :param user_request: The user who requests to edit self. + :return: a message and a boolean which is True if edition is granted. + """ + + return user_request.has_perm('preferences.change_mailmessageoption'), u"Vous n'avez pas le droit\ + d'éditer les préférences concernant les mails" + + def can_delete(self, user_request, *args, **kwargs): + """Check if an user can delete a AssoOption object. + + :param self: The AssoOption which is to be deleted. + :param user_request: The user who requests deletion. + :return: True if deletion is granted, and a message. + """ + return user_request.has_perm('preferences.delete_mailmessageoption'), u"Vous n'avez pas le droit\ + d'éditer les préférences concernant les mails" + + def can_view_all(user_request, *args, **kwargs): + """Check if an user can access to the list of every AssoOption objects + + :param user_request: The user who wants to view the list. + :return: True if the user can view the list and an explanation message. + """ + return user_request.has_perm('preferences.view_mailmessageoption'), u"Vous n'avez pas le droit\ + de voir les préférences concernant les mails" + + def can_view(self, user_request, *args, **kwargs): + """Check if an user can view a AssoOption object. + + :param self: The targeted AssoOption. + :param user_request: The user who ask for viewing the target. + :return: A boolean telling if the acces is granted and an explanation + text + """ + return user_request.has_perm('preferences.view_mailmessageoption'), u"Vous n'avez pas le droit\ + de voir les préférences concernant les mails" diff --git a/preferences/templates/preferences/aff_service.html b/preferences/templates/preferences/aff_service.html index e5f8aecf..b4d4894c 100644 --- a/preferences/templates/preferences/aff_service.html +++ b/preferences/templates/preferences/aff_service.html @@ -21,7 +21,7 @@ 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 acl %}
+ {% can_delete pl %} {% include 'buttons/suppr.html' with href='machines:del-portlist' id=pl.id %} + {% acl_end %} + {% can_edit pl %} {% include 'buttons/edit.html' with href='machines:edit-portlist' id=pl.id %} + {% acl_end %}
@@ -40,9 +40,9 @@ with this program; if not, write to the Free Software Foundation, Inc., diff --git a/preferences/templates/preferences/display_preferences.html b/preferences/templates/preferences/display_preferences.html index ad807155..2b1caec8 100644 --- a/preferences/templates/preferences/display_preferences.html +++ b/preferences/templates/preferences/display_preferences.html @@ -24,17 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load acl %} {% block title %}Création et modification des préférences{% endblock %} {% block content %}

Préférences utilisateur

- {% if is_bureau %} Editer - {% endif %}

{{ service.description }} {{ service.image }} - {% if is_admin %} - {% include 'buttons/edit.html' with href='preferences:edit-services' id=service.id %} - {% endif %} + {% can_edit service%} + {% include 'buttons/edit.html' with href='preferences:edit-service' id=service.id %} + {% acl_end %} {% include 'buttons/history.html' with href='preferences:history' name='service' id=service.id %}
@@ -58,12 +57,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,

Préférences machines

- {% if is_bureau %} Editer - {% endif %}

@@ -81,12 +78,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,

Préférences topologie

- {% if is_bureau %} Editer - {% endif %}

@@ -104,14 +99,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,

Préférences generales

- {% if is_bureau %} Editer - {% endif %}

-

+

@@ -137,12 +130,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Nom du site web

Données de l'association

- {% if is_bureau %} Editer - {% endif %}

@@ -164,19 +155,17 @@ with this program; if not, write to the Free Software Foundation, Inc., - + - -
{{ assooptions.telephone }} Pseudo d'usage {{ assooptions.pseudo }}
Objet utilisateur de l'association {{ assooptions.utilisateur_asso }}
+

Messages personalisé dans les mails

- {% if is_bureau %} Editer - {% endif %}

@@ -190,10 +179,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,

Liste des services page d'accueil

- {% if is_infra %} - Ajouter un service + {% can_create Service%} + Ajouter un service + {% acl_end %} Supprimer un ou plusieurs service - {% endif %} {% include "preferences/aff_service.html" with service_list=service_list %}

diff --git a/preferences/urls.py b/preferences/urls.py index f10d25a0..3bc15275 100644 --- a/preferences/urls.py +++ b/preferences/urls.py @@ -28,6 +28,7 @@ from __future__ import unicode_literals from django.conf.urls import url from . import views +import re2o urlpatterns = [ @@ -61,17 +62,18 @@ urlpatterns = [ views.edit_options, name='edit-options' ), - url(r'^add_services/$', views.add_services, name='add-services'), + url(r'^add_service/$', views.add_service, name='add-service'), url( - r'^edit_services/(?P[0-9]+)$', - views.edit_services, - name='edit-services' + r'^edit_service/(?P[0-9]+)$', + views.edit_service, + name='edit-service' ), url(r'^del_services/$', views.del_services, name='del-services'), url( - r'^history/(?Pservice)/(?P[0-9]+)$', - views.history, - name='history' + r'^history/(?P\w+)/(?P[0-9]+)$', + re2o.views.history, + name='history', + kwargs={'application':'preferences'}, ), url(r'^$', views.display_options, name='display-options'), ] diff --git a/preferences/views.py b/preferences/views.py index 493c1d14..45324f53 100644 --- a/preferences/views.py +++ b/preferences/views.py @@ -42,6 +42,7 @@ from reversion.models import Version from reversion import revisions as reversion from re2o.views import form +from re2o.acl import can_create, can_edit, can_delete_set, can_view_all from .forms import ServiceForm, DelServiceForm from .models import Service, OptionalUser, OptionalMachine, AssoOption from .models import MailMessageOption, GeneralOption, OptionalTopologie @@ -50,7 +51,12 @@ from . import forms @login_required -@permission_required('cableur') +@can_view_all(OptionalUser) +@can_view_all(OptionalMachine) +@can_view_all(OptionalTopologie) +@can_view_all(GeneralOption) +@can_view_all(AssoOption) +@can_view_all(MailMessageOption) def display_options(request): """Vue pour affichage des options (en vrac) classé selon les models correspondants dans un tableau""" @@ -80,6 +86,11 @@ def edit_options(request, section): form_instance = getattr(forms, 'Edit' + section + 'Form', None) if model and form: options_instance, _created = model.objects.get_or_create() + can, msg = options_instance.can_edit(request.user) + if not can: + messages.error(request, msg or "Vous ne pouvez pas éditer cette\ + option.") + return redirect('/') options = form_instance( request.POST or None, instance=options_instance @@ -106,57 +117,52 @@ def edit_options(request, section): @login_required -@permission_required('admin') -def add_services(request): +@can_create(Service) +def add_service(request): """Ajout d'un service de la page d'accueil""" - services = ServiceForm(request.POST or None) - if services.is_valid(): + service = ServiceForm(request.POST or None) + if service.is_valid(): with transaction.atomic(), reversion.create_revision(): - services.save() + service.save() reversion.set_user(request.user) reversion.set_comment("Création") messages.success(request, "Ce service a été ajouté") return redirect(reverse('preferences:display-options')) return form( - {'preferenceform': services}, + {'preferenceform': service}, 'preferences/preferences.html', request ) @login_required -@permission_required('admin') -def edit_services(request, servicesid): +@can_edit(Service) +def edit_service(request, service_instance, serviceid): """Edition des services affichés sur la page d'accueil""" - try: - services_instance = Service.objects.get(pk=servicesid) - except Service.DoesNotExist: - messages.error(request, u"Entrée inexistante") - return redirect(reverse('preferences:display-options')) - services = ServiceForm(request.POST or None, instance=services_instance) - if services.is_valid(): + service = ServiceForm(request.POST or None, instance=service_instance) + if service.is_valid(): with transaction.atomic(), reversion.create_revision(): - services.save() + service.save() reversion.set_user(request.user) reversion.set_comment( "Champs modifié(s) : %s" % ', '.join( - field for field in services.changed_data + field for field in service.changed_data ) ) messages.success(request, "Service modifié") return redirect(reverse('preferences:display-options')) return form( - {'preferenceform': services}, + {'preferenceform': service}, 'preferences/preferences.html', request ) @login_required -@permission_required('admin') -def del_services(request): +@can_delete_set(Service) +def del_services(request, instances): """Suppression d'un service de la page d'accueil""" - services = DelServiceForm(request.POST or None) + services = DelServiceForm(request.POST or None, instances=instances) if services.is_valid(): services_dels = services.cleaned_data['services'] for services_del in services_dels: @@ -164,7 +170,7 @@ def del_services(request): with transaction.atomic(), reversion.create_revision(): services_del.delete() reversion.set_user(request.user) - messages.success(request, "Le services a été supprimée") + messages.success(request, "Le service a été supprimée") except ProtectedError: messages.error(request, "Erreur le service\ suivant %s ne peut être supprimé" % services_del) @@ -174,33 +180,3 @@ def del_services(request): 'preferences/preferences.html', request ) - - -@login_required -@permission_required('cableur') -def history(request, object_name, object_id): - """Historique de creation et de modification d'un service affiché sur - la page d'accueil""" - if object_name == 'service': - try: - object_instance = Service.objects.get(pk=object_id) - except Service.DoesNotExist: - messages.error(request, "Service inexistant") - return redirect(reverse('preferences:display-options')) - options, _created = GeneralOption.objects.get_or_create() - pagination_number = options.pagination_number - reversions = Version.objects.get_for_object(object_instance) - paginator = Paginator(reversions, pagination_number) - page = request.GET.get('page') - try: - reversions = paginator.page(page) - except PageNotAnInteger: - # If page is not an integer, deliver first page. - reversions = paginator.page(1) - except EmptyPage: - # If page is out of range (e.g. 9999), deliver last page of results. - reversions = paginator.page(paginator.num_pages) - return render(request, 're2o/history.html', { - 'reversions': reversions, - 'object': object_instance - }) diff --git a/re2o/acl.py b/re2o/acl.py new file mode 100644 index 00000000..ffbbea42 --- /dev/null +++ b/re2o/acl.py @@ -0,0 +1,235 @@ +# -*- mode: python; coding: utf-8 -*- +# 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 © 2017 Gabriel Détraz +# Copyright © 2017 Goulven Kermarec +# Copyright © 2017 Augustin Lemesle +# +# 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. + +"""Handles ACL for re2o. + +Here are defined some decorators that can be used in views to handle ACL. +""" +from __future__ import unicode_literals + +import sys + +from django.contrib import messages +from django.shortcuts import redirect +from django.urls import reverse + +import cotisations, logs, machines, preferences, search, topologie, users + + +def can_create(model): + """Decorator to check if an user can create a model. + It assumes that a valid user exists in the request and that the model has a + method can_create(user) which returns true if the user can create this kind + of models. + """ + def decorator(view): + def wrapper(request, *args, **kwargs): + can, msg = model.can_create(request.user, *args, **kwargs) + if not can: + messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu") + return redirect(reverse('users:profil', + kwargs={'userid':str(request.user.id)} + )) + return view(request, *args, **kwargs) + return wrapper + return decorator + + +def can_edit(model, *field_list): + """Decorator to check if an user can edit a model. + It tries to get an instance of the model, using + `model.get_instance(*args, **kwargs)` and assumes that the model has a + method `can_edit(user)` which returns `true` if the user can edit this + kind of models. + """ + def decorator(view): + def wrapper(request, *args, **kwargs): + try: + instance = model.get_instance(*args, **kwargs) + except model.DoesNotExist: + messages.error(request, u"Entrée inexistante") + return redirect(reverse('users:profil', + kwargs={'userid':str(request.user.id)} + )) + can, msg = instance.can_edit(request.user) + if not can: + messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu") + return redirect(reverse('users:profil', + kwargs={'userid':str(request.user.id)} + )) + for field in field_list: + can_change = getattr(instance, 'can_change_' + field) + can, msg = can_change(request.user, *args, **kwargs) + if not can: + messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu") + return redirect(reverse('users:profil', + kwargs={'userid':str(request.user.id)} + )) + return view(request, instance, *args, **kwargs) + return wrapper + return decorator + + +def can_change(model, *field_list): + """Decorator to check if an user can edit a field of a model class. + Difference with can_edit : take a class and not an instance + """ + def decorator(view): + def wrapper(request, *args, **kwargs): + for field in field_list: + can_change = getattr(model, 'can_change_' + field) + can, msg = can_change(request.user, *args, **kwargs) + if not can: + messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu") + return redirect(reverse('users:profil', + kwargs={'userid':str(request.user.id)} + )) + return view(request, *args, **kwargs) + return wrapper + return decorator + + +def can_delete(model): + """Decorator to check if an user can delete a model. + It tries to get an instance of the model, using + `model.get_instance(*args, **kwargs)` and assumes that the model has a + method `can_delete(user)` which returns `true` if the user can delete this + kind of models. + """ + def decorator(view): + def wrapper(request, *args, **kwargs): + try: + instance = model.get_instance(*args, **kwargs) + except model.DoesNotExist: + messages.error(request, u"Entrée inexistante") + return redirect(reverse('users:profil', + kwargs={'userid':str(request.user.id)} + )) + can, msg = instance.can_delete(request.user) + if not can: + messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu") + return redirect(reverse('users:profil', + kwargs={'userid':str(request.user.id)} + )) + return view(request, instance, *args, **kwargs) + return wrapper + return decorator + + +def can_delete_set(model): + """Decorator which returns a list of detable models by request user. + If none of them, return an error""" + def decorator(view): + def wrapper(request, *args, **kwargs): + all_objects = model.objects.all() + instances_id = [] + for instance in all_objects: + can, msg = instance.can_delete(request.user) + if can: + instances_id.append(instance.id) + instances = model.objects.filter(id__in=instances_id) + if not instances: + messages.error(request, "Vous ne pouvez pas accéder à ce menu") + return redirect(reverse('users:profil', + kwargs={'userid':str(request.user.id)} + )) + return view(request, instances, *args, **kwargs) + return wrapper + return decorator + + +def can_view(model): + """Decorator to check if an user can view a model. + It tries to get an instance of the model, using + `model.get_instance(*args, **kwargs)` and assumes that the model has a + method `can_view(user)` which returns `true` if the user can view this + kind of models. + """ + def decorator(view): + def wrapper(request, *args, **kwargs): + try: + instance = model.get_instance(*args, **kwargs) + except model.DoesNotExist: + messages.error(request, u"Entrée inexistante") + return redirect(reverse('users:profil', + kwargs={'userid':str(request.user.id)} + )) + can, msg = instance.can_view(request.user) + if not can: + messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu") + return redirect(reverse('users:profil', + kwargs={'userid':str(request.user.id)} + )) + return view(request, instance, *args, **kwargs) + return wrapper + return decorator + + +def can_view_all(model): + """Decorator to check if an user can view a class of model. + """ + def decorator(view): + def wrapper(request, *args, **kwargs): + can, msg = model.can_view_all(request.user) + if not can: + messages.error(request, msg or "Vous ne pouvez pas accéder à ce menu") + return redirect(reverse('users:profil', + kwargs={'userid':str(request.user.id)} + )) + return view(request, *args, **kwargs) + return wrapper + return decorator + + +def can_view_app(app_name): + """Decorator to check if an user can view an application. + """ + assert app_name in sys.modules.keys() + def decorator(view): + def wrapper(request, *args, **kwargs): + app = sys.modules[app_name] + can,msg = app.can_view(request.user) + if can: + return view(request, *args, **kwargs) + messages.error(request, msg) + return redirect(reverse('users:profil', + kwargs={'userid':str(request.user.id)} + )) + return wrapper + return decorator + + +def can_edit_history(view): + """Decorator to check if an user can edit history.""" + def wrapper(request, *args, **kwargs): + if request.user.has_perm('admin.change_logentry'): + return view(request, *args, **kwargs) + messages.error( + request, + "Vous ne pouvez pas éditer l'historique." + ) + return redirect(reverse('users:profil', + kwargs={'userid':str(request.user.id)} + )) + return wrapper + diff --git a/re2o/context_processors.py b/re2o/context_processors.py index 0f9ea3fc..f696c3f4 100644 --- a/re2o/context_processors.py +++ b/re2o/context_processors.py @@ -39,28 +39,10 @@ def context_user(request): messages.warning(request, global_message) if user.is_authenticated(): interfaces = user.user_interfaces() - is_cableur = user.is_cableur - is_bureau = user.is_bureau - is_bofh = user.is_bofh - is_trez = user.is_trez - is_infra = user.is_infra - is_admin = user.is_admin else: interfaces = None - is_cableur = False - is_bureau = False - is_bofh = False - is_trez = False - is_infra = False - is_admin = False return { 'request_user': user, - 'is_cableur': is_cableur, - 'is_bureau': is_bureau, - 'is_bofh': is_bofh, - 'is_trez': is_trez, - 'is_infra': is_infra, - 'is_admin': is_admin, 'interfaces': interfaces, 'site_name': general_options.site_name, 'ipv6_enabled': machine_options.ipv6, diff --git a/re2o/field_permissions.py b/re2o/field_permissions.py new file mode 100644 index 00000000..dc5466c4 --- /dev/null +++ b/re2o/field_permissions.py @@ -0,0 +1,79 @@ +from django.db import models +from django import forms +from functools import partial + + +class FieldPermissionModelMixin: + field_permissions = {} # {'field_name': callable} + FIELD_PERM_CODENAME = 'can_change_{model}_{name}' + FIELD_PERMISSION_GETTER = 'can_change_{name}' + FIELD_PERMISSION_MISSING_DEFAULT = True + + def has_field_perm(self, user, field): + if field in self.field_permissions: + checks = self.field_permissions[field] + if not isinstance(checks, (list, tuple)): + checks = [checks] + + else: + checks = [] + + # Consult the optional field-specific hook. + getter_name = self.FIELD_PERMISSION_GETTER.format(name=field) + if hasattr(self, getter_name): + checks.append(getattr(self, getter_name)) + + # Try to find a static permission for the field + else: + perm_label = self.FIELD_PERM_CODENAME.format(**{ + 'model': self._meta.model_name, + 'name': field, + }) + if perm_label in dict(self._meta.permissions): + checks.append(perm_label) + + # No requirements means no restrictions. + if not len(checks): + return self.FIELD_PERMISSION_MISSING_DEFAULT + + # Try to find a user setting that qualifies them for permission. + for perm in checks: + if callable(perm): + result, reason = perm(user_request=user) + if result is not None: + return result + else: + result = user.has_perm(perm) # Don't supply 'obj', or else infinite recursion. + if result: + return True + + # If no requirement can be met, then permission is denied. + return False + +class FieldPermissionModel(FieldPermissionModelMixin, models.Model): + class Meta: + abstract = True + + +class FieldPermissionFormMixin: + """ + Construit le formulaire et retire les champs interdits + """ + def __init__(self, *args, **kwargs): + user = kwargs.pop('user') + + super(FieldPermissionFormMixin, self).__init__(*args, **kwargs) + to_be_deleted = [] + for name in self.fields: + if not self.instance.has_field_perm(user, field=name): + to_be_deleted.append(name) + for name in to_be_deleted: + self.remove_unauthorized_field(name) + + def remove_unauthorized_field(self, name): + del self.fields[name] + + +class FieldPermissionForm(FieldPermissionFormMixin, forms.ModelForm): + pass + diff --git a/re2o/templates/re2o/index.html b/re2o/templates/re2o/index.html index 477c64e2..877faf10 100644 --- a/re2o/templates/re2o/index.html +++ b/re2o/templates/re2o/index.html @@ -29,7 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% block title %}Accueil{% endblock %} {% block content %} -

Bienvenue sur {{ site_name }} !

+

Bienvenue sur {{ request.get_host }} !

{% for service_list in services_urls %} diff --git a/re2o/templatetags/__init__.py b/re2o/templatetags/__init__.py index d9561b4b..5be3ef33 100644 --- a/re2o/templatetags/__init__.py +++ b/re2o/templatetags/__init__.py @@ -2,19 +2,19 @@ # 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 © 2017 Maël Kervella -# +# # 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. diff --git a/re2o/templatetags/acl.py b/re2o/templatetags/acl.py new file mode 100644 index 00000000..79bdbc5f --- /dev/null +++ b/re2o/templatetags/acl.py @@ -0,0 +1,410 @@ +# -*- mode: python; coding: utf-8 -*- +# 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 © 2017 Maël Kervella +# +# 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. + +""" +Set of templatetags for using acl in templates: + - can_create (model) + - cannot_create (model) + - can_edit (instance) + - cannot_edit (instance) + +Some templatetags require a model to calculate the acl while others are need +an instance of a model (either Model.can_xxx or instance.can_xxx) + +**Parameters**: + model_name or instance - Either the model_name (if templatetag is based on + model) or an instantiated object (if templatetag is base on instance) + that needs to be checked for the current user + args - Any other argument that is interpreted as a python object and passed + to the acl function (can_xxx) + +**Usage**: + {% [arg1 [arg2 [...]]]%} +