mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-09-10 16:23:09 +00:00
416 lines
13 KiB
Python
416 lines
13 KiB
Python
# -*- 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**:
|
|
{% <acl_name> <obj> [arg1 [arg2 [...]]]%}
|
|
<template stuff>
|
|
[{% acl_else %}
|
|
<template stuff>]
|
|
{% acl_end %}
|
|
|
|
where <acl_name> is one of the templatetag names available
|
|
(can_xxx or cannot_xxx)
|
|
|
|
**Example**:
|
|
{% can_create Machine targeted_user %}
|
|
<p>I'm authorized to create new machines.models.for this guy \\o/</p>
|
|
{% acl_else %}
|
|
<p>Why can't I create a little machine for this guy ? :(</p>
|
|
{% acl_end %}
|
|
|
|
{% can_edit user %}
|
|
<p>Oh I can edit myself oO</p>
|
|
{% acl_else %}
|
|
<p>Sniff can't edit my own infos ...</p>
|
|
{% acl_end %}
|
|
|
|
**How to modify**:
|
|
To add a new acl function (can_xxx or cannot_xxx),
|
|
- if it's based on a model (like can_create), add an entry in
|
|
'get_callback' and register your tag with the other ones juste before
|
|
'acl_model_generic' definition
|
|
- if it's bases on an instance (like can_edit), just register yout tag with
|
|
the other ones juste before 'acl_instance_generic' definition
|
|
To add support for a new model, add an entry in 'get_model' and be sure
|
|
the acl function exists in the model definition
|
|
|
|
"""
|
|
import sys
|
|
|
|
from django import template
|
|
from django.template.base import Node, NodeList
|
|
from django.contrib.contenttypes.models import ContentType
|
|
|
|
|
|
register = template.Library()
|
|
|
|
|
|
def get_model(model_name):
|
|
"""Retrieve the model object from its name"""
|
|
splitted = model_name.split(".")
|
|
if len(splitted) > 1:
|
|
try:
|
|
app_label, name = splitted
|
|
except ValueError:
|
|
raise template.TemplateSyntaxError(
|
|
"%r is an inconsistent model name." % model_name
|
|
)
|
|
else:
|
|
app_label, name = None, splitted[0]
|
|
try:
|
|
if app_label is not None:
|
|
content_type = ContentType.objects.get(
|
|
model=name.lower(), app_label=app_label
|
|
)
|
|
else:
|
|
content_type = ContentType.objects.get(model=name.lower())
|
|
except ContentType.DoesNotExist:
|
|
raise template.TemplateSyntaxError(
|
|
"%r is not a valid model for an acl tag." % model_name
|
|
)
|
|
except ContentType.MultipleObjectsReturned:
|
|
raise template.TemplateSyntaxError(
|
|
"More than one model found for %r. Try with `app.model`." % model_name
|
|
)
|
|
return content_type.model_class()
|
|
|
|
|
|
def get_callback(tag_name, obj=None):
|
|
"""Return the right function to call back to check for acl"""
|
|
|
|
if tag_name == "can_create":
|
|
return acl_fct(obj.can_create, False)
|
|
if tag_name == "cannot_create":
|
|
return acl_fct(obj.can_create, True)
|
|
if tag_name == "can_edit":
|
|
return acl_fct(obj.can_edit, False)
|
|
if tag_name == "cannot_edit":
|
|
return acl_fct(obj.can_edit, True)
|
|
if tag_name == "can_edit_all":
|
|
return acl_fct(obj.can_edit_all, False)
|
|
if tag_name == "cannot_edit_all":
|
|
return acl_fct(obj.can_edit_all, True)
|
|
if tag_name == "can_delete":
|
|
return acl_fct(obj.can_delete, False)
|
|
if tag_name == "cannot_delete":
|
|
return acl_fct(obj.can_delete, True)
|
|
if tag_name == "can_delete_all":
|
|
return acl_fct(obj.can_delete_all, False)
|
|
if tag_name == "cannot_delete_all":
|
|
return acl_fct(obj.can_delete_all, True)
|
|
if tag_name == "can_view":
|
|
return acl_fct(obj.can_view, False)
|
|
if tag_name == "cannot_view":
|
|
return acl_fct(obj.can_view, True)
|
|
if tag_name == "can_view_all":
|
|
return acl_fct(obj.can_view_all, False)
|
|
if tag_name == "cannot_view_all":
|
|
return acl_fct(obj.can_view_all, True)
|
|
if tag_name == "can_view_app":
|
|
return acl_fct(
|
|
lambda x: (
|
|
not any(not sys.modules[o].can_view(x)[0] for o in obj),
|
|
None,
|
|
None,
|
|
),
|
|
False,
|
|
)
|
|
if tag_name == "cannot_view_app":
|
|
return acl_fct(
|
|
lambda x: (
|
|
not any(not sys.modules[o].can_view(x)[0] for o in obj),
|
|
None,
|
|
None,
|
|
),
|
|
True,
|
|
)
|
|
if tag_name == "can_edit_history":
|
|
return acl_fct(
|
|
lambda user: (user.has_perm("admin.change_logentry"), None, None), False
|
|
)
|
|
if tag_name == "cannot_edit_history":
|
|
return acl_fct(
|
|
lambda user: (user.has_perm("admin.change_logentry"), None, None), True
|
|
)
|
|
if tag_name == "can_view_any_app":
|
|
return acl_fct(
|
|
lambda x: (any(sys.modules[o].can_view(x)[0] for o in obj), None, None),
|
|
False,
|
|
)
|
|
if tag_name == "cannot_view_any_app":
|
|
return acl_fct(
|
|
lambda x: (any(sys.modules[o].can_view(x)[0] for o in obj), None, None),
|
|
True,
|
|
)
|
|
|
|
raise template.TemplateSyntaxError("%r tag is not a valid can_xxx tag." % tag_name)
|
|
|
|
|
|
def acl_fct(callback, reverse):
|
|
"""Build a function to use as an acl checker"""
|
|
|
|
def acl_fct_normal(user, *args, **kwargs):
|
|
"""The can_xxx checker callback"""
|
|
return callback(user, *args, **kwargs)
|
|
|
|
def acl_fct_reverse(user, *args, **kwargs):
|
|
"""The cannot_xxx checker callback"""
|
|
can, msg, permissions = callback(user, *args, **kwargs)
|
|
return not can, msg, permissions
|
|
|
|
return acl_fct_reverse if reverse else acl_fct_normal
|
|
|
|
|
|
@register.tag("can_edit_history")
|
|
@register.tag("cannot_edit_history")
|
|
def acl_history_filter(parser, token):
|
|
"""Templatetag for acl checking on history."""
|
|
tag_name, = token.split_contents()
|
|
|
|
callback = get_callback(tag_name)
|
|
oknodes = parser.parse(("acl_else", "acl_end"))
|
|
token = parser.next_token()
|
|
if token.contents == "acl_else":
|
|
konodes = parser.parse(("acl_end"))
|
|
token = parser.next_token()
|
|
else:
|
|
konodes = NodeList()
|
|
|
|
assert token.contents == "acl_end"
|
|
|
|
return AclNode(tag_name, callback, oknodes, konodes)
|
|
|
|
|
|
@register.tag("can_view_any_app")
|
|
@register.tag("cannot_view_any_app")
|
|
@register.tag("can_view_app")
|
|
@register.tag("cannot_view_app")
|
|
def acl_app_filter(parser, token):
|
|
"""Templatetag for acl checking on applications."""
|
|
try:
|
|
contents = token.split_contents()
|
|
tag_name = contents[0]
|
|
app_name = contents[1:]
|
|
except ValueError:
|
|
raise template.TemplateSyntaxError(
|
|
"%r tag require 1 argument: an application" % token.contents.split()[0]
|
|
)
|
|
for name in app_name:
|
|
if name not in sys.modules.keys():
|
|
raise template.TemplateSyntaxError(
|
|
"%r is not a registered application for acl." % name
|
|
)
|
|
|
|
callback = get_callback(tag_name, app_name)
|
|
|
|
oknodes = parser.parse(("acl_else", "acl_end"))
|
|
token = parser.next_token()
|
|
if token.contents == "acl_else":
|
|
konodes = parser.parse(("acl_end"))
|
|
token = parser.next_token()
|
|
else:
|
|
konodes = NodeList()
|
|
|
|
assert token.contents == "acl_end"
|
|
|
|
return AclNode(tag_name, callback, oknodes, konodes)
|
|
|
|
|
|
@register.tag("can_change")
|
|
@register.tag("cannot_change")
|
|
def acl_change_filter(parser, token):
|
|
"""Templatetag for acl checking a can_change_xxx function"""
|
|
|
|
try:
|
|
tag_content = token.split_contents()
|
|
tag_name = tag_content[0]
|
|
model_name = tag_content[1]
|
|
field_name = tag_content[2]
|
|
args = tag_content[3:]
|
|
except ValueError:
|
|
raise template.TemplateSyntaxError(
|
|
"%r tag require at least 2 argument: the model and the field."
|
|
% token.contents.split()[0]
|
|
)
|
|
|
|
model = get_model(model_name)
|
|
callback = getattr(model, "can_change_" + field_name)
|
|
|
|
# {% can_create %}
|
|
oknodes = parser.parse(("acl_else", "acl_end"))
|
|
token = parser.next_token()
|
|
|
|
# {% can_create_else %}
|
|
if token.contents == "acl_else":
|
|
konodes = parser.parse(("acl_end"))
|
|
token = parser.next_token()
|
|
else:
|
|
konodes = NodeList()
|
|
|
|
# {% can_create_end %}
|
|
assert token.contents == "acl_end"
|
|
|
|
return AclNode(tag_name, callback, oknodes, konodes, *args)
|
|
|
|
|
|
@register.tag("can_create")
|
|
@register.tag("cannot_create")
|
|
@register.tag("can_edit_all")
|
|
@register.tag("cannot_edit_all")
|
|
@register.tag("can_delete_all")
|
|
@register.tag("cannot_delete_all")
|
|
@register.tag("can_view_all")
|
|
@register.tag("cannot_view_all")
|
|
def acl_model_filter(parser, token):
|
|
"""Generic definition of an acl templatetag for acl based on model"""
|
|
|
|
try:
|
|
tag_content = token.split_contents()
|
|
tag_name = tag_content[0]
|
|
model_name = tag_content[1]
|
|
args = tag_content[2:]
|
|
except ValueError:
|
|
raise template.TemplateSyntaxError(
|
|
"%r tag require at least 1 argument: the model." % token.contents.split()[0]
|
|
)
|
|
|
|
model = get_model(model_name)
|
|
callback = get_callback(tag_name, model)
|
|
|
|
# {% can_create %}
|
|
oknodes = parser.parse(("acl_else", "acl_end"))
|
|
token = parser.next_token()
|
|
|
|
# {% can_create_else %}
|
|
if token.contents == "acl_else":
|
|
konodes = parser.parse(("acl_end"))
|
|
token = parser.next_token()
|
|
else:
|
|
konodes = NodeList()
|
|
|
|
# {% can_create_end %}
|
|
assert token.contents == "acl_end"
|
|
|
|
return AclNode(tag_name, callback, oknodes, konodes, *args)
|
|
|
|
|
|
@register.tag("can_edit")
|
|
@register.tag("cannot_edit")
|
|
@register.tag("can_delete")
|
|
@register.tag("cannot_delete")
|
|
@register.tag("can_view")
|
|
@register.tag("cannot_view")
|
|
def acl_instance_filter(parser, token):
|
|
"""Generic definition of an acl templatetag for acl based on instance"""
|
|
|
|
try:
|
|
tag_content = token.split_contents()
|
|
tag_name = tag_content[0]
|
|
instance_name = tag_content[1]
|
|
args = tag_content[2:]
|
|
except ValueError:
|
|
raise template.TemplateSyntaxError(
|
|
"%r tag require at least 1 argument: the instance."
|
|
% token.contents.split()[0]
|
|
)
|
|
|
|
# {% can_create %}
|
|
oknodes = parser.parse(("acl_else", "acl_end"))
|
|
token = parser.next_token()
|
|
|
|
# {% can_create_else %}
|
|
if token.contents == "acl_else":
|
|
konodes = parser.parse(("acl_end"))
|
|
token = parser.next_token()
|
|
else:
|
|
konodes = NodeList()
|
|
|
|
# {% can_create_end %}
|
|
assert token.contents == "acl_end"
|
|
|
|
return AclInstanceNode(tag_name, instance_name, oknodes, konodes, *args)
|
|
|
|
|
|
class AclNode(Node):
|
|
"""A node for the compiled ACL block when acl callback doesn't require
|
|
context."""
|
|
|
|
def __init__(self, tag_name, callback, oknodes, konodes, *args):
|
|
self.tag_name = tag_name
|
|
self.callback = callback
|
|
self.oknodes = oknodes
|
|
self.konodes = konodes
|
|
self.args = [template.Variable(arg) for arg in args]
|
|
|
|
def render(self, context):
|
|
resolved_args = [arg.resolve(context) for arg in self.args]
|
|
if context["user"].is_anonymous():
|
|
can = False
|
|
else:
|
|
can, _, _ = self.callback(context["user"], *(resolved_args))
|
|
if can:
|
|
return self.oknodes.render(context)
|
|
return self.konodes.render(context)
|
|
|
|
def __repr__(self):
|
|
return "<AclNode %s>" % self.tag_name
|
|
|
|
|
|
class AclInstanceNode(Node):
|
|
"""A node for the compiled ACL block when acl is based on instance"""
|
|
|
|
def __init__(self, tag_name, instance_name, oknodes, konodes, *args):
|
|
self.tag_name = tag_name
|
|
self.instance = template.Variable(instance_name)
|
|
self.oknodes = oknodes
|
|
self.konodes = konodes
|
|
self.args = [template.Variable(arg) for arg in args]
|
|
|
|
def render(self, context):
|
|
callback = get_callback(self.tag_name, self.instance.resolve(context))
|
|
resolved_args = [arg.resolve(context) for arg in self.args]
|
|
if context["user"].is_anonymous():
|
|
can = False
|
|
else:
|
|
can, _, _ = callback(context["user"], *(resolved_args))
|
|
if can:
|
|
return self.oknodes.render(context)
|
|
return self.konodes.render(context)
|
|
|
|
def __repr__(self):
|
|
return "<AclInstanceNode %s>" % self.tag_name
|