Update a lot of things

This commit is contained in:
Yohann D'ANELLO 2020-03-07 13:12:17 +01:00
parent a014a97e14
commit 30ce17b644
11 changed files with 75 additions and 62 deletions

View File

@ -78,6 +78,10 @@ def save_object(sender, instance, **kwargs):
user, ip = get_user_and_ip(sender)
from django.contrib.auth.models import AnonymousUser
if isinstance(user, AnonymousUser):
user = None
if user is not None and instance._meta.label_lower == "auth.user" and previous:
# Don't save last login modifications
if instance.last_login != previous.last_login:

View File

@ -6,7 +6,7 @@ from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from .forms import ProfileForm
from .models import Club, Membership, Profile, Role
from .models import Club, Membership, Profile, Role, RolePermissions
class ProfileInline(admin.StackedInline):
@ -40,3 +40,4 @@ admin.site.register(User, CustomUserAdmin)
admin.site.register(Club)
admin.site.register(Membership)
admin.site.register(Role)
admin.site.register(RolePermissions)

View File

@ -1,33 +1,37 @@
from django.contribs.contenttype.models import ContentType
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from member.models import Club, Membership, RolePermissions
from django.contrib.auth.backends import ModelBackend
class PermissionBackend(object):
class PermissionBackend(ModelBackend):
supports_object_permissions = True
supports_anonymous_user = False
supports_inactive_user = False
def authenticate(self, username, password):
return None
def permissions(self, user, obj):
for membership in user.memberships.all():
if not membership.valid() or membership.role is None:
def permissions(self, user):
for membership in Membership.objects.filter(user=user).all():
if not membership.valid() or membership.roles is None:
continue
for permission in RolePermissions.objects.get(role=membership.role).permissions.objects.all():
for role_permissions in RolePermissions.objects.filter(role=membership.roles).all():
for permission in role_permissions.permissions.all():
permission = permission.about(user=user, club=membership.club)
yield permission
def has_perm(self, user_obj, perm, obj=None):
if user_obj.is_superuser:
return True
if obj is None:
return False
perm = perm.split('_', 3)
perm_type = perm[1]
perm_field = perm[2] if len(perm) == 3 else None
return any(permission.applies(obj, perm_type, perm_field) for obj in self.permissions(user_obj, obj))
return any(permission.applies(obj, perm_type, perm_field) for permission in self.permissions(user_obj))
def has_module_perms(self, user_obj, app_label):
return False
def get_all_permissions(self, user_obj, obj=None):
if obj is None:
return []
else:
return list(self.permissions(user_obj, obj))
return list(self.permissions(user_obj))

View File

@ -154,9 +154,9 @@ class Membership(models.Model):
def valid(self):
if self.date_end is not None:
return self.date_start <= datetime.datetime.now() < self.date_end
return self.date_start.toordinal() <= datetime.datetime.now().toordinal() < self.date_end.toordinal()
else:
return self.date_start <= datetime.datetime.now()
return self.date_start.toordinal() <= datetime.datetime.now().toordinal()
class Meta:
verbose_name = _('membership')

View File

@ -1,3 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-lateré
from django.contrib import admin
from .models import Permission

View File

@ -1,3 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig

View File

@ -1,3 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import functools
import json
import operator
@ -24,28 +27,25 @@ class InstancedPermission:
"""
if self.type == 'add':
if permission_type == self.type:
return self.query(obj)
return obj in self.model.modelclass().objects.get(self.query)
if ContentType.objects.get_for_model(obj) != self.model:
# The permission does not apply to the model
return False
if self.permission is None:
if permission_type == self.type:
if field_name is not None:
return field_name == self.field
else:
return True
else:
if field_name and field_name != self.field:
return False
elif obj in self.model.objects.get(self.query):
return True
return obj in self.model.model_class().objects.filter(self.query).all()
else:
return False
def __repr__(self):
if self.field:
return _("Can {type} {model}.{field} in {permission}").format(type=self.type, model=self.model, field=self.field, permission=self.permission)
return _("Can {type} {model}.{field} in {query}").format(type=self.type, model=self.model, field=self.field, query=self.query)
else:
return _("Can {type} {model} in {permission}").format(type=self.type, model=self.model, permission=self.permission)
return _("Can {type} {model} in {query}").format(type=self.type, model=self.model, query=self.query)
def __str__(self):
return self.__repr__()
class Permission(models.Model):
@ -61,24 +61,24 @@ class Permission(models.Model):
# A json encoded Q object with the following grammar
# query -> [] | {} (the empty query representing all objects)
# query -> ['AND', query, …] AND multiple queries
# | ['OR', query, …] OR multiple queries
# | ['NOT', query] Opposite of query
# query -> ["AND", query, …] AND multiple queries
# | ["OR", query, …] OR multiple queries
# | ["NOT", query] Opposite of query
# query -> {key: value, …} A list of fields and values of a Q object
# key -> string A field name
# value -> int | string | bool | null Literal values
# | [parameter] A parameter
# | {'F': oper} An F object
# | {"F": oper} An F object
# oper -> [string] A parameter
# | ['ADD', oper, …] Sum multiple F objects or literal
# | ['SUB', oper, oper] Substract two F objects or literal
# | ['MUL', oper, …] Multiply F objects or literals
# | ["ADD", oper, …] Sum multiple F objects or literal
# | ["SUB", oper, oper] Substract two F objects or literal
# | ["MUL", oper, …] Multiply F objects or literals
# | int | string | bool | null Literal values
# | ['F', string] A field
# | ["F", string] A field
#
# Examples:
# Q(is_admin=True) := {'is_admin': ['TYPE', 'bool', 'True']}
# ~Q(is_admin=True) := ['NOT', {'is_admin': ['TYPE', 'bool', 'True']}]
# Q(is_superuser=True) := {"is_superuser": true}
# ~Q(is_superuser=True) := ["NOT", {"is_superuser": true}]
query = models.TextField()
type = models.CharField(max_length=15, choices=PERMISSION_TYPES)
@ -94,23 +94,22 @@ class Permission(models.Model):
if self.field and self.type not in {'view', 'change'}:
raise ValidationError(_("Specifying field applies only to view and change permission types."))
def save(self):
def save(self, **kwargs):
self.full_clean()
super().save()
@staticmethod
def compute_f(_oper, **kwargs):
oper = _oper
def compute_f(oper, **kwargs):
if isinstance(oper, list):
if len(oper) == 1:
return kwargs[oper[0]].pk
elif len(oper) >= 2:
if oper[0] == 'ADD':
return functools.reduce(operator.add, [compute_f(oper, **kwargs) for oper in oper[1:]])
return functools.reduce(operator.add, [Permission.compute_f(oper, **kwargs) for oper in oper[1:]])
elif oper[0] == 'SUB':
return compute_f(oper[1], **kwargs) - compute_f(oper[2], **kwargs)
return Permission.compute_f(oper[1], **kwargs) - Permission.compute_f(oper[2], **kwargs)
elif oper[0] == 'MUL':
return functools.reduce(operator.mul, [compute_f(oper, **kwargs) for oper in oper[1:]])
return functools.reduce(operator.mul, [Permission.compute_f(oper, **kwargs) for oper in oper[1:]])
elif oper[0] == 'F':
return F(oper[1])
else:
@ -118,9 +117,7 @@ class Permission(models.Model):
# TODO: find a better way to crash here
raise Exception("F is wrong")
def _about(_self, _query, **kwargs):
self = _self
query = _query
def _about(self, query, **kwargs):
if self.type == 'add':
# Handle add permission differently
return self._about_add(query, **kwargs)
@ -145,7 +142,7 @@ class Permission(models.Model):
q_kwargs[key] = kwargs[value[0]].pk
elif isinstance(value, dict):
# It is an F object
q_kwargs[key] = compute_f(query['F'], **kwargs)
q_kwargs[key] = Permission.compute_f(query['F'], **kwargs)
else:
q_kwargs[key] = value
return Q(**q_kwargs)
@ -153,16 +150,15 @@ class Permission(models.Model):
# TODO: find a better way to crash here
raise Exception("query {} is wrong".format(self.query))
def _about_add(_self, _query, **kwargs):
self = _self
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(query, **kwargs)(obj) for query in query[1:]])
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(query, **kwargs)(obj) for query in query[1:]])
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):
@ -174,7 +170,7 @@ class Permission(models.Model):
q_kwargs[key] = kwargs[value[0]].pk
elif isinstance(value, dict):
# It is an F object
q_kwargs[key] = compute_f(query['F'], **kwargs)
q_kwargs[key] = Permission.compute_f(query['F'], **kwargs)
else:
q_kwargs[key] = value
def func(obj):

View File

@ -1,3 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.test import TestCase
# Create your tests here.

View File

@ -1,3 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.shortcuts import render
# Create your views here.

View File

@ -37,7 +37,6 @@ INSTALLED_APPS = [
# External apps
'polymorphic',
'guardian',
'reversion',
'crispy_forms',
'django_tables2',
@ -134,8 +133,8 @@ PASSWORD_HASHERS = [
# Django Guardian object permissions
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # this is default
'guardian.backends.ObjectPermissionBackend',
#'django.contrib.auth.backends.ModelBackend', # this is default
'member.backends.PermissionBackend',
'cas.backends.CASBackend',
)
@ -153,8 +152,6 @@ REST_FRAMEWORK = {
ANONYMOUS_USER_NAME = None # Disable guardian anonymous user
GUARDIAN_GET_CONTENT_TYPE = 'polymorphic.contrib.guardian.get_polymorphic_base_content_type'
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/

View File

@ -9,7 +9,6 @@ django-cas-server==1.1.0
django-crispy-forms==1.7.2
django-extensions==2.1.9
django-filter==2.2.0
django-guardian==2.1.0
django-polymorphic==2.0.3
djangorestframework==3.9.0
django-rest-polymorphic==0.1.8