8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-09-20 22:44:08 +00:00
re2o/re2o/templatetags/acl.py

417 lines
13 KiB
Python
Raw Permalink Normal View History

# -*- 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.
"""
2017-12-02 00:42:07 +00:00
Set of templatetags for using acl in templates:
2017-12-02 01:46:51 +00:00
- 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**:
2017-12-02 01:46:51 +00:00
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**:
2017-12-02 01:46:51 +00:00
{% <acl_name> <obj> [arg1 [arg2 [...]]]%}
<template stuff>
2017-12-02 01:46:51 +00:00
[{% acl_else %}
<template stuff>]
2017-12-02 01:46:51 +00:00
{% 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>
2017-12-02 01:46:51 +00:00
{% acl_else %}
<p>Why can't I create a little machine for this guy ? :(</p>
2017-12-02 01:46:51 +00:00
{% 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 %}
2017-12-02 00:42:07 +00:00
**How to modify**:
2017-12-02 01:46:51 +00:00
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
2017-12-02 00:42:07 +00:00
"""
import sys
from django import template
from django.template.base import Node, NodeList
from django.contrib.contenttypes.models import ContentType
register = template.Library()
2018-06-23 14:39:03 +00:00
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:
2018-07-28 21:13:51 +00:00
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":
2017-12-02 01:46:51 +00:00
return acl_fct(obj.can_create, False)
if tag_name == "cannot_create":
2017-12-02 01:46:51 +00:00
return acl_fct(obj.can_create, True)
if tag_name == "can_edit":
2017-12-02 01:46:51 +00:00
return acl_fct(obj.can_edit, False)
if tag_name == "cannot_edit":
2017-12-02 01:46:51 +00:00
return acl_fct(obj.can_edit, True)
if tag_name == "can_edit_all":
2017-12-11 21:33:09 +00:00
return acl_fct(obj.can_edit_all, False)
if tag_name == "cannot_edit_all":
2017-12-11 21:33:09 +00:00
return acl_fct(obj.can_edit_all, True)
if tag_name == "can_delete":
2017-12-11 21:33:09 +00:00
return acl_fct(obj.can_delete, False)
if tag_name == "cannot_delete":
2017-12-11 21:33:09 +00:00
return acl_fct(obj.can_delete, True)
if tag_name == "can_delete_all":
2017-12-11 21:33:09 +00:00
return acl_fct(obj.can_delete_all, False)
if tag_name == "cannot_delete_all":
2017-12-11 21:33:09 +00:00
return acl_fct(obj.can_delete_all, True)
if tag_name == "can_view":
2017-12-11 21:33:09 +00:00
return acl_fct(obj.can_view, False)
if tag_name == "cannot_view":
2017-12-11 21:33:09 +00:00
return acl_fct(obj.can_view, True)
if tag_name == "can_view_all":
2017-12-11 21:33:09 +00:00
return acl_fct(obj.can_view_all, False)
if tag_name == "cannot_view_all":
2017-12-11 21:33:09 +00:00
return acl_fct(obj.can_view_all, True)
if tag_name == "can_view_app":
2018-04-13 23:42:22 +00:00
return acl_fct(
lambda x: (
not any(not sys.modules[o].can_view(x)[0] for o in obj),
2019-09-06 13:27:26 +00:00
None,
None,
2018-04-13 23:42:22 +00:00
),
False,
2018-04-13 23:42:22 +00:00
)
if tag_name == "cannot_view_app":
2018-04-13 23:42:22 +00:00
return acl_fct(
lambda x: (
not any(not sys.modules[o].can_view(x)[0] for o in obj),
2019-09-06 13:27:26 +00:00
None,
None,
2018-04-13 23:42:22 +00:00
),
True,
2018-04-13 23:42:22 +00:00
)
if tag_name == "can_edit_history":
2018-04-13 23:42:22 +00:00
return acl_fct(
lambda user: (user.has_perm("admin.change_logentry"), None, None), False
2018-04-13 23:42:22 +00:00
)
if tag_name == "cannot_edit_history":
2018-04-13 23:42:22 +00:00
return acl_fct(
lambda user: (user.has_perm("admin.change_logentry"), None, None), True
2018-04-13 23:42:22 +00:00
)
if tag_name == "can_view_any_app":
2018-04-13 23:42:22 +00:00
return acl_fct(
2019-09-06 13:27:26 +00:00
lambda x: (any(sys.modules[o].can_view(x)[0] for o in obj), None, None),
False,
2018-04-13 23:42:22 +00:00
)
if tag_name == "cannot_view_any_app":
2018-04-13 23:42:22 +00:00
return acl_fct(
2019-09-06 13:27:26 +00:00
lambda x: (any(sys.modules[o].can_view(x)[0] for o in obj), None, None),
True,
2018-04-13 23:42:22 +00:00
)
2017-12-28 15:00:37 +00:00
raise template.TemplateSyntaxError("%r tag is not a valid can_xxx tag." % tag_name)
2017-12-02 00:42:07 +00:00
def acl_fct(callback, reverse):
"""Build a function to use as an acl checker"""
2017-12-02 01:46:51 +00:00
def acl_fct_normal(user, *args, **kwargs):
2017-12-02 00:42:07 +00:00
"""The can_xxx checker callback"""
2017-12-02 01:46:51 +00:00
return callback(user, *args, **kwargs)
2017-12-02 00:42:07 +00:00
2017-12-02 01:46:51 +00:00
def acl_fct_reverse(user, *args, **kwargs):
2017-12-02 00:42:07 +00:00
"""The cannot_xxx checker callback"""
2019-09-06 13:27:26 +00:00
can, msg, permissions = callback(user, *args, **kwargs)
return not can, msg, permissions
2017-12-02 00:42:07 +00:00
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."""
2017-12-29 10:43:09 +00:00
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"
2019-09-06 13:27:26 +00:00
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")
2017-12-28 15:00:37 +00:00
def acl_app_filter(parser, token):
"""Templatetag for acl checking on applications."""
try:
2018-08-10 18:38:17 +00:00
contents = token.split_contents()
tag_name = contents[0]
app_name = contents[1:]
2017-12-28 15:00:37 +00:00
except ValueError:
raise template.TemplateSyntaxError(
"%r tag require 1 argument: an application" % token.contents.split()[0]
2017-12-28 15:00:37 +00:00
)
for name in app_name:
2018-04-13 23:42:22 +00:00
if name not in sys.modules.keys():
raise template.TemplateSyntaxError(
"%r is not a registered application for acl." % name
)
2017-12-28 15:00:37 +00:00
callback = get_callback(tag_name, app_name)
oknodes = parser.parse(("acl_else", "acl_end"))
2017-12-28 15:00:37 +00:00
token = parser.next_token()
if token.contents == "acl_else":
konodes = parser.parse(("acl_end"))
2017-12-28 15:00:37 +00:00
token = parser.next_token()
else:
konodes = NodeList()
assert token.contents == "acl_end"
2017-12-28 15:00:37 +00:00
2019-09-06 13:27:26 +00:00
return AclNode(tag_name, callback, oknodes, konodes)
2017-12-28 15:00:37 +00:00
2018-04-13 23:42:22 +00:00
@register.tag("can_change")
@register.tag("cannot_change")
2017-12-28 21:32:25 +00:00
def acl_change_filter(parser, token):
"""Templatetag for acl checking a can_change_xxx function"""
try:
tag_content = token.split_contents()
2019-09-06 13:27:26 +00:00
tag_name = tag_content[0]
2017-12-28 21:32:25 +00:00
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."
2017-12-28 21:32:25 +00:00
% token.contents.split()[0]
)
model = get_model(model_name)
callback = getattr(model, "can_change_" + field_name)
2017-12-28 21:32:25 +00:00
# {% can_create %}
oknodes = parser.parse(("acl_else", "acl_end"))
2017-12-28 21:32:25 +00:00
token = parser.next_token()
# {% can_create_else %}
if token.contents == "acl_else":
konodes = parser.parse(("acl_end"))
2017-12-28 21:32:25 +00:00
token = parser.next_token()
else:
konodes = NodeList()
# {% can_create_end %}
assert token.contents == "acl_end"
2017-12-28 21:32:25 +00:00
2019-09-06 13:27:26 +00:00
return AclNode(tag_name, callback, oknodes, konodes, *args)
2018-04-13 23:42:22 +00:00
@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")
2017-12-02 01:46:51 +00:00
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]
)
2017-12-02 01:46:51 +00:00
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"
2019-09-06 13:27:26 +00:00
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")
2017-12-02 01:46:51 +00:00
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."
2017-12-02 01:46:51 +00:00
% token.contents.split()[0]
)
# {% can_create %}
oknodes = parser.parse(("acl_else", "acl_end"))
2017-12-02 01:46:51 +00:00
token = parser.next_token()
# {% can_create_else %}
if token.contents == "acl_else":
konodes = parser.parse(("acl_end"))
2017-12-02 01:46:51 +00:00
token = parser.next_token()
else:
konodes = NodeList()
# {% can_create_end %}
assert token.contents == "acl_end"
2017-12-02 01:46:51 +00:00
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."""
2019-09-06 13:27:26 +00:00
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():
2018-01-08 11:35:19 +00:00
can = False
else:
can, _, _ = self.callback(context["user"], *(resolved_args))
if can:
return self.oknodes.render(context)
return self.konodes.render(context)
2017-12-02 01:46:51 +00:00
2019-09-06 13:27:26 +00:00
def __repr__(self):
return "<AclNode %s>" % self.tag_name
2019-09-06 13:27:26 +00:00
2017-12-02 01:46:51 +00:00
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():
2018-01-08 11:35:19 +00:00
can = False
else:
can, _, _ = callback(context["user"], *(resolved_args))
2017-12-02 01:46:51 +00:00
if can:
return self.oknodes.render(context)
return self.konodes.render(context)
2019-09-06 13:27:26 +00:00
def __repr__(self):
return "<AclInstanceNode %s>" % self.tag_name