Optimize permissions, full support add perms, more fixtures

This commit is contained in:
Yohann D'ANELLO 2020-03-20 00:06:28 +01:00
parent c653e0986e
commit f80cb635d3
4 changed files with 329 additions and 72 deletions

View File

@ -7,7 +7,8 @@ from django.db.models import Q, F
from note.models import Note, NoteUser, NoteClub, NoteSpecial from note.models import Note, NoteUser, NoteClub, NoteSpecial
from note_kfet.middlewares import get_current_session from note_kfet.middlewares import get_current_session
from .models import Membership, RolePermissions, Club from permission.models import Permission
from .models import Membership, Club
from django.contrib.auth.backends import ModelBackend from django.contrib.auth.backends import ModelBackend
@ -17,28 +18,31 @@ class PermissionBackend(ModelBackend):
supports_inactive_user = False supports_inactive_user = False
@staticmethod @staticmethod
def permissions(user): def permissions(user, model, type):
for membership in Membership.objects.filter(user=user).all(): for membership in Membership.objects.filter(user=user).all():
if not membership.valid() or membership.roles is None: if not membership.valid() or membership.roles is None:
continue continue
for role_permissions in RolePermissions.objects.filter(role=membership.roles).all(): for permission in Permission.objects.filter(
for permission in role_permissions.permissions.all(): rolepermissions__role=membership.roles,
permission = permission.about( model__app_label=model.app_label, # For polymorphic models, we don't filter on model type
user=user, type=type
club=membership.club, ).all():
User=User, permission = permission.about(
Club=Club, user=user,
Membership=Membership, club=membership.club,
Note=Note, User=User,
NoteUser=NoteUser, Club=Club,
NoteClub=NoteClub, Membership=Membership,
NoteSpecial=NoteSpecial, Note=Note,
F=F, NoteUser=NoteUser,
Q=Q NoteClub=NoteClub,
) NoteSpecial=NoteSpecial,
if permission.mask.rank <= get_current_session().get("permission_mask", 0): F=F,
yield permission Q=Q
)
if permission.mask.rank <= get_current_session().get("permission_mask", 0):
yield permission
@staticmethod @staticmethod
def filter_queryset(user, model, t, field=None): def filter_queryset(user, model, t, field=None):
@ -60,10 +64,10 @@ class PermissionBackend(ModelBackend):
# Never satisfied # Never satisfied
query = Q(pk=-1) query = Q(pk=-1)
for perm in PermissionBackend.permissions(user): for perm in PermissionBackend.permissions(user, model, t):
if perm.field and field != perm.field: if perm.field and field != perm.field:
continue continue
if perm.model != model or perm.type != t: if perm.type != t or perm.model != model:
continue continue
query = query | perm.query query = query | perm.query
return query return query
@ -78,7 +82,9 @@ class PermissionBackend(ModelBackend):
perm = perm.split('.')[-1].split('_', 2) perm = perm.split('.')[-1].split('_', 2)
perm_type = perm[0] perm_type = perm[0]
perm_field = perm[2] if len(perm) == 3 else None perm_field = perm[2] if len(perm) == 3 else None
if any(permission.applies(obj, perm_type, perm_field) for permission in self.permissions(user_obj)): ct = ContentType.objects.get_for_model(obj)
if any(permission.applies(obj, perm_type, perm_field)
for permission in self.permissions(user_obj, ct, perm_type)):
return True return True
return False return False
@ -86,4 +92,5 @@ class PermissionBackend(ModelBackend):
return False return False
def get_all_permissions(self, user_obj, obj=None): def get_all_permissions(self, user_obj, obj=None):
return list(self.permissions(user_obj)) ct = ContentType.objects.get_for_model(obj)
return list(self.permissions(user_obj, ct, "view"))

View File

@ -239,6 +239,186 @@
"description": "Update a note balance with a transaction" "description": "Update a note balance with a transaction"
} }
}, },
{
"model": "permission.permission",
"pk": 19,
"fields": {
"model": 35,
"query": "[\"OR\", {\"pk\": [\"club\", \"note\", \"pk\"]}, {\"pk__in\": [\"NoteUser\", \"objects\", [\"filter\", {\"user__membership__club\": [\"club\"]}], [\"all\"]]}]",
"type": "view",
"mask": 2,
"field": "",
"description": "View notes of club members"
}
},
{
"model": "permission.permission",
"pk": 20,
"fields": {
"model": 37,
"query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], {\"amount__lte\": {\"F\": [\"ADD\", [\"F\", \"source__balance\"], 5000]}}]",
"type": "add",
"mask": 2,
"field": "",
"description": "Create transactions with a club"
}
},
{
"model": "permission.permission",
"pk": 21,
"fields": {
"model": 44,
"query": "[\"AND\", {\"destination\": [\"club\", \"note\"]}, {\"amount__lte\": {\"F\": [\"ADD\", [\"F\", \"source__balance\"], 50]}}]",
"type": "add",
"mask": 2,
"field": "",
"description": "Create transactions from buttons with a club"
}
},
{
"model": "permission.permission",
"pk": 22,
"fields": {
"model": 29,
"query": "{\"pk\": [\"club\", \"pk\"]}",
"type": "view",
"mask": 1,
"field": "",
"description": "View club infos"
}
},
{
"model": "permission.permission",
"pk": 23,
"fields": {
"model": 37,
"query": "{}",
"type": "change",
"mask": 1,
"field": "valid",
"description": "Update validation status of a transaction"
}
},
{
"model": "permission.permission",
"pk": 24,
"fields": {
"model": 37,
"query": "{}",
"type": "view",
"mask": 2,
"field": "",
"description": "View all transactions"
}
},
{
"model": "permission.permission",
"pk": 25,
"fields": {
"model": 43,
"query": "{}",
"type": "view",
"mask": 2,
"field": "",
"description": "Display credit/debit interface"
}
},
{
"model": "permission.permission",
"pk": 26,
"fields": {
"model": 43,
"query": "{}",
"type": "add",
"mask": 2,
"field": "",
"description": "Create credit/debit transaction"
}
},
{
"model": "permission.permission",
"pk": 27,
"fields": {
"model": 36,
"query": "{}",
"type": "view",
"mask": 2,
"field": "",
"description": "View button categories"
}
},
{
"model": "permission.permission",
"pk": 28,
"fields": {
"model": 36,
"query": "{}",
"type": "change",
"mask": 3,
"field": "",
"description": "Change button category"
}
},
{
"model": "permission.permission",
"pk": 29,
"fields": {
"model": 36,
"query": "{}",
"type": "add",
"mask": 3,
"field": "",
"description": "Add button category"
}
},
{
"model": "permission.permission",
"pk": 30,
"fields": {
"model": 38,
"query": "{}",
"type": "view",
"mask": 2,
"field": "",
"description": "View buttons"
}
},
{
"model": "permission.permission",
"pk": 31,
"fields": {
"model": 38,
"query": "{}",
"type": "add",
"mask": 3,
"field": "",
"description": "Add buttons"
}
},
{
"model": "permission.permission",
"pk": 32,
"fields": {
"model": 38,
"query": "{}",
"type": "change",
"mask": 3,
"field": "",
"description": "Update buttons"
}
},
{
"model": "permission.permission",
"pk": 33,
"fields": {
"model": 37,
"query": "{}",
"type": "add",
"mask": 2,
"field": "",
"description": "Create any transaction"
}
},
{ {
"model": "member.role", "model": "member.role",
"pk": 1, "pk": 1,
@ -253,6 +433,48 @@
"name": "Adh\u00e9rent Kfet" "name": "Adh\u00e9rent Kfet"
} }
}, },
{
"model": "member.role",
"pk": 3,
"fields": {
"name": "Pr\u00e9sident\u00b7e BDE"
}
},
{
"model": "member.role",
"pk": 4,
"fields": {
"name": "Tr\u00e9sorier\u00b7\u00e8re BDE"
}
},
{
"model": "member.role",
"pk": 5,
"fields": {
"name": "Respo info"
}
},
{
"model": "member.role",
"pk": 6,
"fields": {
"name": "GC Kfet"
}
},
{
"model": "member.role",
"pk": 7,
"fields": {
"name": "Pr\u00e9sident\u00b7e de club"
}
},
{
"model": "member.role",
"pk": 8,
"fields": {
"name": "Tr\u00e9sorier\u00b7\u00e8re de club"
}
},
{ {
"model": "member.rolepermissions", "model": "member.rolepermissions",
"pk": 1, "pk": 1,
@ -295,5 +517,38 @@
18 18
] ]
} }
},
{
"model": "member.rolepermissions",
"pk": 3,
"fields": {
"role": 8,
"permissions": [
19,
20,
21,
22
]
}
},
{
"model": "member.rolepermissions",
"pk": 4,
"fields": {
"role": 4,
"permissions": [
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33
]
}
} }
] ]

View File

@ -8,7 +8,7 @@ import operator
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.db.models import F, Q from django.db.models import F, Q, Model
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -33,7 +33,14 @@ class InstancedPermission:
if self.type == 'add': if self.type == 'add':
if permission_type == self.type: if permission_type == self.type:
return self.query(obj) # Don't increase indexes
obj.pk = 0
# Force insertion, no data verification, no trigger
Model.save(obj, force_insert=True)
ret = obj in self.model.model_class().objects.filter(self.query).all()
# Delete testing object
Model.delete(obj)
return ret
if permission_type == self.type: if permission_type == self.type:
if self.field and field_name != self.field: if self.field and field_name != self.field:
@ -151,6 +158,10 @@ class Permission(models.Model):
field = kwargs[value[0]] field = kwargs[value[0]]
for i in range(1, len(value)): for i in range(1, len(value)):
if isinstance(value[i], list): if isinstance(value[i], list):
if value[i][0] in kwargs:
field = Permission.compute_param(value[i], **kwargs)
continue
field = getattr(field, value[i][0]) field = getattr(field, value[i][0])
params = [] params = []
call_kwargs = {} call_kwargs = {}
@ -168,9 +179,6 @@ class Permission(models.Model):
return field return field
def _about(self, query, **kwargs): def _about(self, query, **kwargs):
if self.type == 'add':
# Handle add permission differently
return self._about_add(query, **kwargs)
if len(query) == 0: if len(query) == 0:
# The query is either [] or {} and # The query is either [] or {} and
# applies to all objects of the model # applies to all objects of the model
@ -200,48 +208,6 @@ class Permission(models.Model):
# TODO: find a better way to crash here # TODO: find a better way to crash here
raise Exception("query {} is wrong".format(self.query)) raise Exception("query {} is wrong".format(self.query))
def _about_add(self, _query, **kwargs):
query = _query
if len(query) == 0:
return lambda _: True
if isinstance(query, list):
if query[0] == 'AND':
return lambda obj: functools.reduce(operator.and_, [self._about_add(q, **kwargs)(obj) for q in query[1:]])
elif query[0] == 'OR':
return lambda obj: functools.reduce(operator.or_, [self._about_add(q, **kwargs)(obj) for q in query[1:]])
elif query[0] == 'NOT':
return lambda obj: not self._about_add(query[1], **kwargs)(obj)
elif isinstance(query, dict):
q_kwargs = {}
for key in query:
value = query[key]
if isinstance(value, list):
# It is a parameter we query its primary key
q_kwargs[key] = Permission.compute_param(value, **kwargs)
elif isinstance(value, dict):
# It is an F object
q_kwargs[key] = Permission.compute_f(query['F'], **kwargs)
else:
q_kwargs[key] = value
def func(obj):
nonlocal q_kwargs
for arg in q_kwargs:
spl = arg.split('__')
value = obj
last = None
for s in spl:
if not hasattr(obj, s):
last = s
break
value = getattr(obj, s)
if last == "lte": # TODO Add more filters
if value > q_kwargs[arg]:
return False
elif value != q_kwargs[arg]:
return False
return True
return func
def about(self, **kwargs): def about(self, **kwargs):
""" """
Return an InstancedPermission with the parameters Return an InstancedPermission with the parameters

View File

@ -2,7 +2,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db.models.signals import pre_save, pre_delete, post_save, post_delete
from logs import signals as logs_signals
from member.backends import PermissionBackend from member.backends import PermissionBackend
from note_kfet.middlewares import get_current_authenticated_user from note_kfet.middlewares import get_current_authenticated_user
@ -39,20 +41,46 @@ def pre_save_object(sender, instance, **kwargs):
model_name = model_name_full[1] model_name = model_name_full[1]
if qs.exists(): if qs.exists():
# We check if the user can change the model
# If the user has all right on a model, then OK
if PermissionBackend().has_perm(user, app_label + ".change_" + model_name, instance): if PermissionBackend().has_perm(user, app_label + ".change_" + model_name, instance):
return return
# In the other case, we check if he/she has the right to change one field
previous = qs.get() previous = qs.get()
for field in instance._meta.fields: for field in instance._meta.fields:
field_name = field.name field_name = field.name
old_value = getattr(previous, field.name) old_value = getattr(previous, field.name)
new_value = getattr(instance, field.name) new_value = getattr(instance, field.name)
# If the field wasn't modified, no need to check the permissions
if old_value == new_value: if old_value == new_value:
continue continue
if not PermissionBackend().has_perm(user, app_label + ".change_" + model_name + "_" + field_name, instance): if not PermissionBackend().has_perm(user, app_label + ".change_" + model_name + "_" + field_name, instance):
raise PermissionDenied raise PermissionDenied
else: else:
if not PermissionBackend().has_perm(user, app_label + ".add_" + model_name, instance): # We check if the user can add the model
# While checking permissions, the object will be inserted in the DB, then removed.
# We disable temporary the connectors
pre_save.disconnect(pre_save_object)
pre_delete.disconnect(pre_delete_object)
# We disable also logs connectors
pre_save.disconnect(logs_signals.pre_save_object)
post_save.disconnect(logs_signals.save_object)
post_delete.disconnect(logs_signals.delete_object)
# We check if the user has right to add the object
has_perm = PermissionBackend().has_perm(user, app_label + ".add_" + model_name, instance)
# Then we reconnect all
pre_save.connect(pre_save_object)
pre_delete.connect(pre_delete_object)
pre_save.connect(logs_signals.pre_save_object)
post_save.connect(logs_signals.save_object)
post_delete.connect(logs_signals.delete_object)
if not has_perm:
raise PermissionDenied raise PermissionDenied
@ -73,5 +101,6 @@ def pre_delete_object(sender, instance, **kwargs):
app_label = model_name_full[0] app_label = model_name_full[0]
model_name = model_name_full[1] model_name = model_name_full[1]
# We check if the user has rights to delete the object
if not PermissionBackend().has_perm(user, app_label + ".delete_" + model_name, instance): if not PermissionBackend().has_perm(user, app_label + ".delete_" + model_name, instance):
raise PermissionDenied raise PermissionDenied