mirror of https://gitlab.crans.org/bde/nk20
Optimize permissions, full support add perms, more fixtures
This commit is contained in:
parent
c653e0986e
commit
f80cb635d3
|
@ -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,13 +18,16 @@ 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,
|
||||||
|
model__app_label=model.app_label, # For polymorphic models, we don't filter on model type
|
||||||
|
type=type
|
||||||
|
).all():
|
||||||
permission = permission.about(
|
permission = permission.about(
|
||||||
user=user,
|
user=user,
|
||||||
club=membership.club,
|
club=membership.club,
|
||||||
|
@ -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"))
|
||||||
|
|
|
@ -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
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue